diff options
author | Dave Camp <dave@ximian.com> | 2003-06-08 08:45:15 +0000 |
---|---|---|
committer | Dave Camp <campd@src.gnome.org> | 2003-06-08 08:45:15 +0000 |
commit | 9d4fe3275435ac10eb16963a257e02de19dd396f (patch) | |
tree | 13bb6d5fc81d1b5a3cf12fbf07ae0f929b952274 /src/file-manager/fm-properties-window.c | |
parent | b93efdc7a31f36d50944bf8f3d26474823bc55ca (diff) | |
download | nautilus-9d4fe3275435ac10eb16963a257e02de19dd396f.tar.gz |
Implement multi-file property dialogs.
2003-06-08 Dave Camp <dave@ximian.com>
* src/file-manager/fm-properties-window.h:
* libnautilus-private/nautilus-mime-actions.c:
(has_server_info_in_list), (server_info_list_intersection),
(nautilus_mime_get_property_components_for_files):
* libnautilus-private/nautilus-mime-actions.h:
* src/file-manager/fm-directory-view.c:
(open_properties_window_callback), (open_one_in_new_window):
* src/file-manager/fm-properties-window.c: (is_multi_file_window),
(get_original_file), (get_target_file_for_original_file),
(get_target_file), (add_prompt_and_separator),
(get_pixbuf_for_properties_window), (reset_icon),
(create_image_widget_for_file), (update_name_field),
(name_field_restore_original_name), (name_field_done_editing),
(file_has_keyword), (get_initial_emblem_state),
(emblem_button_toggled), (emblem_button_update),
(update_properties_window_title), (remove_from_dialog),
(mime_list_equal), (get_mime_list), (properties_window_update),
(file_list_attributes_identical), (file_list_get_string_attribute),
(file_list_all_local), (file_list_all_directories),
(value_field_update_internal), (value_field_update),
(attach_value_field_internal), (attach_value_field),
(attach_ellipsizing_value_field), (append_separator),
(directory_contents_value_field_update),
(attach_directory_contents_value_field), (append_title_value_pair),
(append_title_and_ellipsizing_value),
(update_visibility_of_item_count_fields),
(should_show_custom_icon_buttons), (should_show_file_type),
(should_show_accessed_date), (should_show_mime_type),
(should_show_link_target), (should_show_free_space),
(create_basic_page), (get_initial_emblems), (create_emblems_page),
(permission_change_callback), (get_initial_permission_state),
(permission_button_toggled), (permission_button_update),
(set_up_permissions_checkbox), (add_permissions_checkbox),
(append_special_execution_checkbox),
(append_special_execution_flags), (all_can_get_permissions),
(all_can_set_permissions), (get_initial_permissions),
(create_permissions_page), (get_uri_list),
(bonobo_page_activate_callback), (can_handle_multiple_files),
(append_bonobo_pages), (should_show_emblems),
(should_show_permissions), (get_pending_key), (startup_data_new),
(startup_data_free), (create_properties_window),
(get_target_file_list), (add_window), (remove_window),
(get_existing_window), (cancel_create_properties_window_callback),
(directory_view_destroyed_callback),
(cancel_call_when_ready_callback), (remove_pending),
(is_directory_ready_callback), (fm_properties_window_present),
(real_destroy), (real_finalize), (set_icon_callback),
(select_image_button_callback), (remove_image_button_callback):
Implement multi-file property dialogs.
* components/image_properties/Nautilus_View_image_properties.server
.in.in:
* components/image_properties/nautilus-image-properties-view.c:
(nautilus_image_properties_view_finalize), (get_property),
(set_property), (nautilus_image_properties_view_init):
* components/notes/Nautilus_View_notes.server.in.in:
* components/notes/nautilus-notes.c: (set_bonobo_properties),
(make_notes_view): Use new multipage property.
Diffstat (limited to 'src/file-manager/fm-properties-window.c')
-rw-r--r-- | src/file-manager/fm-properties-window.c | 1938 |
1 files changed, 1459 insertions, 479 deletions
diff --git a/src/file-manager/fm-properties-window.c b/src/file-manager/fm-properties-window.c index 0d64711ce..abacf0b65 100644 --- a/src/file-manager/fm-properties-window.c +++ b/src/file-manager/fm-properties-window.c @@ -39,6 +39,7 @@ #include <gtk/gtkalignment.h> #include <gtk/gtkcheckbutton.h> #include <gtk/gtkdnd.h> +#include <gtk/gtkeditable.h> #include <gtk/gtkentry.h> #include <gtk/gtkfilesel.h> #include <gtk/gtkhbox.h> @@ -79,16 +80,15 @@ #include <string.h> static GHashTable *windows; -static GHashTable *pending_files; +static GHashTable *pending_lists; + +struct FMPropertiesWindowDetails { + GList *original_files; + GList *target_files; -struct FMPropertiesWindowDetails { - NautilusFile *original_file; - NautilusFile *target_file; GtkNotebook *notebook; GtkWidget *remove_image_button; - guint file_changed_handler_id; - GtkTable *basic_table; GtkTable *permissions_table; @@ -106,6 +106,16 @@ struct FMPropertiesWindowDetails { int first_special_flags_row; int num_special_flags_rows; + GList *emblem_buttons; + GHashTable *initial_emblems; + + GList *permission_buttons; + GHashTable *initial_permissions; + + GList *value_fields; + + GList *mime_list; + gboolean deep_count_finished; }; @@ -130,9 +140,11 @@ enum { }; typedef struct { - NautilusFile *original_file; - NautilusFile *target_file; + GList *original_files; + GList *target_files; FMDirectoryView *directory_view; + char *pending_key; + GHashTable *pending_files; } StartupData; typedef struct { @@ -160,8 +172,15 @@ static GtkTargetEntry target_table[] = { #define STANDARD_EMBLEM_HEIGHT 52 #define EMBLEM_LABEL_SPACING 2 -static void create_properties_window_callback (NautilusFile *file, - gpointer data); +static void permission_button_update (FMPropertiesWindow *window, + GtkToggleButton *button); +static void value_field_update (FMPropertiesWindow *window, + GtkLabel *field); +static void properties_window_update (FMPropertiesWindow *window, + NautilusFile *changed_file); + +static void is_directory_ready_callback (NautilusFile *file, + gpointer data); static void cancel_group_change_callback (gpointer callback_data); static void cancel_owner_change_callback (gpointer callback_data); static void directory_view_destroyed_callback (FMDirectoryView *view, @@ -172,11 +191,10 @@ static void set_icon_callback (const char* icon_path, FMPropertiesWindow *properties_window); static void remove_image_button_callback (GtkWidget *widget, FMPropertiesWindow *properties_window); -static void remove_pending_file (StartupData *data, +static void remove_pending (StartupData *data, gboolean cancel_call_when_ready, gboolean cancel_timed_wait, gboolean cancel_destroy_handler); - static void append_bonobo_pages (FMPropertiesWindow *window); GNOME_CLASS_BOILERPLATE (FMPropertiesWindow, fm_properties_window, @@ -210,6 +228,72 @@ file_name_pair_free (FileNamePair *pair) g_free (pair); } +static gboolean +is_multi_file_window (FMPropertiesWindow *window) +{ + GList *l; + int count; + + count = 0; + + for (l = window->details->original_files; l != NULL; l = l->next) { + if (!nautilus_file_is_gone (NAUTILUS_FILE (l->data))) { + count++; + if (count > 1) { + return TRUE; + } + } + } + + return FALSE; +} + +static NautilusFile * +get_original_file (FMPropertiesWindow *window) +{ + g_return_val_if_fail (!is_multi_file_window (window), NULL); + + return NAUTILUS_FILE (window->details->original_files->data); +} + +static NautilusFile * +get_target_file_for_original_file (NautilusFile *file) +{ + NautilusFile *target_file; + char *uri_to_display; + NautilusDesktopLink *link; + + target_file = NULL; + if (NAUTILUS_IS_DESKTOP_ICON_FILE (file)) { + link = nautilus_desktop_icon_file_get_link (NAUTILUS_DESKTOP_ICON_FILE (file)); + + /* map to linked URI for these types of links */ + uri_to_display = nautilus_desktop_link_get_activation_uri (link); + if (uri_to_display) { + target_file = nautilus_file_get (uri_to_display); + g_free (uri_to_display); + } + + g_object_unref (link); + } + + if (target_file != NULL) { + return target_file; + } + + /* Ref passed-in file here since we've decided to use it. */ + nautilus_file_ref (file); + return file; +} + +static NautilusFile * +get_target_file (FMPropertiesWindow *window) +{ + g_return_val_if_fail (!is_multi_file_window (window), NULL); + + return NAUTILUS_FILE (window->details->target_files->data); +} + static void add_prompt (GtkVBox *vbox, const char *prompt_text, gboolean pack_at_start) { @@ -236,16 +320,24 @@ add_prompt_and_separator (GtkVBox *vbox, const char *prompt_text) separator_line = gtk_hseparator_new (); gtk_widget_show (separator_line); gtk_box_pack_end (GTK_BOX (vbox), separator_line, TRUE, TRUE, GNOME_PAD_BIG); -} +} static GdkPixbuf * get_pixbuf_for_properties_window (NautilusFile *file) { - g_assert (NAUTILUS_IS_FILE (file)); + g_return_val_if_fail (file == NULL || NAUTILUS_IS_FILE (file), NULL); - return nautilus_icon_factory_get_pixbuf_for_file (file, NULL, NAUTILUS_ICON_SIZE_STANDARD); + if (file) { + return nautilus_icon_factory_get_pixbuf_for_file (file, NULL, NAUTILUS_ICON_SIZE_STANDARD); + } else { + return nautilus_icon_factory_get_pixbuf_from_name ("gnome-fs-regular", + NULL, + NAUTILUS_ICON_SIZE_STANDARD, + NULL); + } } + static void update_properties_window_icon (GtkImage *image) { @@ -263,7 +355,6 @@ update_properties_window_icon (GtkImage *image) g_object_unref (pixbuf); } - /* utility to test if a uri refers to a local image */ static gboolean uri_is_local_image (const char *uri) @@ -286,19 +377,29 @@ uri_is_local_image (const char *uri) return TRUE; } + static void reset_icon (FMPropertiesWindow *properties_window) { - NautilusFile *file; - - file = properties_window->details->original_file; - - nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL, NULL); - nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_ICON_SCALE, NULL, NULL); + GList *l; + + for (l = properties_window->details->original_files; l != NULL; l = l->next) { + NautilusFile *file; + + file = NAUTILUS_FILE (l->data); + + nautilus_file_set_metadata (file, + NAUTILUS_METADATA_KEY_ICON_SCALE, + NULL, NULL); + nautilus_file_set_metadata (file, + NAUTILUS_METADATA_KEY_CUSTOM_ICON, + NULL, NULL); + } gtk_widget_set_sensitive (properties_window->details->remove_image_button, FALSE); } + static void fm_properties_window_drag_data_received (GtkWidget *widget, GdkDragContext *context, int x, int y, @@ -372,81 +473,118 @@ create_image_widget_for_file (NautilusFile *file) g_signal_connect (image, "drag_data_received", G_CALLBACK (fm_properties_window_drag_data_received), NULL); - gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf); g_object_unref (pixbuf); - nautilus_file_ref (file); - g_object_set_data_full (G_OBJECT (image), "nautilus_file", - file, (GDestroyNotify) nautilus_file_unref); + if (file) { + nautilus_file_ref (file); + g_object_set_data_full (G_OBJECT (image), "nautilus_file", + file, (GDestroyNotify) nautilus_file_unref); + + /* Name changes can also change icon (since name is determined by MIME type) */ + g_signal_connect_object (file, "changed", + G_CALLBACK (update_properties_window_icon), + image, G_CONNECT_SWAPPED); + } + /* React to icon theme changes. */ g_signal_connect_object (nautilus_icon_factory_get (), "icons_changed", G_CALLBACK (update_properties_window_icon), image, G_CONNECT_SWAPPED); - /* Name changes can also change icon (since name is determined by MIME type) */ - g_signal_connect_object (file, "changed", - G_CALLBACK (update_properties_window_icon), - image, G_CONNECT_SWAPPED); return image; } static void -name_field_update_to_match_file (NautilusEntry *name_field) +update_name_field (FMPropertiesWindow *window) { NautilusFile *file; const char *original_name; char *current_name, *displayed_name; - file = g_object_get_data (G_OBJECT (name_field), "nautilus_file"); + if (is_multi_file_window (window)) { + /* Multifile property dialog, show all names */ + GString *str; + char *name; + gboolean first; + GList *l; + + str = g_string_new (""); - if (file == NULL || nautilus_file_is_gone (file)) { - gtk_widget_set_sensitive (GTK_WIDGET (name_field), FALSE); - gtk_entry_set_text (GTK_ENTRY (name_field), ""); - return; - } + first = TRUE; - original_name = (const char *) g_object_get_data (G_OBJECT (name_field), - "original_name"); + for (l = window->details->original_files; l != NULL; l = l->next) { + file = NAUTILUS_FILE (l->data); - /* If the file name has changed since the original name was stored, - * update the text in the text field, possibly (deliberately) clobbering - * an edit in progress. If the name hasn't changed (but some other - * aspect of the file might have), then don't clobber changes. - */ - current_name = nautilus_file_get_display_name (file); - if (original_name == NULL || - eel_strcmp (original_name, current_name) != 0) { - g_object_set_data_full (G_OBJECT (name_field), - "original_name", - current_name, - g_free); - - /* Only reset the text if it's different from what is - * currently showing. This causes minimal ripples (e.g. - * selection change). - */ - displayed_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1); - if (strcmp (displayed_name, current_name) != 0) { - gtk_entry_set_text (GTK_ENTRY (name_field), current_name); + if (!nautilus_file_is_gone (file)) { + if (!first) { + g_string_append (str, ", "); + } + first = FALSE; + + name = nautilus_file_get_display_name (file); + g_string_append (str, name); + g_free (name); + } } - g_free (displayed_name); + gtk_entry_set_text (GTK_ENTRY (window->details->name_field), + str->str); + g_string_free (str, TRUE); + + gtk_editable_set_editable (GTK_EDITABLE (window->details->name_field), + FALSE); } else { - g_free (current_name); - } + NautilusFile *file; - /* - * The UI would look better here if the name were just drawn as - * a plain label in the case where it's not editable, with no - * border at all. That doesn't seem to be possible with GtkEntry, - * so we'd have to swap out the widget to achieve it. I don't - * care enough to change this now. - */ - gtk_widget_set_sensitive (GTK_WIDGET (name_field), - nautilus_file_can_rename (file)); + file = get_original_file (window); + + if (file == NULL || nautilus_file_is_gone (file)) { + gtk_entry_set_text (GTK_ENTRY (window->details->name_field), ""); + return; + } + + original_name = (const char *) g_object_get_data (G_OBJECT (window->details->name_field), + "original_name"); + + /* If the file name has changed since the original name was stored, + * update the text in the text field, possibly (deliberately) clobbering + * an edit in progress. If the name hasn't changed (but some other + * aspect of the file might have), then don't clobber changes. + */ + current_name = nautilus_file_get_display_name (file); + if (original_name == NULL || + eel_strcmp (original_name, current_name) != 0) { + g_object_set_data_full (G_OBJECT (window->details->name_field), + "original_name", + current_name, + g_free); + + /* Only reset the text if it's different from what is + * currently showing. This causes minimal ripples (e.g. + * selection change). + */ + displayed_name = gtk_editable_get_chars (GTK_EDITABLE (window->details->name_field), 0, -1); + if (strcmp (displayed_name, current_name) != 0) { + gtk_entry_set_text (GTK_ENTRY (window->details->name_field), current_name); + } + g_free (displayed_name); + } else { + g_free (current_name); + } + + /* + * The UI would look better here if the name were just drawn as + * a plain label in the case where it's not editable, with no + * border at all. That doesn't seem to be possible with GtkEntry, + * so we'd have to swap out the widget to achieve it. I don't + * care enough to change this now. + */ + gtk_widget_set_sensitive (GTK_WIDGET (window->details->name_field), + nautilus_file_can_rename (file)); + } } static void @@ -457,6 +595,11 @@ name_field_restore_original_name (NautilusEntry *name_field) original_name = (const char *) g_object_get_data (G_OBJECT (name_field), "original_name"); + + if (!original_name) { + return; + } + displayed_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1); if (strcmp (original_name, displayed_name) != 0) { @@ -503,11 +646,14 @@ name_field_done_editing (NautilusEntry *name_field, FMPropertiesWindow *window) NautilusFile *file; char *new_name; - g_assert (NAUTILUS_IS_ENTRY (name_field)); + g_return_if_fail (NAUTILUS_IS_ENTRY (name_field)); - file = g_object_get_data (G_OBJECT (name_field), "nautilus_file"); + /* Don't apply if the dialog has more than one file */ + if (is_multi_file_window (window)) { + return; + } - g_assert (NAUTILUS_IS_FILE (file)); + file = get_original_file (window); /* This gets called when the window is closed, which might be * caused by the file having been deleted. @@ -557,74 +703,184 @@ name_field_activate (NautilusEntry *name_field, gpointer callback_data) nautilus_entry_select_all_at_idle (name_field); } -static void -property_button_update (GtkToggleButton *button) +static gboolean +file_has_keyword (NautilusFile *file, const char *keyword) { - NautilusFile *file; - char *name; GList *keywords, *word; - file = g_object_get_data (G_OBJECT (button), "nautilus_file"); - name = g_object_get_data (G_OBJECT (button), "nautilus_property_name"); + keywords = nautilus_file_get_keywords (file); + word = g_list_find_custom (keywords, keyword, (GCompareFunc) strcmp); + eel_g_list_free_deep (keywords); + + return (word != NULL); +} - /* Handle the case where there's nothing to toggle. */ - if (file == NULL || nautilus_file_is_gone (file) || name == NULL) { - gtk_widget_set_sensitive (GTK_WIDGET (button), FALSE); - gtk_toggle_button_set_active (button, FALSE); - return; +static void +get_initial_emblem_state (FMPropertiesWindow *window, + const char *name, + GList **on, + GList **off) +{ + GList *l; + + *on = NULL; + *off = NULL; + + for (l = window->details->original_files; l != NULL; l = l->next) { + GList *initial_emblems; + + initial_emblems = g_hash_table_lookup (window->details->initial_emblems, + l->data); + + if (g_list_find_custom (initial_emblems, name, (GCompareFunc) strcmp)) { + *on = g_list_prepend (*on, l->data); + } else { + *off = g_list_prepend (*off, l->data); + } } - - /* Check and see if it's in there. */ - keywords = nautilus_file_get_keywords (file); - word = g_list_find_custom (keywords, name, (GCompareFunc) strcmp); - gtk_toggle_button_set_active (button, word != NULL); } static void -property_button_toggled (GtkToggleButton *button) +emblem_button_toggled (GtkToggleButton *button, + FMPropertiesWindow *window) { - NautilusFile *file; + GList *l; + GList *keywords; + GList *word; char *name; - GList *keywords, *word; - - file = g_object_get_data (G_OBJECT (button), "nautilus_file"); - name = g_object_get_data (G_OBJECT (button), "nautilus_property_name"); - - /* Handle the case where there's nothing to toggle. */ - if (file == NULL || nautilus_file_is_gone (file) || name == NULL) { - return; + GList *files_on; + GList *files_off; + + name = g_object_get_data (G_OBJECT (button), "nautilus_emblem_name"); + + files_on = NULL; + files_off = NULL; + if (gtk_toggle_button_get_active (button) + && !gtk_toggle_button_get_inconsistent (button)) { + /* Go to the initial state unless the initial state was + consistent */ + get_initial_emblem_state (window, name, + &files_on, &files_off); + + if (!(files_on && files_off)) { + g_list_free (files_on); + g_list_free (files_off); + files_on = g_list_copy (window->details->original_files); + files_off = NULL; + } + } else if (gtk_toggle_button_get_inconsistent (button) + && !gtk_toggle_button_get_active (button)) { + files_on = g_list_copy (window->details->original_files); + files_off = NULL; + } else { + files_off = g_list_copy (window->details->original_files); + files_on = NULL; } + + g_signal_handlers_block_by_func (G_OBJECT (button), + G_CALLBACK (emblem_button_toggled), + window); + + gtk_toggle_button_set_active (button, files_on != NULL); + gtk_toggle_button_set_inconsistent (button, files_on && files_off); - /* Check and see if it's already there. */ - keywords = nautilus_file_get_keywords (file); - word = g_list_find_custom (keywords, name, (GCompareFunc) strcmp); - if (gtk_toggle_button_get_active (button)) { - if (word == NULL) { + g_signal_handlers_unblock_by_func (G_OBJECT (button), + G_CALLBACK (emblem_button_toggled), + window); + + for (l = files_on; l != NULL; l = l->next) { + NautilusFile *file; + + file = NAUTILUS_FILE (l->data); + + keywords = nautilus_file_get_keywords (file); + + word = g_list_find_custom (keywords, name, (GCompareFunc)strcmp); + if (!word) { keywords = g_list_prepend (keywords, g_strdup (name)); } - } else { - if (word != NULL) { + nautilus_file_set_keywords (file, keywords); + eel_g_list_free_deep (keywords); + } + + for (l = files_off; l != NULL; l = l->next) { + NautilusFile *file; + + file = NAUTILUS_FILE (l->data); + + keywords = nautilus_file_get_keywords (file); + + word = g_list_find_custom (keywords, name, (GCompareFunc)strcmp); + if (word) { keywords = g_list_remove_link (keywords, word); eel_g_list_free_deep (word); } + nautilus_file_set_keywords (file, keywords); + eel_g_list_free_deep (keywords); + } + + g_list_free (files_on); + g_list_free (files_off); +} + +static void +emblem_button_update (FMPropertiesWindow *window, + GtkToggleButton *button) +{ + GList *l; + char *name; + gboolean all_set; + gboolean all_unset; + + name = g_object_get_data (G_OBJECT (button), "nautilus_emblem_name"); + + all_set = TRUE; + all_unset = TRUE; + for (l = window->details->original_files; l != NULL; l = l->next) { + gboolean has_keyword; + NautilusFile *file; + + file = NAUTILUS_FILE (l->data); + + has_keyword = file_has_keyword (file, name); + + if (has_keyword) { + all_unset = FALSE; + } else { + all_set = FALSE; + } } - nautilus_file_set_keywords (file, keywords); - eel_g_list_free_deep (keywords); + + g_signal_handlers_block_by_func (G_OBJECT (button), + G_CALLBACK (emblem_button_toggled), + window); + + gtk_toggle_button_set_active (button, !all_unset); + gtk_toggle_button_set_inconsistent (button, !all_unset && !all_set); + + g_signal_handlers_unblock_by_func (G_OBJECT (button), + G_CALLBACK (emblem_button_toggled), + window); + } static void -update_properties_window_title (GtkWindow *window, NautilusFile *file) +update_properties_window_title (FMPropertiesWindow *window) { char *name, *title; - g_assert (NAUTILUS_IS_FILE (file)); - g_assert (GTK_IS_WINDOW (window)); + g_return_if_fail (GTK_IS_WINDOW (window)); - name = nautilus_file_get_display_name (file); - title = g_strdup_printf (_("%s Properties"), name); - gtk_window_set_title (window, title); + if (is_multi_file_window (window)) { + title = g_strdup_printf (_("Properties")); + } else { + name = nautilus_file_get_display_name (get_original_file (window)); + title = g_strdup_printf (_("%s Properties"), name); + g_free (name); + } + + gtk_window_set_title (GTK_WINDOW (window), title); - g_free (name); g_free (title); } @@ -659,54 +915,247 @@ refresh_bonobo_pages (FMPropertiesWindow *window) } static void -properties_window_file_changed_callback (FMPropertiesWindow *window, NautilusFile *file) +remove_from_dialog (FMPropertiesWindow *window, + NautilusFile *file) { - g_assert (GTK_IS_WINDOW (window)); - g_assert (NAUTILUS_IS_FILE (file)); + int index; + GList *original_link; + GList *target_link; + NautilusFile *original_file; + NautilusFile *target_file; - if (nautilus_file_is_gone (file)) { - gtk_widget_destroy (GTK_WIDGET (window)); + index = g_list_index (window->details->target_files, file); + if (index == -1) { + index = g_list_index (window->details->original_files, file); + g_return_if_fail (index != -1); + } + + original_link = g_list_nth (window->details->original_files, index); + target_link = g_list_nth (window->details->target_files, index); + + g_return_if_fail (original_link && target_link); + + original_file = NAUTILUS_FILE (original_link->data); + target_file = NAUTILUS_FILE (target_link->data); + + window->details->original_files = g_list_remove_link (window->details->original_files, original_link); + g_list_free (original_link); + + window->details->target_files = g_list_remove_link (window->details->original_files, target_link); + g_list_free (target_link); + + g_hash_table_remove (window->details->initial_emblems, original_file); + g_hash_table_remove (window->details->initial_permissions, target_file); + + g_signal_handlers_disconnect_by_func (original_file, + G_CALLBACK (properties_window_update), + window); + g_signal_handlers_disconnect_by_func (target_file, + G_CALLBACK (properties_window_update), + window); + + nautilus_file_monitor_remove (original_file, window); + nautilus_file_monitor_remove (target_file, window); + + nautilus_file_unref (original_file); + nautilus_file_unref (target_file); + +} + +static gboolean +mime_list_equal (GList *a, GList *b) +{ + while (a && b) { + if (strcmp (a->data, b->data)) { + return FALSE; + } + a = a->next; + b = b->next; + } + + return (a == b); +} + +static GList * +get_mime_list (FMPropertiesWindow *window) +{ + GList *ret; + GList *l; + + ret = NULL; + for (l = window->details->target_files; l != NULL; l = l->next) { + ret = g_list_append (ret, nautilus_file_get_mime_type (NAUTILUS_FILE (l->data))); + } + ret = g_list_reverse (ret); + return ret; +} + +static void +properties_window_update (FMPropertiesWindow *window, + NautilusFile *changed_file) +{ + GList *l; + GList *mime_list; + + if (changed_file && nautilus_file_is_gone (changed_file)) { + remove_from_dialog (window, changed_file); + changed_file = NULL; + + /* Remove the file from the property dialog */ + if (window->details->original_files == NULL) { + gtk_widget_destroy (GTK_WIDGET (window)); + return; + } + + } + + if (!changed_file + || g_list_find (window->details->original_files, changed_file)) { + + update_properties_window_title (window); + + update_name_field (window); + + for (l = window->details->emblem_buttons; l != NULL; l = l->next) { + emblem_button_update (window, GTK_TOGGLE_BUTTON (l->data)); + } + + /* If any of the value fields start to depend on the original + * value, value_field_updates should be added here */ + } + + if (!changed_file + || g_list_find (window->details->target_files, changed_file)) { + for (l = window->details->permission_buttons; l != NULL; l = l->next) { + permission_button_update (window, GTK_TOGGLE_BUTTON (l->data)); + } + + for (l = window->details->value_fields; l != NULL; l = l->next) { + value_field_update (window, GTK_LABEL (l->data)); + } + } + + mime_list = get_mime_list (window); + + if (!window->details->mime_list) { + window->details->mime_list = mime_list; } else { - char *orig_mime_type; - char *new_mime_type; + if (!mime_list_equal (window->details->mime_list, mime_list)) { + refresh_bonobo_pages (window); + } + + eel_g_list_free_deep (window->details->mime_list); + window->details->mime_list = mime_list; + } +} + +static gboolean +file_list_attributes_identical (GList *file_list, const char *attribute_name) +{ + gboolean identical; + char *first_attr; + GList *l; + + first_attr = NULL; + identical = TRUE; + + for (l = file_list; l != NULL; l = l->next) { + NautilusFile *file; - orig_mime_type = nautilus_file_get_mime_type - (window->details->target_file); - nautilus_file_unref (window->details->target_file); + file = NAUTILUS_FILE (l->data); + + if (nautilus_file_is_gone (file)) { + continue; + } + + if (first_attr == NULL) { + first_attr = nautilus_file_get_string_attribute_with_default (file, attribute_name); + } else { + char *attr; + attr = nautilus_file_get_string_attribute_with_default (file, attribute_name); + if (strcmp (attr, first_attr)) { + identical = FALSE; + g_free (attr); + break; + } + g_free (attr); + } + } - window->details->target_file = nautilus_file_ref (file); + g_free (first_attr); + return identical; +} - update_properties_window_title (GTK_WINDOW (window), file); +static char * +file_list_get_string_attribute (GList *file_list, + const char *attribute_name, + const char *inconsistent_value) +{ + if (file_list_attributes_identical (file_list, attribute_name)) { + GList *l; + + for (l = file_list; l != NULL; l = l->next) { + NautilusFile *file; + + file = NAUTILUS_FILE (l->data); + if (!nautilus_file_is_gone (file)) { + return nautilus_file_get_string_attribute_with_default + (file, + attribute_name); + } + } + return g_strdup (_("unknown")); + } else { + return g_strdup (inconsistent_value); + } +} - new_mime_type = nautilus_file_get_mime_type - (window->details->target_file); - if (strcmp (orig_mime_type, new_mime_type) != 0) { - refresh_bonobo_pages (window); +static gboolean +file_list_all_local (GList *file_list) +{ + GList *l; + for (l = file_list; l != NULL; l = l->next) { + if (!nautilus_file_is_local (NAUTILUS_FILE (l->data))) { + return FALSE; } + } + return TRUE; +} - g_free (orig_mime_type); - g_free (new_mime_type); +static gboolean +file_list_all_directories (GList *file_list) +{ + GList *l; + for (l = file_list; l != NULL; l = l->next) { + if (!nautilus_file_is_directory (NAUTILUS_FILE (l->data))) { + return FALSE; + } } + return TRUE; } static void value_field_update_internal (GtkLabel *label, - NautilusFile *file, + GList *file_list, gboolean ellipsize_text) { const char *attribute_name; char *attribute_value; + char *inconsistent_string; g_assert (GTK_IS_LABEL (label)); - g_assert (NAUTILUS_IS_FILE (file)); g_assert (!ellipsize_text || EEL_IS_ELLIPSIZING_LABEL (label)); attribute_name = g_object_get_data (G_OBJECT (label), "file_attribute"); - attribute_value = nautilus_file_get_string_attribute_with_default (file, attribute_name); + inconsistent_string = g_object_get_data (G_OBJECT (label), "inconsistent_string"); + attribute_value = file_list_get_string_attribute (file_list, + attribute_name, + inconsistent_string); if (ellipsize_text) { - eel_ellipsizing_label_set_text (EEL_ELLIPSIZING_LABEL (label), attribute_value); + eel_ellipsizing_label_set_text (EEL_ELLIPSIZING_LABEL (label), + attribute_value); } else { gtk_label_set_text (label, attribute_value); } @@ -714,15 +1163,19 @@ value_field_update_internal (GtkLabel *label, } static void -value_field_update (GtkLabel *label, NautilusFile *file) +value_field_update (FMPropertiesWindow *window, GtkLabel *label) { - value_field_update_internal (label, file, FALSE); -} + gboolean ellipsize_text; + gboolean use_original; -static void -ellipsizing_value_field_update (GtkLabel *label, NautilusFile *file) -{ - value_field_update_internal (label, file, TRUE); + ellipsize_text = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (label), "ellipsize_text")); + use_original = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (label), "use_original")); + + value_field_update_internal (label, + (use_original ? + window->details->original_files : + window->details->target_files), + ellipsize_text); } static GtkLabel * @@ -786,11 +1239,13 @@ attach_ellipsizing_value_label (GtkTable *table, } static void -attach_value_field_internal (GtkTable *table, +attach_value_field_internal (FMPropertiesWindow *window, + GtkTable *table, int row, int column, - NautilusFile *file, const char *file_attribute_name, + const char *inconsistent_string, + gboolean show_original, gboolean ellipsize_text) { GtkLabel *value_field; @@ -801,43 +1256,52 @@ attach_value_field_internal (GtkTable *table, value_field = attach_value_label (table, row, column, ""); } - /* Stash a copy of the file attribute name in this field for the callback's sake. */ + /* Stash a copy of the file attribute name in this field for the callback's sake. */ g_object_set_data_full (G_OBJECT (value_field), "file_attribute", g_strdup (file_attribute_name), g_free); - /* Fill in the value. */ - if (ellipsize_text) { - ellipsizing_value_field_update (value_field, file); - } else { - value_field_update (value_field, file); - } + g_object_set_data_full (G_OBJECT (value_field), "inconsistent_string", + g_strdup (inconsistent_string), g_free); - /* Connect to signal to update value when file changes. */ - g_signal_connect_object (file, "changed", - ellipsize_text - ? G_CALLBACK (ellipsizing_value_field_update) - : G_CALLBACK (value_field_update), - value_field, G_CONNECT_SWAPPED); + g_object_set_data (G_OBJECT (value_field), "show_original", GINT_TO_POINTER (show_original)); + g_object_set_data (G_OBJECT (value_field), "ellipsize_text", GINT_TO_POINTER (ellipsize_text)); + + window->details->value_fields = g_list_prepend (window->details->value_fields, + value_field); } static void -attach_value_field (GtkTable *table, +attach_value_field (FMPropertiesWindow *window, + GtkTable *table, int row, int column, - NautilusFile *file, - const char *file_attribute_name) + const char *file_attribute_name, + const char *inconsistent_string, + gboolean show_original) { - attach_value_field_internal (table, row, column, file, file_attribute_name, FALSE); + attach_value_field_internal (window, + table, row, column, + file_attribute_name, + inconsistent_string, + show_original, + FALSE); } static void -attach_ellipsizing_value_field (GtkTable *table, +attach_ellipsizing_value_field (FMPropertiesWindow *window, + GtkTable *table, int row, int column, - NautilusFile *file, - const char *file_attribute_name) + const char *file_attribute_name, + const char *inconsistent_string, + gboolean show_original) { - attach_value_field_internal (table, row, column, file, file_attribute_name, TRUE); + attach_value_field_internal (window, + table, row, column, + file_attribute_name, + inconsistent_string, + show_original, + TRUE); } static void @@ -1173,8 +1637,8 @@ append_separator (GtkTable *table) GTK_FILL, 0, 0, 0); return separator; -} - +} + static void directory_contents_value_field_update (FMPropertiesWindow *window) { @@ -1191,7 +1655,13 @@ directory_contents_value_field_update (FMPropertiesWindow *window) g_assert (FM_IS_PROPERTIES_WINDOW (window)); - file = window->details->target_file; + /* For right now this will only work on single-file property + * dialogs */ + if (is_multi_file_window (window)) { + return ; + } + + file = get_target_file (window); g_assert (nautilus_file_is_directory (file) || nautilus_file_is_gone (file)); status = nautilus_file_get_deep_counts (file, @@ -1303,20 +1773,25 @@ attach_directory_contents_value_field (FMPropertiesWindow *window, gtk_label_set_line_wrap (value_field, TRUE); + + /* For right now, this will only be called for single-file + * property dialogs */ + g_return_val_if_fail (!is_multi_file_window (window), value_field); + /* Always recompute from scratch when the window is shown. */ - nautilus_file_recompute_deep_counts (window->details->target_file); + nautilus_file_recompute_deep_counts (get_target_file (window)); /* Fill in the initial value. */ directory_contents_value_field_update (window); /* Connect to signal to update value when file changes. */ - g_signal_connect_object (window->details->target_file, + g_signal_connect_object (get_target_file (window), "updated_deep_count_in_progress", G_CALLBACK (schedule_directory_contents_update), window, G_CONNECT_SWAPPED); return value_field; -} +} static GtkLabel * attach_title_field (GtkTable *table, @@ -1343,29 +1818,39 @@ append_title_field (GtkTable *table, const char *title, GtkLabel **label) } static guint -append_title_value_pair (GtkTable *table, +append_title_value_pair (FMPropertiesWindow *window, + GtkTable *table, const char *title, - NautilusFile *file, - const char *file_attribute_name) + const char *file_attribute_name, + const char *inconsistent_state, + gboolean show_original) { guint last_row; last_row = append_title_field (table, title, NULL); - attach_value_field (table, last_row, VALUE_COLUMN, file, file_attribute_name); + attach_value_field (window, table, last_row, VALUE_COLUMN, + file_attribute_name, + inconsistent_state, + show_original); return last_row; } static guint -append_title_and_ellipsizing_value (GtkTable *table, +append_title_and_ellipsizing_value (FMPropertiesWindow *window, + GtkTable *table, const char *title, - NautilusFile *file, - const char *file_attribute_name) + const char *file_attribute_name, + const char *inconsistent_state, + gboolean show_original) { guint last_row; last_row = append_title_field (table, title, NULL); - attach_ellipsizing_value_field (table, last_row, VALUE_COLUMN, file, file_attribute_name); + attach_ellipsizing_value_field (window, table, last_row, VALUE_COLUMN, + file_attribute_name, + inconsistent_state, + show_original); return last_row; } @@ -1396,9 +1881,17 @@ update_visibility_of_table_rows (GtkTable *table, static void update_visibility_of_item_count_fields (FMPropertiesWindow *window) { + gboolean should_show_count; + + if (is_multi_file_window (window)) { + should_show_count = FALSE; + } else { + should_show_count = nautilus_file_should_show_directory_item_count (get_target_file (window)); + } + update_visibility_of_table_rows (window->details->basic_table, - nautilus_file_should_show_directory_item_count (window->details->target_file), + should_show_count, window->details->directory_contents_row, 1, window->details->directory_contents_widgets); @@ -1530,7 +2023,8 @@ should_show_custom_icon_buttons (FMPropertiesWindow *window) * we shouldn't pretend that they work by showing them here. * When bug 5642 is fixed we can remove this case. */ - if (is_merged_trash_directory (window->details->target_file)) { + if (!is_multi_file_window (window) + && is_merged_trash_directory (get_target_file (window))) { return FALSE; } @@ -1541,10 +2035,12 @@ static gboolean should_show_file_type (FMPropertiesWindow *window) { /* The trash on the desktop is one-of-a-kind */ - if (is_merged_trash_directory (window->details->target_file)) { + if (!is_multi_file_window (window) + && is_merged_trash_directory (get_target_file (window))) { return FALSE; } + return TRUE; } @@ -1555,7 +2051,7 @@ should_show_accessed_date (FMPropertiesWindow *window) * day decide that it is useful, we should separately * consider whether it's useful for "trash:". */ - if (nautilus_file_is_directory (window->details->target_file)) { + if (file_list_all_directories (window->details->target_files)) { return FALSE; } @@ -1570,7 +2066,7 @@ should_show_mime_type (FMPropertiesWindow *window) * trash directory, but doesn't. I could trivially fix this * with a check for is_merged_trash_directory here instead. */ - if (nautilus_file_is_directory (window->details->target_file)) { + if (file_list_all_directories (window->details->target_files)) { return FALSE; } @@ -1580,7 +2076,8 @@ should_show_mime_type (FMPropertiesWindow *window) static gboolean should_show_link_target (FMPropertiesWindow *window) { - if (nautilus_file_is_symbolic_link (window->details->target_file)) { + if (!is_multi_file_window (window) + && nautilus_file_is_symbolic_link (get_target_file (window))) { return TRUE; } @@ -1590,8 +2087,8 @@ should_show_link_target (FMPropertiesWindow *window) static gboolean should_show_free_space (FMPropertiesWindow *window) { - if (nautilus_file_is_local (window->details->target_file) - && nautilus_file_is_directory (window->details->target_file)) { + if (file_list_all_local (window->details->target_files) + && file_list_all_directories (window->details->target_files)) { return TRUE; } @@ -1603,14 +2100,11 @@ create_basic_page (FMPropertiesWindow *window) { GtkTable *table; GtkWidget *container; - GtkWidget *icon_pixmap_widget, *icon_aligner, *name_field; - GtkWidget *button_box, *temp_button; - char *image_uri; - NautilusFile *target_file, *original_file; - GtkWidget *hbox, *name_label; + GtkWidget *name_field; + GtkWidget *icon_aligner; + GtkWidget *icon_pixmap_widget; - target_file = window->details->target_file; - original_file = window->details->original_file; + GtkWidget *hbox, *name_label; create_page_with_table_in_vbox (window->details->notebook, _("Basic"), @@ -1630,17 +2124,25 @@ create_basic_page (FMPropertiesWindow *window) 0, 0, 0, 0); - icon_pixmap_widget = create_image_widget_for_file (original_file); + if (is_multi_file_window (window)) { + icon_pixmap_widget = create_image_widget_for_file (NULL); + } else { + icon_pixmap_widget = create_image_widget_for_file (get_original_file (window)); + } gtk_widget_show (icon_pixmap_widget); icon_aligner = gtk_alignment_new (1, 0.5, 0, 0); gtk_widget_show (icon_aligner); - + gtk_container_add (GTK_CONTAINER (icon_aligner), icon_pixmap_widget); gtk_box_pack_start (GTK_BOX (hbox), icon_aligner, TRUE, TRUE, 0); /* Name label */ - name_label = gtk_label_new_with_mnemonic (_("_Name:")); + if (is_multi_file_window (window)) { + name_label = gtk_label_new_with_mnemonic (_("_Names:")); + } else { + name_label = gtk_label_new_with_mnemonic (_("_Name:")); + } eel_gtk_label_make_bold (GTK_LABEL (name_label)); gtk_widget_show (name_label); gtk_box_pack_end (GTK_BOX (hbox), name_label, FALSE, FALSE, 0); @@ -1658,13 +2160,8 @@ create_basic_page (FMPropertiesWindow *window) 0, 0); gtk_label_set_mnemonic_widget (GTK_LABEL (name_label), name_field); - /* Attach parameters and signal handler. */ - nautilus_file_ref (original_file); - g_object_set_data_full (G_OBJECT (name_field),"nautilus_file", - original_file, (GDestroyNotify) nautilus_file_unref); - /* Update name field initially before hooking up changed signal. */ - name_field_update_to_match_file (NAUTILUS_ENTRY (name_field)); + update_name_field (window); /* FIXME bugzilla.gnome.org 42151: * With this (and one place elsewhere in this file, not sure which is the @@ -1690,44 +2187,80 @@ create_basic_page (FMPropertiesWindow *window) nautilus_entry_select_all (NAUTILUS_ENTRY (name_field)); gtk_widget_grab_focus (GTK_WIDGET (name_field)); } - - /* React to name changes from elsewhere. */ - g_signal_connect_object (target_file, - "changed", - G_CALLBACK (name_field_update_to_match_file), - name_field, G_CONNECT_SWAPPED); - + if (should_show_file_type (window)) { - append_title_value_pair (table, _("Type:"), target_file, "type"); + append_title_value_pair (window, + table, _("Type:"), + "type", + _("--"), + FALSE); } - if (nautilus_file_is_directory (target_file)) { + + if (is_multi_file_window (window)) { + /* FIXME: append a total size field here */ +#if 0 + append_total_size_field (window, table); +#endif + } else if (nautilus_file_is_directory (get_target_file (window))) { append_directory_contents_fields (window, table); } else { - append_title_value_pair (table, _("Size:"), target_file, "size"); + append_title_value_pair (window, table, _("Size:"), + "size", + _("--"), + FALSE); } - append_title_and_ellipsizing_value (table, _("Location:"), target_file, "where"); + + append_title_and_ellipsizing_value (window, table, _("Location:"), + "where", + _("--"), + FALSE); if (should_show_free_space (window)) { - append_title_and_ellipsizing_value (table, _("Volume:"), target_file, "volume"); - append_title_value_pair (table, _("Free space:"), target_file, "free_space"); + append_title_and_ellipsizing_value (window, table, + _("Volume:"), + "volume", + _("--"), + FALSE); + append_title_value_pair (window, table, _("Free space:"), + "free_space", + _("--"), + FALSE); } + if (should_show_link_target (window)) { - append_title_and_ellipsizing_value (table, _("Link target:"), target_file, "link_target"); + append_title_and_ellipsizing_value (window, table, + _("Link target:"), + "link_target", + _("--"), + FALSE); } if (should_show_mime_type (window)) { - append_title_value_pair (table, _("MIME type:"), target_file, "mime_type"); + append_title_value_pair (window, table, _("MIME type:"), + "mime_type", + _("--"), + FALSE); } /* Blank title ensures standard row height */ append_title_field (table, "", NULL); - append_title_value_pair (table, _("Modified:"), target_file, "date_modified"); - + append_title_value_pair (window, table, _("Modified:"), + "date_modified", + _("--"), + FALSE); + if (should_show_accessed_date (window)) { - append_title_value_pair (table, _("Accessed:"), target_file, "date_accessed"); + append_title_value_pair (window, table, _("Accessed:"), + "date_accessed", + _("--"), + FALSE); } if (should_show_custom_icon_buttons (window)) { + GtkWidget *button_box; + GtkWidget *temp_button; + GList *l; + /* add command buttons for setting and clearing custom icons */ button_box = gtk_hbox_new (FALSE, 0); gtk_widget_show (button_box); @@ -1748,11 +2281,43 @@ create_basic_page (FMPropertiesWindow *window) window->details->remove_image_button = temp_button; /* de-sensitize the remove button if there isn't a custom image */ - image_uri = nautilus_file_get_metadata - (original_file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL); - gtk_widget_set_sensitive (temp_button, image_uri != NULL); - g_free (image_uri); + + gtk_widget_set_sensitive (temp_button, FALSE); + for (l = window->details->original_files; l != NULL; l = l->next) { + char *image_uri = nautilus_file_get_metadata + (NAUTILUS_FILE (l->data), + NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL); + if (image_uri) { + gtk_widget_set_sensitive (temp_button, TRUE); + } + + g_free (image_uri); + } + } +} + +static GHashTable * +get_initial_emblems (GList *files) +{ + GHashTable *ret; + GList *l; + + ret = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + (GDestroyNotify)eel_g_list_free_deep); + + for (l = files; l != NULL; l = l->next) { + NautilusFile *file; + GList *keywords; + + file = NAUTILUS_FILE (l->data); + + keywords = nautilus_file_get_keywords (file); + g_hash_table_insert (ret, file, keywords); } + + return ret; } static void @@ -1762,11 +2327,8 @@ create_emblems_page (FMPropertiesWindow *window) char *emblem_name; GdkPixbuf *pixbuf; char *label; - NautilusFile *file; GList *icons, *l; - file = window->details->original_file; - /* The emblems wrapped table */ scroller = eel_scrolled_wrap_table_new (TRUE, &emblems_table); @@ -1779,6 +2341,8 @@ create_emblems_page (FMPropertiesWindow *window) icons = nautilus_emblem_list_availible (); + window->details->initial_emblems = get_initial_emblems (window->details->original_files); + l = icons; while (l != NULL) { emblem_name = l->data; @@ -1808,133 +2372,222 @@ create_emblems_page (FMPropertiesWindow *window) g_object_unref (pixbuf); /* Attach parameters and signal handler. */ - g_object_set_data_full (G_OBJECT (button), "nautilus_property_name", + g_object_set_data_full (G_OBJECT (button), "nautilus_emblem_name", nautilus_emblem_get_keyword_from_icon_name (emblem_name), g_free); - nautilus_file_ref (file); - g_object_set_data_full (G_OBJECT (button), "nautilus_file", - file, (GDestroyNotify) nautilus_file_unref); - - g_signal_connect (button, "toggled", - G_CALLBACK (property_button_toggled), NULL); + window->details->emblem_buttons = + g_list_append (window->details->emblem_buttons, + button); - /* Set initial state of button. */ - property_button_update (GTK_TOGGLE_BUTTON (button)); - - /* Update button when file changes in future. */ - g_signal_connect_object (file, "changed", - G_CALLBACK (property_button_update), button, - G_CONNECT_SWAPPED); + g_signal_connect_object (button, "toggled", + G_CALLBACK (emblem_button_toggled), + G_OBJECT (window), + 0); gtk_container_add (GTK_CONTAINER (emblems_table), button); } eel_g_list_free_deep (icons); - gtk_widget_show_all (emblems_table); } static void -update_permissions_check_button_state (GtkWidget *check_button, NautilusFile *file) +permission_change_callback (NautilusFile *file, GnomeVFSResult result, gpointer callback_data) { - GnomeVFSFilePermissions file_permissions, permission; - gulong toggled_signal_id; + g_assert (callback_data == NULL); + + /* Report the error if it's an error. */ + fm_report_error_setting_permissions (file, result, NULL); +} - g_assert (GTK_IS_CHECK_BUTTON (check_button)); - g_assert (NAUTILUS_IS_FILE (file)); +static void +get_initial_permission_state (FMPropertiesWindow *window, + GnomeVFSFilePermissions mask, + GList **on, + GList **off) +{ + GList *l; + + *on = NULL; + *off = NULL; + + for (l = window->details->target_files; l != NULL; l = l->next) { + GnomeVFSFilePermissions permissions; + + permissions = GPOINTER_TO_INT (g_hash_table_lookup (window->details->initial_permissions, + l->data)); + + if ((permissions & mask) != 0) { + *on = g_list_prepend (*on, l->data); + } else { + *off = g_list_prepend (*off, l->data); + } + } +} - if (nautilus_file_is_gone (file)) { - return; +static void +permission_button_toggled (GtkToggleButton *button, + FMPropertiesWindow *window) +{ + GList *l; + GnomeVFSFilePermissions permission_mask; + GList *files_on; + GList *files_off; + + permission_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "permission")); + + files_on = NULL; + files_off = NULL; + if (gtk_toggle_button_get_active (button) + && !gtk_toggle_button_get_inconsistent (button)) { + /* Go to the initial state unless the initial state was + consistent */ + get_initial_permission_state (window, permission_mask, + &files_on, &files_off); + + if (!(files_on && files_off)) { + g_list_free (files_on); + g_list_free (files_off); + files_on = g_list_copy (window->details->target_files); + files_off = NULL; + } + } else if (gtk_toggle_button_get_inconsistent (button) + && !gtk_toggle_button_get_active (button)) { + files_on = g_list_copy (window->details->target_files); + files_off = NULL; + } else { + files_off = g_list_copy (window->details->target_files); + files_on = NULL; } - g_assert (nautilus_file_can_get_permissions (file)); + g_signal_handlers_block_by_func (G_OBJECT (button), + G_CALLBACK (permission_button_toggled), + window); + + gtk_toggle_button_set_active (button, files_on != NULL); + gtk_toggle_button_set_inconsistent (button, files_on && files_off); - toggled_signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (check_button), - "toggled_signal_id")); - permission = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (check_button), - "permission")); - g_assert (toggled_signal_id != 0); - g_assert (permission != 0); + g_signal_handlers_unblock_by_func (G_OBJECT (button), + G_CALLBACK (permission_button_toggled), + window); - file_permissions = nautilus_file_get_permissions (file); - gtk_widget_set_sensitive (GTK_WIDGET (check_button), - nautilus_file_can_set_permissions (file)); + for (l = files_on; l != NULL; l = l->next) { + NautilusFile *file; + + file = NAUTILUS_FILE (l->data); - /* Don't react to the "toggled" signal here to avoid recursion. */ - g_signal_handler_block (check_button, toggled_signal_id); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), - (file_permissions & permission) != 0); - g_signal_handler_unblock (check_button, toggled_signal_id); -} + if (nautilus_file_can_set_permissions (file)) { + GnomeVFSFilePermissions permissions; -static void -permission_change_callback (NautilusFile *file, GnomeVFSResult result, gpointer callback_data) -{ - g_assert (callback_data == NULL); + permissions = nautilus_file_get_permissions (file); + permissions |= permission_mask; + nautilus_file_set_permissions + (file, permissions, + permission_change_callback, + NULL); + } + + } - /* Report the error if it's an error. */ - fm_report_error_setting_permissions (file, result, NULL); + for (l = files_off; l != NULL; l = l->next) { + NautilusFile *file; + + file = NAUTILUS_FILE (l->data); + + if (nautilus_file_can_set_permissions (file)) { + GnomeVFSFilePermissions permissions; + + permissions = nautilus_file_get_permissions (file); + permissions &= ~permission_mask; + + nautilus_file_set_permissions + (file, permissions, + permission_change_callback, + NULL); + } + } + + g_list_free (files_on); + g_list_free (files_off); } static void -permissions_check_button_toggled (GtkToggleButton *toggle_button, gpointer user_data) +permission_button_update (FMPropertiesWindow *window, + GtkToggleButton *button) { - NautilusFile *file; - GnomeVFSFilePermissions permissions, permission_mask; + GList *l; + gboolean all_set; + gboolean all_unset; + gboolean all_cannot_set; + GnomeVFSFilePermissions button_permission; + + button_permission = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "permission")); + + all_set = TRUE; + all_unset = TRUE; + all_cannot_set = TRUE; + for (l = window->details->target_files; l != NULL; l = l->next) { + NautilusFile *file; + GnomeVFSFilePermissions file_permissions; + + file = NAUTILUS_FILE (l->data); - g_assert (NAUTILUS_IS_FILE (user_data)); + if (!nautilus_file_can_get_permissions (file)) { + continue; + } - file = NAUTILUS_FILE (user_data); - g_assert (nautilus_file_can_get_permissions (file)); - g_assert (nautilus_file_can_set_permissions (file)); + file_permissions = nautilus_file_get_permissions (file); - permission_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (toggle_button), - "permission")); + if ((file_permissions & button_permission) != 0) { + all_unset = FALSE; + } else { + all_set = FALSE; + } - /* Modify the file's permissions according to the state of this check button. */ - permissions = nautilus_file_get_permissions (file); - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle_button))) { - /* Turn this specific permission bit on. */ - permissions |= permission_mask; - } else { - /* Turn this specific permission bit off. */ - permissions &= ~permission_mask; + if (nautilus_file_can_set_permissions (file)) { + all_cannot_set = FALSE; + } } + + g_signal_handlers_block_by_func (G_OBJECT (button), + G_CALLBACK (permission_button_toggled), + window); + + gtk_toggle_button_set_active (button, !all_unset); + gtk_toggle_button_set_inconsistent (button, !all_unset && !all_set); + gtk_widget_set_sensitive (GTK_WIDGET (button), !all_cannot_set); - /* Try to change file permissions. If this fails, complain to user. */ - nautilus_file_set_permissions - (file, permissions, - permission_change_callback, NULL); + g_signal_handlers_unblock_by_func (G_OBJECT (button), + G_CALLBACK (permission_button_toggled), + window); } + static void -set_up_permissions_checkbox (GtkWidget *check_button, - NautilusFile *file, +set_up_permissions_checkbox (FMPropertiesWindow *window, + GtkWidget *check_button, GnomeVFSFilePermissions permission) { - gulong toggled_signal_id; - - toggled_signal_id = g_signal_connect (check_button, "toggled", - G_CALLBACK (permissions_check_button_toggled), - file); - /* Load up the check_button with data we'll need when updating its state. */ - g_object_set_data (G_OBJECT (check_button), "toggled_signal_id", - GUINT_TO_POINTER (toggled_signal_id)); g_object_set_data (G_OBJECT (check_button), "permission", GINT_TO_POINTER (permission)); + g_object_set_data (G_OBJECT (check_button), "properties_window", + window); - /* Set initial state. */ - update_permissions_check_button_state (check_button, file); + window->details->permission_buttons = + g_list_prepend (window->details->permission_buttons, + check_button); - /* Update state later if file changes. */ - g_signal_connect_object (file, "changed", - G_CALLBACK (update_permissions_check_button_state), - check_button, G_CONNECT_SWAPPED); + g_signal_connect_object (check_button, "toggled", + G_CALLBACK (permission_button_toggled), + window, + 0); } static void -add_permissions_checkbox (GtkTable *table, - NautilusFile *file, +add_permissions_checkbox (FMPropertiesWindow *window, + GtkTable *table, int row, int column, GnomeVFSFilePermissions permission_to_check) { @@ -1957,7 +2610,9 @@ add_permissions_checkbox (GtkTable *table, GTK_FILL, 0, 0, 0); - set_up_permissions_checkbox (check_button, file, permission_to_check); + set_up_permissions_checkbox (window, + check_button, + permission_to_check); } static GtkWidget * @@ -1980,7 +2635,9 @@ append_special_execution_checkbox (FMPropertiesWindow *window, GTK_FILL, 0, 0, 0); - set_up_permissions_checkbox (check_button, window->details->target_file, permission_to_check); + set_up_permissions_checkbox (window, + check_button, + permission_to_check); ++window->details->num_special_flags_rows; return check_button; @@ -2014,9 +2671,10 @@ update_visibility_of_special_flags_widgets_wrapper (gpointer callback_data) } static void -append_special_execution_flags (FMPropertiesWindow *window, - GtkTable *table) +append_special_execution_flags (FMPropertiesWindow *window, GtkTable *table) { + + remember_special_flags_widget (window, append_special_execution_checkbox (window, table, _("Set _user ID"), GNOME_VFS_PERM_SUID)); @@ -2042,24 +2700,84 @@ append_special_execution_flags (FMPropertiesWindow *window, } +static gboolean +all_can_get_permissions (GList *file_list) +{ + GList *l; + for (l = file_list; l != NULL; l = l->next) { + NautilusFile *file; + + file = NAUTILUS_FILE (l->data); + + if (!nautilus_file_can_get_permissions (file)) { + return FALSE; + } + } + + return TRUE; +} + +static gboolean +all_can_set_permissions (GList *file_list) +{ + GList *l; + for (l = file_list; l != NULL; l = l->next) { + NautilusFile *file; + + file = NAUTILUS_FILE (l->data); + + if (!nautilus_file_can_set_permissions (file)) { + return FALSE; + } + } + + return TRUE; +} + +static GHashTable * +get_initial_permissions (GList *file_list) +{ + GHashTable *ret; + GList *l; + + ret = g_hash_table_new (g_direct_hash, + g_direct_equal); + + for (l = file_list; l != NULL; l = l->next) { + GnomeVFSFilePermissions permissions; + NautilusFile *file; + + file = NAUTILUS_FILE (l->data); + + permissions = nautilus_file_get_permissions (file); + g_hash_table_insert (ret, file, + GINT_TO_POINTER (permissions)); + } + + return ret; +} + static void create_permissions_page (FMPropertiesWindow *window) { GtkWidget *vbox; GtkTable *page_table, *check_button_table; char *file_name, *prompt_text; - NautilusFile *file; guint last_row; guint checkbox_titles_row; GtkLabel *group_label; GtkOptionMenu *group_menu; + GList *file_list; - file = window->details->target_file; + vbox = create_page_with_vbox (window->details->notebook, + _("Permissions")); - vbox = create_page_with_vbox (window->details->notebook, _("Permissions")); + file_list = window->details->original_files; - if (nautilus_file_can_get_permissions (file)) { - if (!nautilus_file_can_set_permissions (file)) { + window->details->initial_permissions = get_initial_permissions (window->details->target_files); + + if (all_can_get_permissions (file_list)) { + if (!all_can_set_permissions (file_list)) { add_prompt_and_separator ( GTK_VBOX (vbox), _("You are not the owner, so you can't change these permissions.")); @@ -2076,21 +2794,27 @@ create_permissions_page (FMPropertiesWindow *window) TRUE, TRUE, 0); attach_title_field (page_table, last_row, _("File owner:")); - if (nautilus_file_can_set_owner (file)) { + + + if (!is_multi_file_window (window) && nautilus_file_can_set_owner (get_target_file (window))) { /* Option menu in this case. */ - attach_owner_menu (page_table, last_row, file); + attach_owner_menu (page_table, last_row, get_target_file (window)); } else { /* Static text in this case. */ - attach_value_field (page_table, last_row, VALUE_COLUMN, file, "owner"); + attach_value_field (window, + page_table, last_row, VALUE_COLUMN, + "owner", + _("--"), + FALSE); } - if (nautilus_file_can_set_group (file)) { + if (!is_multi_file_window (window) && nautilus_file_can_set_group (get_target_file (window))) { last_row = append_title_field (page_table, _("_File group:"), &group_label); /* Option menu in this case. */ group_menu = attach_group_menu (page_table, last_row, - file); + get_target_file (window)); gtk_label_set_mnemonic_widget (GTK_LABEL (group_label), GTK_WIDGET (group_menu)); } else { @@ -2098,11 +2822,15 @@ create_permissions_page (FMPropertiesWindow *window) _("File group:"), NULL); /* Static text in this case. */ - attach_value_field (page_table, last_row, VALUE_COLUMN, file, "group"); + attach_value_field (window, page_table, last_row, + VALUE_COLUMN, + "group", + _("--"), + FALSE); } append_separator (page_table); - + checkbox_titles_row = append_title_field (page_table, _("Owner:"), NULL); append_title_field (page_table, _("Group:"), NULL); append_title_field (page_table, _("Others:"), NULL); @@ -2119,47 +2847,56 @@ create_permissions_page (FMPropertiesWindow *window) 0, 0, 0, 0); - add_permissions_checkbox (check_button_table, file, + add_permissions_checkbox (window, + check_button_table, PERMISSIONS_CHECKBOXES_OWNER_ROW, PERMISSIONS_CHECKBOXES_READ_COLUMN, GNOME_VFS_PERM_USER_READ); - add_permissions_checkbox (check_button_table, file, + add_permissions_checkbox (window, + check_button_table, PERMISSIONS_CHECKBOXES_OWNER_ROW, PERMISSIONS_CHECKBOXES_WRITE_COLUMN, GNOME_VFS_PERM_USER_WRITE); - add_permissions_checkbox (check_button_table, file, + add_permissions_checkbox (window, + check_button_table, PERMISSIONS_CHECKBOXES_OWNER_ROW, PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN, GNOME_VFS_PERM_USER_EXEC); - add_permissions_checkbox (check_button_table, file, + add_permissions_checkbox (window, + check_button_table, PERMISSIONS_CHECKBOXES_GROUP_ROW, PERMISSIONS_CHECKBOXES_READ_COLUMN, GNOME_VFS_PERM_GROUP_READ); - add_permissions_checkbox (check_button_table, file, + add_permissions_checkbox (window, + check_button_table, PERMISSIONS_CHECKBOXES_GROUP_ROW, PERMISSIONS_CHECKBOXES_WRITE_COLUMN, GNOME_VFS_PERM_GROUP_WRITE); - add_permissions_checkbox (check_button_table, file, + add_permissions_checkbox (window, + check_button_table, PERMISSIONS_CHECKBOXES_GROUP_ROW, PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN, GNOME_VFS_PERM_GROUP_EXEC); - add_permissions_checkbox (check_button_table, file, + add_permissions_checkbox (window, + check_button_table, PERMISSIONS_CHECKBOXES_OTHERS_ROW, PERMISSIONS_CHECKBOXES_READ_COLUMN, GNOME_VFS_PERM_OTHER_READ); - add_permissions_checkbox (check_button_table, file, + add_permissions_checkbox (window, + check_button_table, PERMISSIONS_CHECKBOXES_OTHERS_ROW, PERMISSIONS_CHECKBOXES_WRITE_COLUMN, GNOME_VFS_PERM_OTHER_WRITE); - add_permissions_checkbox (check_button_table, file, + add_permissions_checkbox (window, + check_button_table, PERMISSIONS_CHECKBOXES_OTHERS_ROW, PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN, GNOME_VFS_PERM_OTHER_EXEC); @@ -2167,15 +2904,28 @@ create_permissions_page (FMPropertiesWindow *window) append_separator (page_table); append_special_execution_flags (window, page_table); - - append_title_value_pair (page_table, _("Text view:"), file, "permissions"); - append_title_value_pair (page_table, _("Number view:"), file, "octal_permissions"); - append_title_value_pair (page_table, _("Last changed:"), file, "date_permissions"); + append_title_value_pair + (window, page_table, _("Text view:"), + "permissions", _("--"), + FALSE); + append_title_value_pair + (window, page_table, _("Number view:"), + "octal_permissions", _("--"), + FALSE); + append_title_value_pair + (window, page_table, _("Last changed:"), + "date_permissions", _("--"), + FALSE); } else { - file_name = nautilus_file_get_display_name (file); - prompt_text = g_strdup_printf (_("The permissions of \"%s\" could not be determined."), file_name); - g_free (file_name); + if (!is_multi_file_window (window)) { + file_name = nautilus_file_get_display_name (get_target_file (window)); + prompt_text = g_strdup_printf (_("The permissions of \"%s\" could not be determined."), file_name); + g_free (file_name); + } else { + prompt_text = g_strdup (_("The permissions of the selected file could not be determined.")); + } + add_prompt (GTK_VBOX (vbox), prompt_text, TRUE); g_free (prompt_text); } @@ -2205,6 +2955,28 @@ bonobo_page_error_message (const char *view_name, return hbox; } +static CORBA_sequence_CORBA_string * +get_uri_list (GList *file_list) +{ + CORBA_sequence_CORBA_string *uri_list; + GList *l; + int i; + + uri_list = CORBA_sequence_CORBA_string__alloc (); + uri_list->_maximum = g_list_length (file_list); + uri_list->_length = uri_list->_maximum; + uri_list->_buffer = CORBA_sequence_CORBA_string_allocbuf (uri_list->_length); + for (i = 0, l = file_list; l != NULL; i++, l = l->next) { + char *uri; + + uri = nautilus_file_get_uri (NAUTILUS_FILE (l->data)); + uri_list->_buffer[i] = CORBA_string_dup (uri); + g_free (uri); + } + + return uri_list; +} + static void bonobo_page_activate_callback (CORBA_Object obj, const char *error_reason, @@ -2226,23 +2998,54 @@ bonobo_page_activate_callback (CORBA_Object obj, if (obj != CORBA_OBJECT_NIL) { Bonobo_Control control; Bonobo_PropertyBag pb; - BonoboArg *arg; - char *uri; - - uri = nautilus_file_get_uri (window->details->target_file); + GList *keys; control = Bonobo_Unknown_queryInterface (obj, "IDL:Bonobo/Control:1.0", &ev); - pb = Bonobo_Control_getProperties (control, &ev); if (!BONOBO_EX (&ev)) { - arg = bonobo_arg_new (BONOBO_ARG_STRING); - BONOBO_ARG_SET_STRING (arg, uri); + gboolean new_property; + + keys = bonobo_pbclient_get_keys (pb, NULL); + new_property = FALSE; + if (g_list_find_custom (keys, "uris", (GCompareFunc)strcmp)) { + new_property = TRUE; + } + bonobo_pbclient_free_keys (keys); + + if (new_property) { + /* Set the 'uris' property. */ + CORBA_sequence_CORBA_string *uri_list; + BonoboArg *arg; + + uri_list = get_uri_list (window->details->target_files); + arg = bonobo_arg_new (TC_CORBA_sequence_CORBA_string); + arg->_value = uri_list; + bonobo_pbclient_set_value_async (pb, "uris", arg, &ev); + bonobo_arg_release (arg); + } else { + /* Set the 'URI' property. */ + BonoboArg *arg; + char *uri; + + if (is_multi_file_window (window)) { + g_warning ("Multifile property page does not support the 'uris' property"); + bonobo_object_release_unref (pb, NULL); + bonobo_object_release_unref (control, NULL); + return; + } + + uri = nautilus_file_get_uri (get_target_file (window)); + + arg = bonobo_arg_new (BONOBO_ARG_STRING); + BONOBO_ARG_SET_STRING (arg, uri); + bonobo_pbclient_set_value_async (pb, "URI", arg, &ev); + bonobo_arg_release (arg); + g_free (uri); + } - bonobo_pbclient_set_value_async (pb, "URI", arg, &ev); - bonobo_arg_release (arg); bonobo_object_release_unref (pb, NULL); if (!BONOBO_EX (&ev)) { @@ -2251,8 +3054,6 @@ bonobo_page_activate_callback (CORBA_Object obj, bonobo_object_release_unref (control, &ev); } } - - g_free (uri); } if (widget == NULL) { @@ -2267,16 +3068,42 @@ bonobo_page_activate_callback (CORBA_Object obj, g_free (data); } +static gboolean +can_handle_multiple_files (Bonobo_ServerInfo *info) +{ + Bonobo_ActivationProperty *prop; + + prop = bonobo_server_info_prop_find (info, "nautilus:can_handle_multiple_files"); + + return prop && prop->v._u.value_boolean; +} + static void append_bonobo_pages (FMPropertiesWindow *window) { - GList *components, *l; + GList *all_components, *l; + GList *components; CORBA_Environment ev; /* find all the property pages for this file */ - components = nautilus_mime_get_property_components_for_file - (window->details->target_file); + all_components = nautilus_mime_get_property_components_for_files + (window->details->target_files); + /* filter out property pages that don't support multiple files */ + if (is_multi_file_window (window)) { + components = NULL; + for (l = all_components; l != NULL; l = l->next) { + if (can_handle_multiple_files (l->data)) { + components = g_list_prepend (components, + l->data); + } + } + components = g_list_reverse (components); + g_list_free (all_components); + } else { + components = all_components; + } + CORBA_exception_init (&ev); l = components; @@ -2317,7 +3144,8 @@ should_show_emblems (FMPropertiesWindow *window) * we shouldn't pretend that they work by showing them here. * When bug 5643 is fixed we can remove this case. */ - if (is_merged_trash_directory (window->details->target_file)) { + if (!is_multi_file_window (window) + && is_merged_trash_directory (get_target_file (window))) { return FALSE; } @@ -2330,24 +3158,62 @@ should_show_permissions (FMPropertiesWindow *window) /* Don't show permissions for the Trash since it's not * really a file system object. */ - if (is_merged_trash_directory (window->details->target_file)) { + if (!is_multi_file_window (window) + && is_merged_trash_directory (get_target_file (window))) { return FALSE; } return TRUE; } +static char * +get_pending_key (GList *file_list) +{ + GList *l; + GList *uris; + GString *key; + char *ret; + + uris = NULL; + for (l = file_list; l != NULL; l = l->next) { + uris = g_list_prepend (uris, nautilus_file_get_uri (NAUTILUS_FILE (l->data))); + } + uris = g_list_sort (uris, (GCompareFunc)strcmp); + + key = g_string_new (""); + for (l = uris; l != NULL; l = l->next) { + g_string_append (key, l->data); + g_string_append (key, ";"); + } + + eel_g_list_free_deep (uris); + + ret = key->str; + g_string_free (key, FALSE); + + return ret; +} + static StartupData * -startup_data_new (NautilusFile *original_file, - NautilusFile *target_file, +startup_data_new (GList *original_files, + GList *target_files, + const char *pending_key, FMDirectoryView *directory_view) { StartupData *data; + GList *l; data = g_new0 (StartupData, 1); - data->original_file = nautilus_file_ref (original_file); - data->target_file = nautilus_file_ref (target_file); + data->original_files = nautilus_file_list_copy (original_files); + data->target_files = nautilus_file_list_copy (target_files); data->directory_view = directory_view; + data->pending_key = g_strdup (pending_key); + data->pending_files = g_hash_table_new (g_direct_hash, + g_direct_equal); + + for (l = data->target_files; l != NULL; l = l->next) { + g_hash_table_insert (data->pending_files, l->data, l->data); + } return data; } @@ -2355,8 +3221,10 @@ startup_data_new (NautilusFile *original_file, static void startup_data_free (StartupData *data) { - nautilus_file_unref (data->original_file); - nautilus_file_unref (data->target_file); + nautilus_file_list_free (data->original_files); + nautilus_file_list_free (data->target_files); + g_hash_table_destroy (data->pending_files); + g_free (data->pending_key); g_free (data); } @@ -2384,45 +3252,74 @@ static FMPropertiesWindow * create_properties_window (StartupData *startup_data) { FMPropertiesWindow *window; - NautilusFileAttributes attributes; GtkWidget *vbox; GtkWidget *hbox; GtkWidget *button; + GList *l; window = FM_PROPERTIES_WINDOW (gtk_widget_new (fm_properties_window_get_type (), NULL)); - window->details->original_file = nautilus_file_ref (startup_data->original_file); - window->details->target_file = nautilus_file_ref (startup_data->target_file); + window->details->original_files = nautilus_file_list_copy (startup_data->original_files); + window->details->target_files = nautilus_file_list_copy (startup_data->target_files); + gtk_window_set_wmclass (GTK_WINDOW (window), "file_properties", "Nautilus"); gtk_window_set_resizable (GTK_WINDOW (window), FALSE); gtk_window_set_screen (GTK_WINDOW (window), gtk_widget_get_screen (GTK_WIDGET (startup_data->directory_view))); /* Set initial window title */ - update_properties_window_title (GTK_WINDOW (window), window->details->target_file); + update_properties_window_title (window); /* Start monitoring the file attributes we display. Note that some * of the attributes are for the original file, and some for the - * target file. + * target files. */ - attributes = nautilus_icon_factory_get_required_file_attributes (); - attributes |= NAUTILUS_FILE_ATTRIBUTE_DISPLAY_NAME; - nautilus_file_monitor_add (window->details->original_file, window, attributes); - attributes = 0; - if (nautilus_file_is_directory (window->details->target_file)) { - attributes |= NAUTILUS_FILE_ATTRIBUTE_DEEP_COUNTS; + for (l = window->details->original_files; l != NULL; l = l->next) { + NautilusFile *file; + NautilusFileAttributes attributes; + + file = NAUTILUS_FILE (l->data); + + attributes = nautilus_icon_factory_get_required_file_attributes (); + attributes |= NAUTILUS_FILE_ATTRIBUTE_DISPLAY_NAME; + + nautilus_file_monitor_add (NAUTILUS_FILE (l->data), + window, + attributes); } - attributes |= NAUTILUS_FILE_ATTRIBUTE_METADATA; - nautilus_file_monitor_add (window->details->target_file, window, attributes); + for (l = window->details->target_files; l != NULL; l = l->next) { + NautilusFile *file; + NautilusFileAttributes attributes; + + file = NAUTILUS_FILE (l->data); + + attributes = 0; + if (nautilus_file_is_directory (file)) { + attributes |= NAUTILUS_FILE_ATTRIBUTE_DEEP_COUNTS; + } + + attributes |= NAUTILUS_FILE_ATTRIBUTE_METADATA; + nautilus_file_monitor_add (file, window, attributes); + } + + for (l = window->details->target_files; l != NULL; l = l->next) { + g_signal_connect_object (NAUTILUS_FILE (l->data), + "changed", + G_CALLBACK (properties_window_update), + G_OBJECT (window), + G_CONNECT_SWAPPED); + } - /* React to future property changes and file deletions. */ - window->details->file_changed_handler_id = - g_signal_connect_object (window->details->target_file, "changed", - G_CALLBACK (properties_window_file_changed_callback), - window, G_CONNECT_SWAPPED); + for (l = window->details->original_files; l != NULL; l = l->next) { + g_signal_connect_object (NAUTILUS_FILE (l->data), + "changed", + G_CALLBACK (properties_window_update), + G_OBJECT (window), + G_CONNECT_SWAPPED); + } /* Create box for notebook and button box. */ vbox = gtk_vbox_new (FALSE, 0); @@ -2473,67 +3370,70 @@ create_properties_window (StartupData *startup_data) G_CALLBACK (gtk_widget_destroy), window); + /* Update from initial state */ + properties_window_update (window, NULL); + return window; } -static NautilusFile * -get_target_file (NautilusFile *file) +static GList * +get_target_file_list (GList *original_files) { - NautilusFile *target_file; - char *uri_to_display; - NautilusDesktopLink *link; - - target_file = NULL; - if (NAUTILUS_IS_DESKTOP_ICON_FILE (file)) { - link = nautilus_desktop_icon_file_get_link (NAUTILUS_DESKTOP_ICON_FILE (file)); + GList *ret; + GList *l; + + ret = NULL; + + for (l = original_files; l != NULL; l = l->next) { + NautilusFile *target; - /* map to linked URI for these types of links */ - uri_to_display = nautilus_desktop_link_get_activation_uri (link); - if (uri_to_display) { - target_file = nautilus_file_get (uri_to_display); - g_free (uri_to_display); - } + target = get_target_file_for_original_file (NAUTILUS_FILE (l->data)); - g_object_unref (link); + ret = g_list_prepend (ret, target); } - if (target_file != NULL) { - return target_file; - } + ret = g_list_reverse (ret); - /* Ref passed-in file here since we've decided to use it. */ - nautilus_file_ref (file); - return file; + return ret; } static void -create_properties_window_callback (NautilusFile *file, gpointer callback_data) +add_window (FMPropertiesWindow *window) { - FMPropertiesWindow *new_window; - StartupData *startup_data; - - startup_data = (StartupData *)callback_data; + if (!is_multi_file_window (window)) { + g_hash_table_insert (windows, + get_original_file (window), + window); + g_object_set_data (G_OBJECT (window), "window_key", + get_original_file (window)); + } +} - new_window = create_properties_window (startup_data); +static void +remove_window (FMPropertiesWindow *window) +{ + gpointer key; - g_hash_table_insert (windows, startup_data->original_file, new_window); + key = g_object_get_data (G_OBJECT (window), "window_key"); + if (key) { + g_hash_table_remove (windows, key); + } +} - remove_pending_file (startup_data, FALSE, TRUE, TRUE); +static GtkWindow * +get_existing_window (GList *file_list) +{ + if (!file_list->next) { + return g_hash_table_lookup (windows, file_list->data); + } -/* FIXME bugzilla.gnome.org 42151: - * See comment elsewhere in this file about bug 2151. - */ -#ifdef UNDO_ENABLED - nautilus_undo_share_undo_manager (GTK_OBJECT (new_window), - GTK_OBJECT (callback_data)); -#endif - gtk_window_present (GTK_WINDOW (new_window)); + return NULL; } static void cancel_create_properties_window_callback (gpointer callback_data) { - remove_pending_file ((StartupData *)callback_data, TRUE, FALSE, TRUE); + remove_pending ((StartupData *)callback_data, TRUE, FALSE, TRUE); } static void @@ -2541,18 +3441,31 @@ directory_view_destroyed_callback (FMDirectoryView *view, gpointer callback_data { g_assert (view == ((StartupData *)callback_data)->directory_view); - remove_pending_file ((StartupData *)callback_data, TRUE, TRUE, FALSE); + remove_pending ((StartupData *)callback_data, TRUE, TRUE, FALSE); +} + +static void +cancel_call_when_ready_callback (gpointer key, + gpointer value, + gpointer user_data) +{ + nautilus_file_cancel_call_when_ready + (NAUTILUS_FILE (key), + is_directory_ready_callback, + user_data); } static void -remove_pending_file (StartupData *startup_data, - gboolean cancel_call_when_ready, - gboolean cancel_timed_wait, - gboolean cancel_destroy_handler) +remove_pending (StartupData *startup_data, + gboolean cancel_call_when_ready, + gboolean cancel_timed_wait, + gboolean cancel_destroy_handler) { if (cancel_call_when_ready) { - nautilus_file_cancel_call_when_ready - (startup_data->target_file, create_properties_window_callback, startup_data); + g_hash_table_foreach (startup_data->pending_files, + cancel_call_when_ready_callback, + startup_data); + } if (cancel_timed_wait) { eel_timed_wait_stop @@ -2563,19 +3476,55 @@ remove_pending_file (StartupData *startup_data, G_CALLBACK (directory_view_destroyed_callback), startup_data); } - g_hash_table_remove (pending_files, startup_data->original_file); + + g_hash_table_remove (pending_lists, startup_data->pending_key); + startup_data_free (startup_data); } +static void +is_directory_ready_callback (NautilusFile *file, + gpointer data) +{ + StartupData *startup_data; + + startup_data = data; + + g_hash_table_remove (startup_data->pending_files, file); + + if (g_hash_table_size (startup_data->pending_files) == 0) { + FMPropertiesWindow *new_window; + + new_window = create_properties_window (startup_data); + + add_window (new_window); + + remove_pending (startup_data, FALSE, TRUE, TRUE); + +/* FIXME bugzilla.gnome.org 42151: + * See comment elsewhere in this file about bug 2151. + */ +#ifdef UNDO_ENABLED + nautilus_undo_share_undo_manager (GTK_OBJECT (new_window), + GTK_OBJECT (callback_data)); +#endif + gtk_window_present (GTK_WINDOW (new_window)); + } +} + + void -fm_properties_window_present (NautilusFile *original_file, FMDirectoryView *directory_view) +fm_properties_window_present (GList *original_files, + FMDirectoryView *directory_view) { - GtkWindow *existing_window; + GList *l; GtkWidget *parent_window; - NautilusFile *target_file; StartupData *startup_data; + GList *target_files; + GtkWindow *existing_window; + char *pending_key; - g_return_if_fail (NAUTILUS_IS_FILE (original_file)); + g_return_if_fail (original_files != NULL); g_return_if_fail (FM_IS_DIRECTORY_VIEW (directory_view)); /* Create the hash tables first time through. */ @@ -2584,13 +3533,13 @@ fm_properties_window_present (NautilusFile *original_file, FMDirectoryView *dire (NULL, NULL, "property windows"); } - if (pending_files == NULL) { - pending_files = eel_g_hash_table_new_free_at_exit - (NULL, NULL, "pending property window files"); + if (pending_lists == NULL) { + pending_lists = eel_g_hash_table_new_free_at_exit + (g_str_hash, g_str_equal, "pending property window files"); } /* Look to see if there's already a window for this file. */ - existing_window = g_hash_table_lookup (windows, original_file); + existing_window = get_existing_window (original_files); if (existing_window != NULL) { gtk_window_set_screen (existing_window, gtk_widget_get_screen (GTK_WIDGET (directory_view))); @@ -2598,57 +3547,71 @@ fm_properties_window_present (NautilusFile *original_file, FMDirectoryView *dire return; } + + pending_key = get_pending_key (original_files); + /* Look to see if we're already waiting for a window for this file. */ - if (g_hash_table_lookup (pending_files, original_file) != NULL) { + if (g_hash_table_lookup (pending_lists, pending_key) != NULL) { return; } - target_file = get_target_file (original_file); - startup_data = startup_data_new (original_file, target_file, directory_view); - nautilus_file_unref (target_file); + target_files = get_target_file_list (original_files); + + startup_data = startup_data_new (original_files, + target_files, + pending_key, + directory_view); + + nautilus_file_list_free (target_files); /* Wait until we can tell whether it's a directory before showing, since * some one-time layout decisions depend on that info. */ - g_hash_table_insert (pending_files, target_file, target_file); + g_hash_table_insert (pending_lists, pending_key, pending_key); g_signal_connect (directory_view, "destroy", G_CALLBACK (directory_view_destroyed_callback), startup_data); parent_window = gtk_widget_get_ancestor (GTK_WIDGET (directory_view), GTK_TYPE_WINDOW); + eel_timed_wait_start (cancel_create_properties_window_callback, startup_data, _("Cancel Showing Properties Window?"), _("Creating Properties window"), parent_window == NULL ? NULL : GTK_WINDOW (parent_window)); - nautilus_file_call_when_ready - (target_file, NAUTILUS_FILE_ATTRIBUTE_IS_DIRECTORY, - create_properties_window_callback, startup_data); + + + for (l = startup_data->target_files; l != NULL; l = l->next) { + nautilus_file_call_when_ready + (NAUTILUS_FILE (l->data), + NAUTILUS_FILE_ATTRIBUTE_IS_DIRECTORY, + is_directory_ready_callback, + startup_data); + } } static void real_destroy (GtkObject *object) { FMPropertiesWindow *window; + GList *l; window = FM_PROPERTIES_WINDOW (object); - g_hash_table_remove (windows, window->details->original_file); - - if (window->details->original_file != NULL) { - nautilus_file_monitor_remove (window->details->original_file, window); - nautilus_file_unref (window->details->original_file); - window->details->original_file = NULL; - } + remove_window (window); - if (window->details->target_file != NULL) { - g_signal_handler_disconnect (window->details->target_file, - window->details->file_changed_handler_id); - nautilus_file_monitor_remove (window->details->target_file, window); - nautilus_file_unref (window->details->target_file); - window->details->target_file = NULL; + for (l = window->details->original_files; l != NULL; l = l->next) { + nautilus_file_monitor_remove (NAUTILUS_FILE (l->data), window); + } + nautilus_file_list_free (window->details->original_files); + window->details->original_files = NULL; + + for (l = window->details->target_files; l != NULL; l = l->next) { + nautilus_file_monitor_remove (NAUTILUS_FILE (l->data), window); } + nautilus_file_list_free (window->details->target_files); + window->details->target_files = NULL; window->details->name_field = NULL; @@ -2658,11 +3621,29 @@ real_destroy (GtkObject *object) g_list_free (window->details->special_flags_widgets); window->details->special_flags_widgets = NULL; + g_list_free (window->details->emblem_buttons); + window->details->emblem_buttons = NULL; + + if (window->details->initial_emblems) { + g_hash_table_destroy (window->details->initial_emblems); + window->details->initial_emblems = NULL; + } + + g_list_free (window->details->permission_buttons); + window->details->permission_buttons = NULL; + + if (window->details->initial_permissions) { + g_hash_table_destroy (window->details->initial_permissions); + window->details->initial_permissions = NULL; + } + + g_list_free (window->details->value_fields); + window->details->value_fields = NULL; + if (window->details->update_directory_contents_timeout_id != 0) { g_source_remove (window->details->update_directory_contents_timeout_id); window->details->update_directory_contents_timeout_id = 0; } - GTK_OBJECT_CLASS (parent_class)->destroy (object); } @@ -2673,6 +3654,8 @@ real_finalize (GObject *object) window = FM_PROPERTIES_WINDOW (object); + eel_g_list_free_deep (window->details->mime_list); + g_free (window->details->pending_name); g_free (window->details); @@ -2690,19 +3673,23 @@ set_icon_callback (const char* icon_path, FMPropertiesWindow *properties_window) g_return_if_fail (FM_IS_PROPERTIES_WINDOW (properties_window)); if (icon_path != NULL) { - file = properties_window->details->original_file; + GList *l; + icon_uri = gnome_vfs_get_uri_from_local_path (icon_path); - nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL, icon_uri); - g_free (icon_uri); - nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_ICON_SCALE, NULL, NULL); + for (l = properties_window->details->original_files; l != NULL; l = l->next) { + file = NAUTILUS_FILE (l->data); + + nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL, icon_uri); + nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_ICON_SCALE, NULL, NULL); + } + g_free (icon_uri); + /* re-enable the property window's clear image button */ gtk_widget_set_sensitive (properties_window->details->remove_image_button, TRUE); - } else { } } - /* handle the "select icon" button */ static void select_image_button_callback (GtkWidget *widget, FMPropertiesWindow *properties_window) @@ -2715,22 +3702,15 @@ select_image_button_callback (GtkWidget *widget, FMPropertiesWindow *properties_ NULL, GTK_WINDOW (properties_window), (EelIconSelectionFunction) set_icon_callback, - properties_window); + properties_window); } static void remove_image_button_callback (GtkWidget *widget, FMPropertiesWindow *properties_window) { - g_assert (FM_IS_PROPERTIES_WINDOW (properties_window)); + g_return_if_fail (FM_IS_PROPERTIES_WINDOW (properties_window)); - nautilus_file_set_metadata (properties_window->details->original_file, - NAUTILUS_METADATA_KEY_ICON_SCALE, - NULL, NULL); - nautilus_file_set_metadata (properties_window->details->original_file, - NAUTILUS_METADATA_KEY_CUSTOM_ICON, - NULL, NULL); - - gtk_widget_set_sensitive (widget, FALSE); + reset_icon (properties_window); } static void |