/* fm-properties-window.c - window that lets user modify file properties * * Copyright (C) 2000 Eazel, Inc. * * The Gnome Library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * The Gnome Library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with the Gnome Library; see the file COPYING.LIB. If not, * see . * * Authors: Darin Adler */ #include "nautilus-properties-window.h" #include #include #include #include #include #include #include #include #include #include #include #define GNOME_DESKTOP_USE_UNSTABLE_API #include #include "nautilus-enums.h" #include "nautilus-error-reporting.h" #include "nautilus-file-operations.h" #include "nautilus-file-utilities.h" #include "nautilus-global-preferences.h" #include "nautilus-icon-info.h" #include "nautilus-metadata.h" #include "nautilus-mime-actions.h" #include "nautilus-module.h" #include "nautilus-signaller.h" #include "nautilus-ui-utilities.h" #include "nautilus-signaller.h" #define PREVIEW_IMAGE_WIDTH 96 #define ROW_PAD 6 static GHashTable *windows; static GHashTable *pending_lists; typedef struct { NautilusFile *file; char *owner; GtkWindow *window; unsigned int timeout; gboolean cancelled; } OwnerChange; typedef struct { NautilusFile *file; char *group; GtkWindow *window; unsigned int timeout; gboolean cancelled; } GroupChange; struct _NautilusPropertiesWindow { GtkWindow parent_instance; GList *original_files; GList *target_files; GtkNotebook *notebook; /* Basic tab widgets */ GtkStack *icon_stack; GtkWidget *icon_image; GtkWidget *icon_button; GtkWidget *icon_button_image; GtkWidget *icon_chooser; GtkGrid *basic_grid; GtkLabel *name_title_label; GtkStack *name_stack; GtkWidget *name_field; char *pending_name; guint select_idle_id; GtkWidget *type_title_label; GtkWidget *type_value_label; GtkWidget *link_target_title_label; GtkWidget *link_target_value_label; GtkWidget *contents_title_label; GtkWidget *contents_value_label; GtkWidget *contents_spinner; guint update_directory_contents_timeout_id; guint update_files_timeout_id; GtkWidget *size_title_label; GtkWidget *size_value_label; GtkWidget *parent_folder_title_label; GtkWidget *parent_folder_value_label; GtkWidget *original_folder_title_label; GtkWidget *original_folder_value_label; GtkWidget *volume_title_label; GtkWidget *volume_value_label; GtkWidget *trashed_on_title_label; GtkWidget *trashed_on_value_label; GtkWidget *spacer_2; GtkWidget *accessed_title_label; GtkWidget *accessed_value_label; GtkWidget *modified_title_label; GtkWidget *modified_value_label; GtkWidget *spacer_3; GtkWidget *free_space_title_label; GtkWidget *free_space_value_label; GtkWidget *volume_widget_box; GtkWidget *open_in_disks_button; GtkWidget *pie_chart; GtkWidget *used_color; GtkWidget *used_value; GtkWidget *free_color; GtkWidget *free_value; GtkWidget *total_capacity_value; GtkWidget *file_system_value; /* Permissions tab Widgets */ GtkWidget *permissions_box; GtkWidget *permissions_grid; GtkWidget *bottom_prompt_seperator; GtkWidget *not_the_owner_label; GtkWidget *permission_indeterminable_label; GtkWidget *owner_value_stack; GtkWidget *owner_access_label; GtkWidget *owner_access_combo; GtkWidget *owner_folder_access_label; GtkWidget *owner_folder_access_combo; GtkWidget *owner_file_access_label; GtkWidget *owner_file_access_combo; GtkWidget *group_value_stack; GtkWidget *group_access_label; GtkWidget *group_access_combo; GtkWidget *group_folder_access_label; GtkWidget *group_folder_access_combo; GtkWidget *group_file_access_label; GtkWidget *group_file_access_combo; GtkWidget *others_access_label; GtkWidget *others_access_combo; GtkWidget *others_folder_access_label; GtkWidget *others_folder_access_combo; GtkWidget *others_file_access_label; GtkWidget *others_file_access_combo; GtkWidget *execute_label; GtkWidget *execute_checkbox; GtkWidget *security_context_title_label; GtkWidget *security_context_value_label; GtkWidget *change_permissions_button_box; GtkWidget *change_permissions_button; /* Open With tab Widgets */ GtkWidget *open_with_box; GtkWidget *open_with_label; GtkWidget *app_chooser_widget_box; GtkWidget *app_chooser_widget; GtkWidget *reset_button; GtkWidget *add_button; GtkWidget *set_as_default_button; char *content_type; GList *open_with_files; GroupChange *group_change; OwnerChange *owner_change; GList *permission_buttons; GList *permission_combos; GList *change_permission_combos; GHashTable *initial_permissions; gboolean has_recursive_apply; GList *value_fields; GList *mime_list; gboolean deep_count_finished; GList *deep_count_files; guint deep_count_spinner_timeout_id; guint long_operation_underway; GList *changed_files; guint64 volume_capacity; guint64 volume_free; guint64 volume_used; }; enum { COLUMN_NAME, COLUMN_VALUE, COLUMN_USE_ORIGINAL, COLUMN_ID, NUM_COLUMNS }; typedef struct { GList *original_files; GList *target_files; GtkWidget *parent_widget; GtkWindow *parent_window; char *startup_id; char *pending_key; GHashTable *pending_files; NautilusPropertiesWindowCallback callback; gpointer callback_data; NautilusPropertiesWindow *window; gboolean cancelled; } StartupData; /* drag and drop definitions */ enum { TARGET_URI_LIST, TARGET_GNOME_URI_LIST, }; static const GtkTargetEntry target_table[] = { { "text/uri-list", 0, TARGET_URI_LIST }, { "x-special/gnome-icon-list", 0, TARGET_GNOME_URI_LIST }, }; #define DIRECTORY_CONTENTS_UPDATE_INTERVAL 200 /* milliseconds */ #define FILES_UPDATE_INTERVAL 200 /* milliseconds */ /* * A timeout before changes through the user/group combo box will be applied. * When quickly changing owner/groups (i.e. by keyboard or scroll wheel), * this ensures that the GUI doesn't end up unresponsive. * * Both combos react on changes by scheduling a new change and unscheduling * or cancelling old pending changes. */ #define CHOWN_CHGRP_TIMEOUT 300 /* milliseconds */ static void schedule_directory_contents_update (NautilusPropertiesWindow *window); static void directory_contents_value_field_update (NautilusPropertiesWindow *window); static void file_changed_callback (NautilusFile *file, gpointer user_data); static void permission_button_update (NautilusPropertiesWindow *window, GtkToggleButton *button); static void permission_combo_update (NautilusPropertiesWindow *window, GtkComboBox *combo); static void value_field_update (NautilusPropertiesWindow *window, GtkLabel *field); static void properties_window_update (NautilusPropertiesWindow *window, GList *files); static void is_directory_ready_callback (NautilusFile *file, gpointer data); static void cancel_group_change_callback (GroupChange *change); static void cancel_owner_change_callback (OwnerChange *change); static void select_image_button_callback (GtkWidget *widget, NautilusPropertiesWindow *properties_window); static void set_icon (const char *icon_path, NautilusPropertiesWindow *properties_window); static void remove_pending (StartupData *data, gboolean cancel_call_when_ready, gboolean cancel_timed_wait); static void append_extension_pages (NautilusPropertiesWindow *window); static void name_field_focus_changed (GObject *object, GParamSpec *pspec, gpointer user_data); static void name_field_activate (GtkWidget *name_field, gpointer user_data); static void setup_pie_widget (NautilusPropertiesWindow *window); G_DEFINE_TYPE (NautilusPropertiesWindow, nautilus_properties_window, GTK_TYPE_WINDOW); static gboolean is_multi_file_window (NautilusPropertiesWindow *window) { GList *l; int count; count = 0; for (l = window->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 int get_not_gone_original_file_count (NautilusPropertiesWindow *window) { GList *l; int count; count = 0; for (l = window->original_files; l != NULL; l = l->next) { if (!nautilus_file_is_gone (NAUTILUS_FILE (l->data))) { count++; } } return count; } static NautilusFile * get_original_file (NautilusPropertiesWindow *window) { g_return_val_if_fail (!is_multi_file_window (window), NULL); if (window->original_files == NULL) { return NULL; } return NAUTILUS_FILE (window->original_files->data); } static NautilusFile * get_target_file_for_original_file (NautilusFile *file) { NautilusFile *target_file; g_autoptr (GFile) location = NULL; g_autofree char *uri_to_display = NULL; uri_to_display = nautilus_file_get_uri (file); location = g_file_new_for_uri (uri_to_display); target_file = nautilus_file_get (location); return target_file; } static NautilusFile * get_target_file (NautilusPropertiesWindow *window) { return NAUTILUS_FILE (window->target_files->data); } static void get_image_for_properties_window (NautilusPropertiesWindow *window, char **icon_name, GdkPixbuf **icon_pixbuf) { NautilusIconInfo *icon, *new_icon; GList *l; gint icon_scale; icon = NULL; icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (window->notebook)); for (l = window->original_files; l != NULL; l = l->next) { NautilusFile *file; file = NAUTILUS_FILE (l->data); if (!icon) { icon = nautilus_file_get_icon (file, NAUTILUS_CANVAS_ICON_SIZE_STANDARD, icon_scale, NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS | NAUTILUS_FILE_ICON_FLAGS_IGNORE_VISITING); } else { new_icon = nautilus_file_get_icon (file, NAUTILUS_CANVAS_ICON_SIZE_STANDARD, icon_scale, NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS | NAUTILUS_FILE_ICON_FLAGS_IGNORE_VISITING); if (!new_icon || new_icon != icon) { g_object_unref (icon); g_object_unref (new_icon); icon = NULL; break; } g_object_unref (new_icon); } } if (!icon) { icon = nautilus_icon_info_lookup_from_name ("text-x-generic", NAUTILUS_CANVAS_ICON_SIZE_STANDARD, icon_scale); } if (icon_name != NULL) { *icon_name = g_strdup (nautilus_icon_info_get_used_name (icon)); } if (icon_pixbuf != NULL) { *icon_pixbuf = nautilus_icon_info_get_pixbuf_at_size (icon, NAUTILUS_CANVAS_ICON_SIZE_STANDARD); } g_object_unref (icon); } static void update_properties_window_icon (NautilusPropertiesWindow *window) { GdkPixbuf *pixbuf; cairo_surface_t *surface; char *name; get_image_for_properties_window (window, &name, &pixbuf); if (name != NULL) { gtk_window_set_icon_name (GTK_WINDOW (window), name); } else { gtk_window_set_icon (GTK_WINDOW (window), pixbuf); } surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, gtk_widget_get_scale_factor (GTK_WIDGET (window)), gtk_widget_get_window (GTK_WIDGET (window))); gtk_image_set_from_surface (GTK_IMAGE (window->icon_image), surface); gtk_image_set_from_surface (GTK_IMAGE (window->icon_button_image), surface); g_free (name); g_object_unref (pixbuf); cairo_surface_destroy (surface); } /* utility to test if a uri refers to a local image */ static gboolean uri_is_local_image (const char *uri) { GdkPixbuf *pixbuf; char *image_path; image_path = g_filename_from_uri (uri, NULL, NULL); if (image_path == NULL) { return FALSE; } pixbuf = gdk_pixbuf_new_from_file (image_path, NULL); g_free (image_path); if (pixbuf == NULL) { return FALSE; } g_object_unref (pixbuf); return TRUE; } static void reset_icon (NautilusPropertiesWindow *properties_window) { GList *l; for (l = properties_window->original_files; l != NULL; l = l->next) { NautilusFile *file; file = NAUTILUS_FILE (l->data); nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL, NULL); } } static void nautilus_properties_window_drag_data_received (GtkWidget *widget, GdkDragContext *context, int x, int y, GtkSelectionData *selection_data, guint info, guint time) { char **uris; gboolean exactly_one; GtkImage *image; GtkWindow *window; image = GTK_IMAGE (widget); window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (image))); uris = g_strsplit ((const gchar *) gtk_selection_data_get_data (selection_data), "\r\n", 0); exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0'); if (!exactly_one) { show_dialog (_("You cannot assign more than one custom icon at a time!"), _("Please drop just one image to set a custom icon."), window, GTK_MESSAGE_ERROR); } else { if (uri_is_local_image (uris[0])) { set_icon (uris[0], NAUTILUS_PROPERTIES_WINDOW (window)); } else { GFile *f; f = g_file_new_for_uri (uris[0]); if (!g_file_is_native (f)) { show_dialog (_("The file that you dropped is not local."), _("You can only use local images as custom icons."), window, GTK_MESSAGE_ERROR); } else { show_dialog (_("The file that you dropped is not an image."), _("You can only use local images as custom icons."), window, GTK_MESSAGE_ERROR); } g_object_unref (f); } } g_strfreev (uris); } static void setup_image_widget (NautilusPropertiesWindow *window, gboolean is_customizable) { update_properties_window_icon (window); if (is_customizable) { /* prepare the image to receive dropped objects to assign custom images */ gtk_drag_dest_set (window->icon_button_image, GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, target_table, G_N_ELEMENTS (target_table), GDK_ACTION_COPY | GDK_ACTION_MOVE); g_signal_connect (window->icon_button_image, "drag-data-received", G_CALLBACK (nautilus_properties_window_drag_data_received), NULL); g_signal_connect (window->icon_button, "clicked", G_CALLBACK (select_image_button_callback), window); gtk_stack_set_visible_child (window->icon_stack, window->icon_button); } else { gtk_stack_set_visible_child (window->icon_stack, window->icon_image); } } static void set_name_field (NautilusPropertiesWindow *window, const gchar *original_name, const gchar *name) { GtkWidget *stack_child_label; GtkWidget *stack_child_entry; gboolean use_label; stack_child_label = gtk_stack_get_child_by_name (window->name_stack, "name_value_label"); stack_child_entry = gtk_stack_get_child_by_name (window->name_stack, "name_value_entry"); use_label = is_multi_file_window (window) || !nautilus_file_can_rename (get_original_file (window)); if (use_label) { gtk_label_set_text (GTK_LABEL (stack_child_label), name); gtk_stack_set_visible_child (window->name_stack, stack_child_label); } else { gtk_stack_set_visible_child (window->name_stack, stack_child_entry); } /* Only replace text if the file's name has changed. */ if (original_name == NULL || strcmp (original_name, name) != 0) { if (!use_label) { /* Only reset the text if it's different from what is * currently showing. This causes minimal ripples (e.g. * selection change). */ gchar *displayed_name = gtk_editable_get_chars (GTK_EDITABLE (window->name_field), 0, -1); if (strcmp (displayed_name, name) != 0) { gtk_entry_set_text (GTK_ENTRY (window->name_field), name); } g_free (displayed_name); } } } static void update_name_field (NautilusPropertiesWindow *window) { NautilusFile *file; gtk_label_set_text_with_mnemonic (window->name_title_label, ngettext ("_Name", "_Names", get_not_gone_original_file_count (window))); if (is_multi_file_window (window)) { /* Multifile property dialog, show all names */ GString *str; char *name; gboolean first; GList *l; str = g_string_new (""); first = TRUE; for (l = window->target_files; l != NULL; l = l->next) { file = NAUTILUS_FILE (l->data); 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); } } set_name_field (window, NULL, str->str); g_string_free (str, TRUE); } else { const char *original_name = NULL; char *current_name; file = get_original_file (window); if (file == NULL || nautilus_file_is_gone (file)) { current_name = g_strdup (""); } else { current_name = nautilus_file_get_display_name (file); } /* 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. */ original_name = (const char *) g_object_get_data (G_OBJECT (window->name_field), "original_name"); set_name_field (window, original_name, current_name); if (original_name == NULL || g_strcmp0 (original_name, current_name) != 0) { g_object_set_data_full (G_OBJECT (window->name_field), "original_name", current_name, g_free); } else { g_free (current_name); } } } static void name_field_restore_original_name (GtkWidget *name_field) { const char *original_name; char *displayed_name; 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) { gtk_entry_set_text (GTK_ENTRY (name_field), original_name); } gtk_editable_select_region (GTK_EDITABLE (name_field), 0, -1); g_free (displayed_name); } static void rename_callback (NautilusFile *file, GFile *res_loc, GError *error, gpointer callback_data) { NautilusPropertiesWindow *window; window = NAUTILUS_PROPERTIES_WINDOW (callback_data); /* Complain to user if rename failed. */ if (error != NULL) { nautilus_report_error_renaming_file (file, window->pending_name, error, GTK_WINDOW (window)); name_field_restore_original_name (window->name_field); } g_object_unref (window); } static void set_pending_name (NautilusPropertiesWindow *window, const char *name) { g_free (window->pending_name); window->pending_name = g_strdup (name); } static void name_field_done_editing (GtkWidget *name_field, NautilusPropertiesWindow *window) { NautilusFile *file; char *new_name; const char *original_name; g_return_if_fail (GTK_IS_ENTRY (name_field)); /* Don't apply if the dialog has more than one file */ if (is_multi_file_window (window)) { return; } file = get_original_file (window); /* This gets called when the window is closed, which might be * caused by the file having been deleted. */ if (file == NULL || nautilus_file_is_gone (file)) { return; } new_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1); /* Special case: silently revert text if new text is empty. */ if (strlen (new_name) == 0) { name_field_restore_original_name (name_field); } else { original_name = (const char *) g_object_get_data (G_OBJECT (window->name_field), "original_name"); /* Don't rename if not changed since we read the display name. * This is needed so that we don't save the display name to the * file when nothing is changed */ if (strcmp (new_name, original_name) != 0) { set_pending_name (window, new_name); g_object_ref (window); nautilus_file_rename (file, new_name, rename_callback, window); } } g_free (new_name); } static void name_field_focus_changed (GObject *object, GParamSpec *pspec, gpointer user_data) { GtkWidget *widget; g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (user_data)); widget = GTK_WIDGET (object); if (!gtk_widget_has_focus (widget) && gtk_widget_get_sensitive (widget)) { name_field_done_editing (widget, NAUTILUS_PROPERTIES_WINDOW (user_data)); } } static gboolean select_all_at_idle (gpointer user_data) { NautilusPropertiesWindow *window; window = NAUTILUS_PROPERTIES_WINDOW (user_data); gtk_editable_select_region (GTK_EDITABLE (window->name_field), 0, -1); window->select_idle_id = 0; return FALSE; } static void name_field_activate (GtkWidget *name_field, gpointer user_data) { NautilusPropertiesWindow *window; g_assert (GTK_IS_ENTRY (name_field)); g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (user_data)); window = NAUTILUS_PROPERTIES_WINDOW (user_data); /* Accept changes. */ name_field_done_editing (name_field, window); if (window->select_idle_id == 0) { window->select_idle_id = g_idle_add (select_all_at_idle, window); } } static void update_properties_window_title (NautilusPropertiesWindow *window) { char *name, *title; NautilusFile *file; g_return_if_fail (GTK_IS_WINDOW (window)); title = g_strdup_printf (_("Properties")); if (!is_multi_file_window (window)) { file = get_original_file (window); if (file != NULL) { g_free (title); name = nautilus_file_get_display_name (file); if (nautilus_file_is_directory (file)) { /* To translators: %s is the name of the folder. */ title = g_strdup_printf (C_("folder", "%s Properties"), name); } else { /* To translators: %s is the name of the file. */ title = g_strdup_printf (C_("file", "%s Properties"), name); } g_free (name); } } gtk_window_set_title (GTK_WINDOW (window), title); g_free (title); } static void clear_extension_pages (NautilusPropertiesWindow *window) { int i; int num_pages; GtkWidget *page; num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (window->notebook)); for (i = 0; i < num_pages; i++) { page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (window->notebook), i); if (g_object_get_data (G_OBJECT (page), "is-extension-page")) { gtk_notebook_remove_page (GTK_NOTEBOOK (window->notebook), i); num_pages--; i--; } } } static void refresh_extension_pages (NautilusPropertiesWindow *window) { clear_extension_pages (window); append_extension_pages (window); } static void remove_from_dialog (NautilusPropertiesWindow *window, NautilusFile *file) { int index; GList *original_link; GList *target_link; NautilusFile *original_file; NautilusFile *target_file; index = g_list_index (window->target_files, file); if (index == -1) { index = g_list_index (window->original_files, file); g_return_if_fail (index != -1); } original_link = g_list_nth (window->original_files, index); target_link = g_list_nth (window->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->original_files = g_list_remove_link (window->original_files, original_link); g_list_free (original_link); window->target_files = g_list_remove_link (window->target_files, target_link); g_list_free (target_link); g_hash_table_remove (window->initial_permissions, target_file); g_signal_handlers_disconnect_by_func (original_file, G_CALLBACK (file_changed_callback), window); g_signal_handlers_disconnect_by_func (target_file, G_CALLBACK (file_changed_callback), window); nautilus_file_monitor_remove (original_file, &window->original_files); nautilus_file_monitor_remove (target_file, &window->target_files); 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 (NautilusPropertiesWindow *window) { GList *ret; GList *l; ret = NULL; for (l = window->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 gboolean start_spinner_callback (NautilusPropertiesWindow *window) { gtk_widget_show (window->contents_spinner); gtk_spinner_start (GTK_SPINNER (window->contents_spinner)); window->deep_count_spinner_timeout_id = 0; return FALSE; } static void schedule_start_spinner (NautilusPropertiesWindow *window) { if (window->deep_count_spinner_timeout_id == 0) { window->deep_count_spinner_timeout_id = g_timeout_add_seconds (1, (GSourceFunc) start_spinner_callback, window); } } static void stop_spinner (NautilusPropertiesWindow *window) { gtk_spinner_stop (GTK_SPINNER (window->contents_spinner)); gtk_widget_hide (window->contents_spinner); if (window->deep_count_spinner_timeout_id > 0) { g_source_remove (window->deep_count_spinner_timeout_id); window->deep_count_spinner_timeout_id = 0; } } static void stop_deep_count_for_file (NautilusPropertiesWindow *window, NautilusFile *file) { if (g_list_find (window->deep_count_files, file)) { g_signal_handlers_disconnect_by_func (file, G_CALLBACK (schedule_directory_contents_update), window); nautilus_file_unref (file); window->deep_count_files = g_list_remove (window->deep_count_files, file); } } static void start_deep_count_for_file (NautilusPropertiesWindow *window, NautilusFile *file) { if (!nautilus_file_is_directory (file)) { return; } if (!g_list_find (window->deep_count_files, file)) { nautilus_file_ref (file); window->deep_count_files = g_list_prepend (window->deep_count_files, file); nautilus_file_recompute_deep_counts (file); if (!window->deep_count_finished) { g_signal_connect_object (file, "updated-deep-count-in-progress", G_CALLBACK (schedule_directory_contents_update), window, G_CONNECT_SWAPPED); schedule_start_spinner (window); } } } static void properties_window_update (NautilusPropertiesWindow *window, GList *files) { GList *l; GList *mime_list; GList *tmp; NautilusFile *changed_file; gboolean dirty_original = FALSE; gboolean dirty_target = FALSE; if (files == NULL) { dirty_original = TRUE; dirty_target = TRUE; } for (tmp = files; tmp != NULL; tmp = tmp->next) { changed_file = NAUTILUS_FILE (tmp->data); if (changed_file && nautilus_file_is_gone (changed_file)) { /* Remove the file from the property dialog */ remove_from_dialog (window, changed_file); changed_file = NULL; if (window->original_files == NULL) { return; } } if (changed_file == NULL || g_list_find (window->original_files, changed_file)) { dirty_original = TRUE; } if (changed_file == NULL || g_list_find (window->target_files, changed_file)) { dirty_target = TRUE; } } if (dirty_original) { update_properties_window_title (window); update_properties_window_icon (window); update_name_field (window); /* If any of the value fields start to depend on the original * value, value_field_updates should be added here */ } if (dirty_target) { for (l = window->permission_buttons; l != NULL; l = l->next) { permission_button_update (window, GTK_TOGGLE_BUTTON (l->data)); } for (l = window->permission_combos; l != NULL; l = l->next) { permission_combo_update (window, GTK_COMBO_BOX (l->data)); } for (l = window->value_fields; l != NULL; l = l->next) { value_field_update (window, GTK_LABEL (l->data)); } } mime_list = get_mime_list (window); if (!window->mime_list) { window->mime_list = mime_list; } else { if (!mime_list_equal (window->mime_list, mime_list)) { refresh_extension_pages (window); } g_list_free_full (window->mime_list, g_free); window->mime_list = mime_list; } } static gboolean update_files_callback (gpointer data) { NautilusPropertiesWindow *window; window = NAUTILUS_PROPERTIES_WINDOW (data); window->update_files_timeout_id = 0; properties_window_update (window, window->changed_files); if (window->original_files == NULL) { /* Close the window if no files are left */ gtk_widget_destroy (GTK_WIDGET (window)); } else { nautilus_file_list_free (window->changed_files); window->changed_files = NULL; } return FALSE; } static void schedule_files_update (NautilusPropertiesWindow *window) { g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window)); if (window->update_files_timeout_id == 0) { window->update_files_timeout_id = g_timeout_add (FILES_UPDATE_INTERVAL, update_files_callback, window); } } 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; 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); } } g_free (first_attr); return identical; } 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); } } 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; } #define INCONSISTENT_STATE_STRING \ "\xE2\x80\x92" static gboolean location_show_original (NautilusPropertiesWindow *window) { NautilusFile *file; /* there is no way a recent item will be mixed with * other items so just pick the first file to check */ file = NAUTILUS_FILE (g_list_nth_data (window->original_files, 0)); return (file != NULL && !nautilus_file_is_in_recent (file)); } static void value_field_update (NautilusPropertiesWindow *window, GtkLabel *label) { GList *file_list; const char *attribute_name; char *attribute_value; char *inconsistent_string; char *mime_type, *tmp; gboolean is_where; g_assert (GTK_IS_LABEL (label)); attribute_name = g_object_get_data (G_OBJECT (label), "file_attribute"); is_where = (g_strcmp0 (attribute_name, "where") == 0); if (is_where && location_show_original (window)) { file_list = window->original_files; } else { file_list = window->target_files; } inconsistent_string = INCONSISTENT_STATE_STRING; attribute_value = file_list_get_string_attribute (file_list, attribute_name, inconsistent_string); if (!strcmp (attribute_name, "detailed_type") && strcmp (attribute_value, inconsistent_string)) { mime_type = file_list_get_string_attribute (file_list, "mime_type", inconsistent_string); if (strcmp (mime_type, inconsistent_string)) { tmp = attribute_value; attribute_value = g_strdup_printf (C_("MIME type description (MIME type)", "%s (%s)"), attribute_value, mime_type); g_free (tmp); } g_free (mime_type); } gtk_label_set_text (label, attribute_value); g_free (attribute_value); } static void group_change_free (GroupChange *change) { nautilus_file_unref (change->file); g_free (change->group); g_object_unref (change->window); g_free (change); } static void group_change_callback (NautilusFile *file, GFile *res_loc, GError *error, GroupChange *change) { NautilusPropertiesWindow *window; g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window)); g_assert (NAUTILUS_IS_FILE (change->file)); g_assert (change->group != NULL); if (!change->cancelled) { /* Report the error if it's an error. */ eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, change); nautilus_report_error_setting_group (change->file, error, change->window); } window = NAUTILUS_PROPERTIES_WINDOW (change->window); if (window->group_change == change) { window->group_change = NULL; } group_change_free (change); } static void cancel_group_change_callback (GroupChange *change) { g_assert (NAUTILUS_IS_FILE (change->file)); g_assert (change->group != NULL); change->cancelled = TRUE; nautilus_file_cancel (change->file, (NautilusFileOperationCallback) group_change_callback, change); } static gboolean schedule_group_change_timeout (GroupChange *change) { g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window)); g_assert (NAUTILUS_IS_FILE (change->file)); g_assert (change->group != NULL); change->timeout = 0; eel_timed_wait_start ((EelCancelCallback) cancel_group_change_callback, change, _("Cancel Group Change?"), change->window); nautilus_file_set_group (change->file, change->group, (NautilusFileOperationCallback) group_change_callback, change); return FALSE; } static void schedule_group_change (NautilusPropertiesWindow *window, NautilusFile *file, const char *group) { GroupChange *change; g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window)); g_assert (window->group_change == NULL); g_assert (NAUTILUS_IS_FILE (file)); change = g_new0 (GroupChange, 1); change->file = nautilus_file_ref (file); change->group = g_strdup (group); change->window = GTK_WINDOW (g_object_ref (window)); change->timeout = g_timeout_add (CHOWN_CHGRP_TIMEOUT, (GSourceFunc) schedule_group_change_timeout, change); window->group_change = change; } static void unschedule_or_cancel_group_change (NautilusPropertiesWindow *window) { GroupChange *change; g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window)); change = window->group_change; if (change != NULL) { if (change->timeout == 0) { /* The operation was started, cancel it and let the operation callback free the change */ cancel_group_change_callback (change); eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, change); } else { g_source_remove (change->timeout); group_change_free (change); } window->group_change = NULL; } } static void changed_group_callback (GtkComboBox *combo_box, NautilusFile *file) { NautilusPropertiesWindow *window; char *group; char *cur_group; g_assert (GTK_IS_COMBO_BOX (combo_box)); g_assert (NAUTILUS_IS_FILE (file)); group = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (combo_box)); cur_group = nautilus_file_get_group_name (file); if (group != NULL && strcmp (group, cur_group) != 0) { /* Try to change file group. If this fails, complain to user. */ window = NAUTILUS_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW)); unschedule_or_cancel_group_change (window); schedule_group_change (window, file, group); } g_free (group); g_free (cur_group); } /* checks whether the given column at the first level * of model has the specified entries in the given order. */ static gboolean tree_model_entries_equal (GtkTreeModel *model, unsigned int column, GList *entries) { GtkTreeIter iter; gboolean empty_model; g_assert (GTK_IS_TREE_MODEL (model)); g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING); empty_model = !gtk_tree_model_get_iter_first (model, &iter); if (!empty_model && entries != NULL) { GList *l; l = entries; do { char *val; gtk_tree_model_get (model, &iter, column, &val, -1); if ((val == NULL && l->data != NULL) || (val != NULL && l->data == NULL) || (val != NULL && strcmp (val, l->data))) { g_free (val); return FALSE; } g_free (val); l = l->next; } while (gtk_tree_model_iter_next (model, &iter)); return l == NULL; } else { return (empty_model && entries == NULL) || (!empty_model && entries != NULL); } } static char * combo_box_get_active_entry (GtkComboBox *combo_box, unsigned int column) { GtkTreeModel *model; GtkTreeIter iter; char *val; g_assert (GTK_IS_COMBO_BOX (combo_box)); if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter)) { model = gtk_combo_box_get_model (combo_box); g_assert (GTK_IS_TREE_MODEL (model)); gtk_tree_model_get (model, &iter, column, &val, -1); return val; } return NULL; } /* returns the index of the given entry in the the given column * at the first level of model. Returns -1 if entry can't be found * or entry is NULL. * */ static int tree_model_get_entry_index (GtkTreeModel *model, unsigned int column, const char *entry) { GtkTreeIter iter; int index; gboolean empty_model; g_assert (GTK_IS_TREE_MODEL (model)); g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING); empty_model = !gtk_tree_model_get_iter_first (model, &iter); if (!empty_model && entry != NULL) { index = 0; do { char *val; gtk_tree_model_get (model, &iter, column, &val, -1); if (val != NULL && !strcmp (val, entry)) { g_free (val); return index; } g_free (val); index++; } while (gtk_tree_model_iter_next (model, &iter)); } return -1; } static void synch_groups_combo_box (GtkComboBox *combo_box, NautilusFile *file) { GList *groups; GList *node; GtkTreeModel *model; GtkListStore *store; const char *group_name; char *current_group_name; int group_index; int current_group_index; g_assert (GTK_IS_COMBO_BOX (combo_box)); g_assert (NAUTILUS_IS_FILE (file)); if (nautilus_file_is_gone (file)) { return; } groups = nautilus_file_get_settable_group_names (file); model = gtk_combo_box_get_model (combo_box); store = GTK_LIST_STORE (model); g_assert (GTK_IS_LIST_STORE (model)); if (!tree_model_entries_equal (model, 0, groups)) { /* Clear the contents of ComboBox in a wacky way because there * is no function to clear all items and also no function to obtain * the number of items in a combobox. */ gtk_list_store_clear (store); for (node = groups, group_index = 0; node != NULL; node = node->next, ++group_index) { group_name = (const char *) node->data; gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo_box), group_name); } } current_group_name = nautilus_file_get_group_name (file); current_group_index = tree_model_get_entry_index (model, 0, current_group_name); /* If current group wasn't in list, we prepend it (with a separator). * This can happen if the current group is an id with no matching * group in the groups file. */ if (current_group_index < 0 && current_group_name != NULL) { if (groups != NULL) { /* add separator */ gtk_combo_box_text_prepend_text (GTK_COMBO_BOX_TEXT (combo_box), "-"); } gtk_combo_box_text_prepend_text (GTK_COMBO_BOX_TEXT (combo_box), current_group_name); current_group_index = 0; } gtk_combo_box_set_active (combo_box, current_group_index); g_free (current_group_name); g_list_free_full (groups, g_free); } static gboolean combo_box_row_separator_func (GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { gchar *text; gboolean ret; gtk_tree_model_get (model, iter, 0, &text, -1); if (text == NULL) { return FALSE; } if (strcmp (text, "-") == 0) { ret = TRUE; } else { ret = FALSE; } g_free (text); return ret; } static void setup_group_combo_box (GtkWidget *combo_box, NautilusFile *file) { gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo_box), combo_box_row_separator_func, NULL, NULL); synch_groups_combo_box (GTK_COMBO_BOX (combo_box), file); /* Connect to signal to update menu when file changes. */ g_signal_connect_object (file, "changed", G_CALLBACK (synch_groups_combo_box), combo_box, G_CONNECT_SWAPPED); g_signal_connect_data (combo_box, "changed", G_CALLBACK (changed_group_callback), nautilus_file_ref (file), (GClosureNotify) nautilus_file_unref, 0); } static void owner_change_free (OwnerChange *change) { nautilus_file_unref (change->file); g_free (change->owner); g_object_unref (change->window); g_free (change); } static void owner_change_callback (NautilusFile *file, GFile *result_location, GError *error, OwnerChange *change) { NautilusPropertiesWindow *window; g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window)); g_assert (NAUTILUS_IS_FILE (change->file)); g_assert (change->owner != NULL); if (!change->cancelled) { /* Report the error if it's an error. */ eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, change); nautilus_report_error_setting_owner (file, error, change->window); } window = NAUTILUS_PROPERTIES_WINDOW (change->window); if (window->owner_change == change) { window->owner_change = NULL; } owner_change_free (change); } static void cancel_owner_change_callback (OwnerChange *change) { g_assert (NAUTILUS_IS_FILE (change->file)); g_assert (change->owner != NULL); change->cancelled = TRUE; nautilus_file_cancel (change->file, (NautilusFileOperationCallback) owner_change_callback, change); } static gboolean schedule_owner_change_timeout (OwnerChange *change) { g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window)); g_assert (NAUTILUS_IS_FILE (change->file)); g_assert (change->owner != NULL); change->timeout = 0; eel_timed_wait_start ((EelCancelCallback) cancel_owner_change_callback, change, _("Cancel Owner Change?"), change->window); nautilus_file_set_owner (change->file, change->owner, (NautilusFileOperationCallback) owner_change_callback, change); return FALSE; } static void schedule_owner_change (NautilusPropertiesWindow *window, NautilusFile *file, const char *owner) { OwnerChange *change; g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window)); g_assert (window->owner_change == NULL); g_assert (NAUTILUS_IS_FILE (file)); change = g_new0 (OwnerChange, 1); change->file = nautilus_file_ref (file); change->owner = g_strdup (owner); change->window = GTK_WINDOW (g_object_ref (window)); change->timeout = g_timeout_add (CHOWN_CHGRP_TIMEOUT, (GSourceFunc) schedule_owner_change_timeout, change); window->owner_change = change; } static void unschedule_or_cancel_owner_change (NautilusPropertiesWindow *window) { OwnerChange *change; g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window)); change = window->owner_change; if (change != NULL) { g_assert (NAUTILUS_IS_FILE (change->file)); if (change->timeout == 0) { /* The operation was started, cancel it and let the operation callback free the change */ cancel_owner_change_callback (change); eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, change); } else { g_source_remove (change->timeout); owner_change_free (change); } window->owner_change = NULL; } } static void changed_owner_callback (GtkComboBox *combo_box, NautilusFile *file) { NautilusPropertiesWindow *window; char *new_owner; char *cur_owner; g_assert (GTK_IS_COMBO_BOX (combo_box)); g_assert (NAUTILUS_IS_FILE (file)); new_owner = combo_box_get_active_entry (combo_box, 2); if (!new_owner) { return; } cur_owner = nautilus_file_get_owner_name (file); if (strcmp (new_owner, cur_owner) != 0) { /* Try to change file owner. If this fails, complain to user. */ window = NAUTILUS_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW)); unschedule_or_cancel_owner_change (window); schedule_owner_change (window, file, new_owner); } g_free (new_owner); g_free (cur_owner); } static void synch_user_menu (GtkComboBox *combo_box, NautilusFile *file) { GList *users; GList *node; GtkTreeModel *model; GtkListStore *store; GtkTreeIter iter; char *user_name; char *owner_name; char *nice_owner_name; int user_index; int owner_index; char **name_array; char *combo_text; g_assert (GTK_IS_COMBO_BOX (combo_box)); g_assert (NAUTILUS_IS_FILE (file)); if (nautilus_file_is_gone (file)) { return; } users = nautilus_get_user_names (); model = gtk_combo_box_get_model (combo_box); store = GTK_LIST_STORE (model); g_assert (GTK_IS_LIST_STORE (model)); if (!tree_model_entries_equal (model, 1, users)) { /* Clear the contents of ComboBox in a wacky way because there * is no function to clear all items and also no function to obtain * the number of items in a combobox. */ gtk_list_store_clear (store); for (node = users, user_index = 0; node != NULL; node = node->next, ++user_index) { user_name = (char *) node->data; name_array = g_strsplit (user_name, "\n", 2); if (name_array[1] != NULL && *name_array[1] != 0) { combo_text = g_strdup_printf ("%s - %s", name_array[0], name_array[1]); } else { combo_text = g_strdup (name_array[0]); } gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, combo_text, 1, user_name, 2, name_array[0], -1); g_strfreev (name_array); g_free (combo_text); } } owner_name = nautilus_file_get_owner_name (file); owner_index = tree_model_get_entry_index (model, 2, owner_name); nice_owner_name = nautilus_file_get_string_attribute (file, "owner"); /* If owner wasn't in list, we prepend it (with a separator). * This can happen if the owner is an id with no matching * identifier in the passwords file. */ if (owner_index < 0 && owner_name != NULL) { if (users != NULL) { /* add separator */ gtk_list_store_prepend (store, &iter); gtk_list_store_set (store, &iter, 0, "-", 1, NULL, 2, NULL, -1); } owner_index = 0; gtk_list_store_prepend (store, &iter); gtk_list_store_set (store, &iter, 0, nice_owner_name, 1, owner_name, 2, owner_name, -1); } gtk_combo_box_set_active (combo_box, owner_index); g_free (owner_name); g_free (nice_owner_name); g_list_free_full (users, g_free); } static void setup_owner_combo_box (GtkWidget *combo_box, NautilusFile *file) { GtkTreeModel *model; GtkCellRenderer *renderer; model = GTK_TREE_MODEL (gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING)); gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), model); g_object_unref (G_OBJECT (model)); renderer = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, TRUE); gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo_box), renderer, "text", 0); gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo_box), combo_box_row_separator_func, NULL, NULL); synch_user_menu (GTK_COMBO_BOX (combo_box), file); /* Connect to signal to update menu when file changes. */ g_signal_connect_object (file, "changed", G_CALLBACK (synch_user_menu), combo_box, G_CONNECT_SWAPPED); g_signal_connect_data (combo_box, "changed", G_CALLBACK (changed_owner_callback), nautilus_file_ref (file), (GClosureNotify) nautilus_file_unref, 0); } static gboolean file_has_prefix (NautilusFile *file, GList *prefix_candidates) { GList *p; GFile *location, *candidate_location; location = nautilus_file_get_location (file); for (p = prefix_candidates; p != NULL; p = p->next) { if (file == p->data) { continue; } candidate_location = nautilus_file_get_location (NAUTILUS_FILE (p->data)); if (g_file_has_prefix (location, candidate_location)) { g_object_unref (location); g_object_unref (candidate_location); return TRUE; } g_object_unref (candidate_location); } g_object_unref (location); return FALSE; } static void directory_contents_value_field_update (NautilusPropertiesWindow *window) { NautilusRequestStatus file_status; char *text, *temp; guint directory_count; guint file_count; guint total_count; guint unreadable_directory_count; goffset total_size; NautilusFile *file; GList *l; guint file_unreadable; goffset file_size; gboolean deep_count_active; g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window)); total_count = 0; total_size = 0; unreadable_directory_count = FALSE; for (l = window->target_files; l; l = l->next) { file = NAUTILUS_FILE (l->data); if (file_has_prefix (file, window->target_files)) { /* don't count nested files twice */ continue; } if (nautilus_file_is_directory (file)) { file_status = nautilus_file_get_deep_counts (file, &directory_count, &file_count, &file_unreadable, &file_size, TRUE); total_count += (file_count + directory_count); total_size += file_size; if (file_unreadable) { unreadable_directory_count = TRUE; } if (file_status == NAUTILUS_REQUEST_DONE) { stop_deep_count_for_file (window, file); } } else { ++total_count; total_size += nautilus_file_get_size (file); } } deep_count_active = (window->deep_count_files != NULL); /* If we've already displayed the total once, don't do another visible * count-up if the deep_count happens to get invalidated. * But still display the new total, since it might have changed. */ if (window->deep_count_finished && deep_count_active) { return; } text = NULL; if (total_count == 0) { if (!deep_count_active) { if (unreadable_directory_count == 0) { text = g_strdup (_("nothing")); } else { text = g_strdup (_("unreadable")); } } else { text = g_strdup ("…"); } } else { char *size_str; size_str = g_format_size (total_size); text = g_strdup_printf (ngettext ("%'d item, with size %s", "%'d items, totalling %s", total_count), total_count, size_str); g_free (size_str); if (unreadable_directory_count != 0) { temp = text; text = g_strconcat (temp, "\n", _("(some contents unreadable)"), NULL); g_free (temp); } } gtk_label_set_text (GTK_LABEL (window->contents_value_label), text); g_free (text); if (!deep_count_active) { window->deep_count_finished = TRUE; stop_spinner (window); } } static gboolean update_directory_contents_callback (gpointer data) { NautilusPropertiesWindow *window; window = NAUTILUS_PROPERTIES_WINDOW (data); window->update_directory_contents_timeout_id = 0; directory_contents_value_field_update (window); return FALSE; } static void schedule_directory_contents_update (NautilusPropertiesWindow *window) { g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window)); if (window->update_directory_contents_timeout_id == 0) { window->update_directory_contents_timeout_id = g_timeout_add (DIRECTORY_CONTENTS_UPDATE_INTERVAL, update_directory_contents_callback, window); } } static void setup_contents_field (NautilusPropertiesWindow *window, GtkGrid *grid) { GList *l; for (l = window->target_files; l; l = l->next) { NautilusFile *file; file = NAUTILUS_FILE (l->data); start_deep_count_for_file (window, file); } /* Fill in the initial value. */ directory_contents_value_field_update (window); } static gboolean is_root_directory (NautilusFile *file) { GFile *location; gboolean result; location = nautilus_file_get_location (file); result = nautilus_is_root_directory (location); g_object_unref (location); return result; } static gboolean is_network_directory (NautilusFile *file) { char *file_uri; gboolean result; file_uri = nautilus_file_get_uri (file); result = strcmp (file_uri, "network:///") == 0; g_free (file_uri); return result; } static gboolean is_burn_directory (NautilusFile *file) { char *file_uri; gboolean result; file_uri = nautilus_file_get_uri (file); result = strcmp (file_uri, "burn:///") == 0; g_free (file_uri); return result; } static gboolean should_show_custom_icon_buttons (NautilusPropertiesWindow *window) { if (is_multi_file_window (window)) { return FALSE; } return TRUE; } static gboolean should_show_file_type (NautilusPropertiesWindow *window) { if (!is_multi_file_window (window) && (nautilus_file_is_in_trash (get_target_file (window)) || is_network_directory (get_target_file (window)) || is_burn_directory (get_target_file (window)))) { return FALSE; } return TRUE; } static gboolean should_show_location_info (NautilusPropertiesWindow *window) { GList *l; for (l = window->original_files; l != NULL; l = l->next) { if (nautilus_file_is_in_trash (NAUTILUS_FILE (l->data)) || is_root_directory (NAUTILUS_FILE (l->data)) || is_network_directory (NAUTILUS_FILE (l->data)) || is_burn_directory (NAUTILUS_FILE (l->data))) { return FALSE; } } return TRUE; } static gboolean should_show_trash_orig_path (NautilusPropertiesWindow *window) { GList *l; for (l = window->original_files; l != NULL; l = l->next) { if (!nautilus_file_is_in_trash (NAUTILUS_FILE (l->data))) { return FALSE; } } return TRUE; } static gboolean should_show_accessed_date (NautilusPropertiesWindow *window) { /* Accessed date for directory seems useless. If we some * day decide that it is useful, we should separately * consider whether it's useful for "trash:". */ if (file_list_all_directories (window->target_files) || is_multi_file_window (window)) { return FALSE; } return TRUE; } static gboolean should_show_modified_date (NautilusPropertiesWindow *window) { return !is_multi_file_window (window); } static gboolean should_show_trashed_on (NautilusPropertiesWindow *window) { GList *l; for (l = window->original_files; l != NULL; l = l->next) { if (!nautilus_file_is_in_trash (NAUTILUS_FILE (l->data))) { return FALSE; } } return TRUE; } static gboolean should_show_link_target (NautilusPropertiesWindow *window) { if (!is_multi_file_window (window) && nautilus_file_is_symbolic_link (get_target_file (window))) { return TRUE; } return FALSE; } static gboolean should_show_free_space (NautilusPropertiesWindow *window) { if (!is_multi_file_window (window) && (nautilus_file_is_in_trash (get_target_file (window)) || is_network_directory (get_target_file (window)) || nautilus_file_is_in_recent (get_target_file (window)) || is_burn_directory (get_target_file (window)))) { return FALSE; } if (file_list_all_directories (window->target_files)) { return TRUE; } return FALSE; } static gboolean should_show_volume_info (NautilusPropertiesWindow *window) { NautilusFile *file; if (is_multi_file_window (window)) { return FALSE; } file = get_original_file (window); if (file == NULL) { return FALSE; } if (nautilus_file_can_unmount (file)) { return TRUE; } return FALSE; } static gboolean should_show_volume_usage (NautilusPropertiesWindow *window) { NautilusFile *file; gboolean success = FALSE; if (is_multi_file_window (window)) { return FALSE; } file = get_original_file (window); if (file == NULL) { return FALSE; } if (nautilus_file_can_unmount (file)) { return TRUE; } success = is_root_directory (file); #ifdef TODO_GIO /* Look at is_mountpoint for activation uri */ #endif return success; } static void paint_legend (GtkWidget *widget, cairo_t *cr, gpointer data) { GtkStyleContext *context; GtkAllocation allocation; gtk_widget_get_allocation (widget, &allocation); context = gtk_widget_get_style_context (widget); gtk_render_background (context, cr, 0, 0, allocation.width, allocation.height); gtk_render_frame (context, cr, 0, 0, allocation.width, allocation.height); } static void paint_slice (GtkWidget *widget, cairo_t *cr, double percent_start, double percent_width, const gchar *style_class) { double angle1; double angle2; gboolean full; double offset = G_PI / 2.0; GdkRGBA fill; GdkRGBA stroke; GtkStateFlags state; GtkBorder border; GtkStyleContext *context; double x, y, radius; gint width, height; if (percent_width < .01) { return; } context = gtk_widget_get_style_context (widget); state = gtk_style_context_get_state (context); gtk_style_context_get_border (context, state, &border); gtk_style_context_save (context); gtk_style_context_add_class (context, style_class); gtk_style_context_get_color (context, state, &fill); gtk_style_context_add_class (context, "border"); gtk_style_context_get_color (context, state, &stroke); gtk_style_context_restore (context); width = gtk_widget_get_allocated_width (widget); height = gtk_widget_get_allocated_height (widget); x = width / 2; y = height / 2; if (width < height) { radius = (width - border.left) / 2; } else { radius = (height - border.top) / 2; } angle1 = (percent_start * 2 * G_PI) - offset; angle2 = angle1 + (percent_width * 2 * G_PI); full = (percent_width > .99); if (!full) { cairo_move_to (cr, x, y); } cairo_arc (cr, x, y, radius, angle1, angle2); if (!full) { cairo_line_to (cr, x, y); } cairo_set_line_width (cr, border.top); gdk_cairo_set_source_rgba (cr, &fill); cairo_fill_preserve (cr); gdk_cairo_set_source_rgba (cr, &stroke); cairo_stroke (cr); } static void paint_pie_chart (GtkWidget *widget, cairo_t *cr, gpointer data) { NautilusPropertiesWindow *window; double free, used, reserved; window = NAUTILUS_PROPERTIES_WINDOW (data); free = (double) window->volume_free / (double) window->volume_capacity; used = (double) window->volume_used / (double) window->volume_capacity; reserved = 1.0 - (used + free); paint_slice (widget, cr, 0, free, "free"); paint_slice (widget, cr, free + used, reserved, "unknown"); /* paint the used last so its slice strokes are on top */ paint_slice (widget, cr, free, used, "used"); } static void setup_pie_widget (NautilusPropertiesWindow *window) { NautilusFile *file; gchar *capacity; gchar *used; gchar *free; const char *fs_type; gchar *uri; GFile *location; GFileInfo *info; capacity = g_format_size (window->volume_capacity); free = g_format_size (window->volume_free); used = g_format_size (window->volume_used); file = get_original_file (window); uri = nautilus_file_get_activation_uri (file); /* Translators: "used" refers to the capacity of the filesystem */ gtk_label_set_text (GTK_LABEL (window->used_value), used); /* Translators: "free" refers to the capacity of the filesystem */ gtk_label_set_text (GTK_LABEL (window->free_value), free); gtk_label_set_text (GTK_LABEL (window->total_capacity_value), capacity); gtk_label_set_text (GTK_LABEL (window->file_system_value), NULL); location = g_file_new_for_uri (uri); info = g_file_query_filesystem_info (location, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, NULL, NULL); if (info) { fs_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE); if (fs_type != NULL) { gtk_label_set_text (GTK_LABEL (window->file_system_value), fs_type); } g_object_unref (info); } g_object_unref (location); g_free (uri); g_free (capacity); g_free (used); g_free (free); g_signal_connect (window->pie_chart, "draw", G_CALLBACK (paint_pie_chart), window); g_signal_connect (window->used_color, "draw", G_CALLBACK (paint_legend), window); g_signal_connect (window->free_color, "draw", G_CALLBACK (paint_legend), window); } static void setup_volume_usage_widget (NautilusPropertiesWindow *window) { gchar *uri; NautilusFile *file; GFile *location; GFileInfo *info; file = get_original_file (window); uri = nautilus_file_get_activation_uri (file); location = g_file_new_for_uri (uri); info = g_file_query_filesystem_info (location, "filesystem::*", NULL, NULL); if (info) { window->volume_capacity = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE); window->volume_free = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED)) { window->volume_used = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED); } else { window->volume_used = window->volume_capacity - window->volume_free; } g_object_unref (info); } else { window->volume_capacity = 0; window->volume_free = 0; window->volume_used = 0; } g_object_unref (location); if (window->volume_capacity > 0) { setup_pie_widget (window); } } static void open_in_disks (GtkButton *button, NautilusPropertiesWindow *self) { g_autofree char *message = NULL; g_autoptr (GError) error = NULL; g_autoptr (GAppInfo) app_info = NULL; app_info = g_app_info_create_from_commandline ("gnome-disks", NULL, G_APP_INFO_CREATE_NONE, NULL); g_app_info_launch (app_info, NULL, NULL, &error); if (error != NULL) { message = g_strdup_printf (_("Details: %s"), error->message); show_dialog (_("There was an error launching the application."), message, GTK_WINDOW (self), GTK_MESSAGE_ERROR); } } static void setup_basic_page (NautilusPropertiesWindow *window) { GtkGrid *grid; /* Icon pixmap */ setup_image_widget (window, should_show_custom_icon_buttons (window)); window->icon_chooser = NULL; /* Grid */ grid = window->basic_grid; update_name_field (window); g_signal_connect_object (window->name_field, "notify::has-focus", G_CALLBACK (name_field_focus_changed), window, 0); g_signal_connect_object (window->name_field, "activate", G_CALLBACK (name_field_activate), window, 0); /* Start with name field selected, if it's an entry. */ if (GTK_IS_ENTRY (gtk_stack_get_visible_child (window->name_stack))) { gtk_widget_grab_focus (GTK_WIDGET (window->name_field)); } if (should_show_file_type (window)) { gtk_widget_show (window->type_title_label); gtk_widget_show (window->type_value_label); g_object_set_data_full (G_OBJECT (window->type_value_label), "file_attribute", g_strdup ("detailed_type"), g_free); window->value_fields = g_list_prepend (window->value_fields, window->type_value_label); } if (should_show_link_target (window)) { gtk_widget_show (window->link_target_title_label); gtk_widget_show (window->link_target_value_label); g_object_set_data_full (G_OBJECT (window->link_target_value_label), "file_attribute", g_strdup ("link_target"), g_free); window->value_fields = g_list_prepend (window->value_fields, window->link_target_value_label); } if (is_multi_file_window (window) || nautilus_file_is_directory (get_target_file (window))) { gtk_widget_show (window->contents_title_label); gtk_widget_show (window->contents_value_label); setup_contents_field (window, grid); } else { gtk_widget_show (window->size_title_label); gtk_widget_show (window->size_value_label); /* Stash a copy of the file attribute name in this field for the callback's sake. */ g_object_set_data_full (G_OBJECT (window->size_value_label), "file_attribute", g_strdup ("size_detail"), g_free); window->value_fields = g_list_prepend (window->value_fields, window->size_value_label); } if (should_show_location_info (window)) { gtk_widget_show (window->parent_folder_title_label); gtk_widget_show (window->parent_folder_value_label); g_object_set_data_full (G_OBJECT (window->parent_folder_value_label), "file_attribute", g_strdup ("where"), g_free); window->value_fields = g_list_prepend (window->value_fields, window->parent_folder_value_label); } if (should_show_trash_orig_path (window)) { gtk_widget_show (window->original_folder_title_label); gtk_widget_show (window->original_folder_value_label); g_object_set_data_full (G_OBJECT (window->original_folder_value_label), "file_attribute", g_strdup ("trash_orig_path"), g_free); window->value_fields = g_list_prepend (window->value_fields, window->original_folder_value_label); } if (should_show_volume_info (window)) { gtk_widget_show (window->volume_title_label); gtk_widget_show (window->volume_value_label); g_object_set_data_full (G_OBJECT (window->volume_value_label), "file_attribute", g_strdup ("volume"), g_free); window->value_fields = g_list_prepend (window->value_fields, window->volume_value_label); } if (should_show_trashed_on (window)) { gtk_widget_show (window->trashed_on_title_label); gtk_widget_show (window->trashed_on_value_label); g_object_set_data_full (G_OBJECT (window->trashed_on_value_label), "file_attribute", g_strdup ("trashed_on_full"), g_free); window->value_fields = g_list_prepend (window->value_fields, window->trashed_on_value_label); } if (should_show_accessed_date (window) || should_show_modified_date (window)) { gtk_widget_show (window->spacer_2); } if (should_show_accessed_date (window)) { gtk_widget_show (window->accessed_title_label); gtk_widget_show (window->accessed_value_label); /* Stash a copy of the file attribute name in this field for the callback's sake. */ g_object_set_data_full (G_OBJECT (window->accessed_value_label), "file_attribute", g_strdup ("date_accessed_full"), g_free); window->value_fields = g_list_prepend (window->value_fields, window->accessed_value_label); } if (should_show_modified_date (window)) { gtk_widget_show (window->modified_title_label); gtk_widget_show (window->modified_value_label); /* Stash a copy of the file attribute name in this field for the callback's sake. */ g_object_set_data_full (G_OBJECT (window->modified_value_label), "file_attribute", g_strdup ("date_modified_full"), g_free); window->value_fields = g_list_prepend (window->value_fields, window->modified_value_label); } if (should_show_free_space (window) && !should_show_volume_usage (window)) { gtk_widget_show (window->spacer_3); gtk_widget_show (window->free_space_title_label); gtk_widget_show (window->free_space_value_label); /* Stash a copy of the file attribute name in this field for the callback's sake. */ g_object_set_data_full (G_OBJECT (window->free_space_value_label), "file_attribute", g_strdup ("free_space"), g_free); window->value_fields = g_list_prepend (window->value_fields, window->free_space_value_label); } if (should_show_volume_usage (window)) { gtk_widget_show (window->volume_widget_box); gtk_widget_show (window->open_in_disks_button); setup_volume_usage_widget (window); /*Translators: Here Disks mean the name of application GNOME Disks.*/ g_signal_connect (window->open_in_disks_button, "clicked", G_CALLBACK (open_in_disks), NULL); } } static gboolean files_has_directory (NautilusPropertiesWindow *window) { GList *l; for (l = window->target_files; l != NULL; l = l->next) { NautilusFile *file; file = NAUTILUS_FILE (l->data); if (nautilus_file_is_directory (file)) { return TRUE; } } return FALSE; } static gboolean files_has_changable_permissions_directory (NautilusPropertiesWindow *window) { GList *l; gboolean changable = FALSE; for (l = window->target_files; l != NULL; l = l->next) { NautilusFile *file; file = NAUTILUS_FILE (l->data); if (nautilus_file_is_directory (file) && nautilus_file_can_get_permissions (file) && nautilus_file_can_set_permissions (file)) { changable = TRUE; } else { changable = FALSE; break; } } return changable; } static gboolean files_has_file (NautilusPropertiesWindow *window) { GList *l; for (l = window->target_files; l != NULL; l = l->next) { NautilusFile *file; file = NAUTILUS_FILE (l->data); if (!nautilus_file_is_directory (file)) { return TRUE; } } return FALSE; } static void start_long_operation (NautilusPropertiesWindow *window) { if (window->long_operation_underway == 0) { /* start long operation */ GdkDisplay *display; GdkCursor *cursor; display = gtk_widget_get_display (GTK_WIDGET (window)); cursor = gdk_cursor_new_for_display (display, GDK_WATCH); gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), cursor); g_object_unref (cursor); } window->long_operation_underway++; } static void end_long_operation (NautilusPropertiesWindow *window) { if (gtk_widget_get_window (GTK_WIDGET (window)) != NULL && window->long_operation_underway == 1) { /* finished !! */ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), NULL); } window->long_operation_underway--; } static void permission_change_callback (NautilusFile *file, GFile *res_loc, GError *error, gpointer callback_data) { NautilusPropertiesWindow *window; g_assert (callback_data != NULL); window = NAUTILUS_PROPERTIES_WINDOW (callback_data); end_long_operation (window); /* Report the error if it's an error. */ nautilus_report_error_setting_permissions (file, error, GTK_WINDOW (window)); g_object_unref (window); } static void update_permissions (NautilusPropertiesWindow *window, guint32 vfs_new_perm, guint32 vfs_mask, gboolean is_folder, gboolean apply_to_both_folder_and_dir, gboolean use_original) { GList *l; for (l = window->target_files; l != NULL; l = l->next) { NautilusFile *file; guint32 permissions; file = NAUTILUS_FILE (l->data); if (!nautilus_file_can_get_permissions (file)) { continue; } if (!apply_to_both_folder_and_dir && ((nautilus_file_is_directory (file) && !is_folder) || (!nautilus_file_is_directory (file) && is_folder))) { continue; } permissions = nautilus_file_get_permissions (file); if (use_original) { gpointer ptr; if (g_hash_table_lookup_extended (window->initial_permissions, file, NULL, &ptr)) { permissions = (permissions & ~vfs_mask) | (GPOINTER_TO_INT (ptr) & vfs_mask); } } else { permissions = (permissions & ~vfs_mask) | vfs_new_perm; } start_long_operation (window); g_object_ref (window); nautilus_file_set_permissions (file, permissions, permission_change_callback, window); } } static gboolean initial_permission_state_consistent (NautilusPropertiesWindow *window, guint32 mask, gboolean is_folder, gboolean both_folder_and_dir) { GList *l; gboolean first; guint32 first_permissions; first = TRUE; first_permissions = 0; for (l = window->target_files; l != NULL; l = l->next) { NautilusFile *file; guint32 permissions; file = l->data; if (!both_folder_and_dir && ((nautilus_file_is_directory (file) && !is_folder) || (!nautilus_file_is_directory (file) && is_folder))) { continue; } permissions = GPOINTER_TO_INT (g_hash_table_lookup (window->initial_permissions, file)); if (first) { if ((permissions & mask) != mask && (permissions & mask) != 0) { /* Not fully on or off -> inconsistent */ return FALSE; } first_permissions = permissions; first = FALSE; } else if ((permissions & mask) != (first_permissions & mask)) { /* Not same permissions as first -> inconsistent */ return FALSE; } } return TRUE; } static void permission_button_toggled (GtkToggleButton *button, NautilusPropertiesWindow *window) { gboolean is_folder, is_special; guint32 permission_mask; gboolean inconsistent; gboolean on; permission_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "permission")); is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "is-folder")); is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "is-special")); if (gtk_toggle_button_get_active (button) && !gtk_toggle_button_get_inconsistent (button)) { /* Go to the initial state unless the initial state was * consistent, or we support recursive apply */ inconsistent = TRUE; on = TRUE; if (initial_permission_state_consistent (window, permission_mask, is_folder, is_special)) { inconsistent = FALSE; on = TRUE; } } else if (gtk_toggle_button_get_inconsistent (button) && !gtk_toggle_button_get_active (button)) { inconsistent = FALSE; on = TRUE; } else { inconsistent = FALSE; on = FALSE; } g_signal_handlers_block_by_func (G_OBJECT (button), G_CALLBACK (permission_button_toggled), window); gtk_toggle_button_set_active (button, on); gtk_toggle_button_set_inconsistent (button, inconsistent); g_signal_handlers_unblock_by_func (G_OBJECT (button), G_CALLBACK (permission_button_toggled), window); update_permissions (window, on ? permission_mask : 0, permission_mask, is_folder, is_special, inconsistent); } static void permission_button_update (NautilusPropertiesWindow *window, GtkToggleButton *button) { GList *l; gboolean all_set; gboolean all_unset; gboolean all_cannot_set; gboolean is_folder, is_special; gboolean no_match; gboolean sensitive; guint32 button_permission; button_permission = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "permission")); is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "is-folder")); is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "is-special")); all_set = TRUE; all_unset = TRUE; all_cannot_set = TRUE; no_match = TRUE; for (l = window->target_files; l != NULL; l = l->next) { NautilusFile *file; guint32 file_permissions; file = NAUTILUS_FILE (l->data); if (!nautilus_file_can_get_permissions (file)) { continue; } if (!is_special && ((nautilus_file_is_directory (file) && !is_folder) || (!nautilus_file_is_directory (file) && is_folder))) { continue; } no_match = FALSE; file_permissions = nautilus_file_get_permissions (file); if ((file_permissions & button_permission) == button_permission) { all_unset = FALSE; } else if ((file_permissions & button_permission) == 0) { all_set = FALSE; } else { all_unset = FALSE; all_set = FALSE; } if (nautilus_file_can_set_permissions (file)) { all_cannot_set = FALSE; } } sensitive = !all_cannot_set; g_signal_handlers_block_by_func (G_OBJECT (button), G_CALLBACK (permission_button_toggled), window); gtk_toggle_button_set_active (button, !all_unset); /* if actually inconsistent, or default value for file buttons * if no files are selected. (useful for recursive apply) */ gtk_toggle_button_set_inconsistent (button, (!all_unset && !all_set) || (!is_folder && no_match)); gtk_widget_set_sensitive (GTK_WIDGET (button), sensitive); g_signal_handlers_unblock_by_func (G_OBJECT (button), G_CALLBACK (permission_button_toggled), window); } static void setup_execute_checkbox_with_label (NautilusPropertiesWindow *window, guint32 permission_to_check) { gboolean a11y_enabled; GtkLabel *label_for; label_for = GTK_LABEL (window->execute_label); gtk_widget_show (window->execute_label); gtk_widget_show (window->execute_checkbox); /* Load up the check_button with data we'll need when updating its state. */ g_object_set_data (G_OBJECT (window->execute_checkbox), "permission", GINT_TO_POINTER (permission_to_check)); g_object_set_data (G_OBJECT (window->execute_checkbox), "properties_window", window); g_object_set_data (G_OBJECT (window->execute_checkbox), "is-folder", GINT_TO_POINTER (FALSE)); window->permission_buttons = g_list_prepend (window->permission_buttons, window->execute_checkbox); g_signal_connect_object (window->execute_checkbox, "toggled", G_CALLBACK (permission_button_toggled), window, 0); a11y_enabled = GTK_IS_ACCESSIBLE (gtk_widget_get_accessible (window->execute_checkbox)); if (a11y_enabled && label_for != NULL) { AtkObject *atk_widget; AtkObject *atk_label; atk_label = gtk_widget_get_accessible (GTK_WIDGET (label_for)); atk_widget = gtk_widget_get_accessible (window->execute_checkbox); /* Create the label -> widget relation */ atk_object_add_relationship (atk_label, ATK_RELATION_LABEL_FOR, atk_widget); /* Create the widget -> label relation */ atk_object_add_relationship (atk_widget, ATK_RELATION_LABELLED_BY, atk_label); } } enum { UNIX_PERM_SUID = S_ISUID, UNIX_PERM_SGID = S_ISGID, UNIX_PERM_STICKY = 01000, /* S_ISVTX not defined on all systems */ UNIX_PERM_USER_READ = S_IRUSR, UNIX_PERM_USER_WRITE = S_IWUSR, UNIX_PERM_USER_EXEC = S_IXUSR, UNIX_PERM_USER_ALL = S_IRUSR | S_IWUSR | S_IXUSR, UNIX_PERM_GROUP_READ = S_IRGRP, UNIX_PERM_GROUP_WRITE = S_IWGRP, UNIX_PERM_GROUP_EXEC = S_IXGRP, UNIX_PERM_GROUP_ALL = S_IRGRP | S_IWGRP | S_IXGRP, UNIX_PERM_OTHER_READ = S_IROTH, UNIX_PERM_OTHER_WRITE = S_IWOTH, UNIX_PERM_OTHER_EXEC = S_IXOTH, UNIX_PERM_OTHER_ALL = S_IROTH | S_IWOTH | S_IXOTH }; typedef enum { PERMISSION_READ = (1 << 0), PERMISSION_WRITE = (1 << 1), PERMISSION_EXEC = (1 << 2) } PermissionValue; typedef enum { PERMISSION_USER, PERMISSION_GROUP, PERMISSION_OTHER } PermissionType; static guint32 vfs_perms[3][3] = { {UNIX_PERM_USER_READ, UNIX_PERM_USER_WRITE, UNIX_PERM_USER_EXEC}, {UNIX_PERM_GROUP_READ, UNIX_PERM_GROUP_WRITE, UNIX_PERM_GROUP_EXEC}, {UNIX_PERM_OTHER_READ, UNIX_PERM_OTHER_WRITE, UNIX_PERM_OTHER_EXEC}, }; static guint32 permission_to_vfs (PermissionType type, PermissionValue perm) { guint32 vfs_perm; g_assert (type >= 0 && type < 3); vfs_perm = 0; if (perm & PERMISSION_READ) { vfs_perm |= vfs_perms[type][0]; } if (perm & PERMISSION_WRITE) { vfs_perm |= vfs_perms[type][1]; } if (perm & PERMISSION_EXEC) { vfs_perm |= vfs_perms[type][2]; } return vfs_perm; } static PermissionValue permission_from_vfs (PermissionType type, guint32 vfs_perm) { PermissionValue perm; g_assert (type >= 0 && type < 3); perm = 0; if (vfs_perm & vfs_perms[type][0]) { perm |= PERMISSION_READ; } if (vfs_perm & vfs_perms[type][1]) { perm |= PERMISSION_WRITE; } if (vfs_perm & vfs_perms[type][2]) { perm |= PERMISSION_EXEC; } return perm; } static void permission_combo_changed (GtkWidget *combo, NautilusPropertiesWindow *window) { GtkTreeIter iter; GtkTreeModel *model; gboolean is_folder, use_original; PermissionType type; int new_perm, mask; guint32 vfs_new_perm, vfs_mask; is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder")); type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type")); if (is_folder) { mask = PERMISSION_READ | PERMISSION_WRITE | PERMISSION_EXEC; } else { mask = PERMISSION_READ | PERMISSION_WRITE; } vfs_mask = permission_to_vfs (type, mask); model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) { return; } gtk_tree_model_get (model, &iter, COLUMN_VALUE, &new_perm, COLUMN_USE_ORIGINAL, &use_original, -1); vfs_new_perm = permission_to_vfs (type, new_perm); update_permissions (window, vfs_new_perm, vfs_mask, is_folder, FALSE, use_original); } static void permission_combo_add_multiple_choice (GtkComboBox *combo, GtkTreeIter *iter) { GtkTreeModel *model; GtkListStore *store; gboolean found; model = gtk_combo_box_get_model (combo); store = GTK_LIST_STORE (model); found = FALSE; gtk_tree_model_get_iter_first (model, iter); do { gboolean multi; gtk_tree_model_get (model, iter, COLUMN_USE_ORIGINAL, &multi, -1); if (multi) { found = TRUE; break; } } while (gtk_tree_model_iter_next (model, iter)); if (!found) { gtk_list_store_append (store, iter); gtk_list_store_set (store, iter, COLUMN_NAME, "---", COLUMN_VALUE, 0, COLUMN_USE_ORIGINAL, TRUE, -1); } } static void permission_combo_update (NautilusPropertiesWindow *window, GtkComboBox *combo) { PermissionType type; PermissionValue perm, all_dir_perm, all_file_perm, all_perm; gboolean is_folder, no_files, no_dirs, all_file_same, all_dir_same, all_same; gboolean all_dir_cannot_set, all_file_cannot_set, sensitive; GtkTreeIter iter; int mask; GtkTreeModel *model; GtkListStore *store; GList *l; gboolean is_multi; model = gtk_combo_box_get_model (combo); is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder")); type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type")); is_multi = FALSE; if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) { gtk_tree_model_get (model, &iter, COLUMN_USE_ORIGINAL, &is_multi, -1); } no_files = TRUE; no_dirs = TRUE; all_dir_same = TRUE; all_file_same = TRUE; all_dir_perm = 0; all_file_perm = 0; all_dir_cannot_set = TRUE; all_file_cannot_set = TRUE; for (l = window->target_files; l != NULL; l = l->next) { NautilusFile *file; guint32 file_permissions; file = NAUTILUS_FILE (l->data); if (!nautilus_file_can_get_permissions (file)) { continue; } if (nautilus_file_is_directory (file)) { mask = PERMISSION_READ | PERMISSION_WRITE | PERMISSION_EXEC; } else { mask = PERMISSION_READ | PERMISSION_WRITE; } file_permissions = nautilus_file_get_permissions (file); perm = permission_from_vfs (type, file_permissions) & mask; if (nautilus_file_is_directory (file)) { if (no_dirs) { all_dir_perm = perm; no_dirs = FALSE; } else if (perm != all_dir_perm) { all_dir_same = FALSE; } if (nautilus_file_can_set_permissions (file)) { all_dir_cannot_set = FALSE; } } else { if (no_files) { all_file_perm = perm; no_files = FALSE; } else if (perm != all_file_perm) { all_file_same = FALSE; } if (nautilus_file_can_set_permissions (file)) { all_file_cannot_set = FALSE; } } } if (is_folder) { all_same = all_dir_same; all_perm = all_dir_perm; } else { all_same = all_file_same && !no_files; all_perm = all_file_perm; } store = GTK_LIST_STORE (model); if (all_same) { gboolean found; found = FALSE; gtk_tree_model_get_iter_first (model, &iter); do { int current_perm; gtk_tree_model_get (model, &iter, 1, ¤t_perm, -1); if (current_perm == all_perm) { found = TRUE; break; } } while (gtk_tree_model_iter_next (model, &iter)); if (!found) { GString *str; str = g_string_new (""); if (!(all_perm & PERMISSION_READ)) { /* translators: this gets concatenated to "no read", * "no access", etc. (see following strings) */ g_string_append (str, _("no ")); } if (is_folder) { g_string_append (str, _("list")); } else { g_string_append (str, _("read")); } g_string_append (str, ", "); if (!(all_perm & PERMISSION_WRITE)) { g_string_append (str, _("no ")); } if (is_folder) { g_string_append (str, _("create/delete")); } else { g_string_append (str, _("write")); } if (is_folder) { g_string_append (str, ", "); if (!(all_perm & PERMISSION_EXEC)) { g_string_append (str, _("no ")); } g_string_append (str, _("access")); } gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, str->str, 1, all_perm, -1); g_string_free (str, TRUE); } } else { permission_combo_add_multiple_choice (combo, &iter); } g_signal_handlers_block_by_func (G_OBJECT (combo), G_CALLBACK (permission_combo_changed), window); gtk_combo_box_set_active_iter (combo, &iter); /* Also enable if no files found (for recursive * file changes when only selecting folders) */ if (is_folder) { sensitive = !all_dir_cannot_set; } else { sensitive = !all_file_cannot_set; } gtk_widget_set_sensitive (GTK_WIDGET (combo), sensitive); g_signal_handlers_unblock_by_func (G_OBJECT (combo), G_CALLBACK (permission_combo_changed), window); } static void setup_permissions_combo_box (GtkComboBox *combo, PermissionType type, gboolean is_folder) { GtkListStore *store; GtkCellRenderer *cell; GtkTreeIter iter; store = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_STRING); gtk_combo_box_set_model (combo, GTK_TREE_MODEL (store)); gtk_combo_box_set_id_column (GTK_COMBO_BOX (combo), COLUMN_ID); g_object_set_data (G_OBJECT (combo), "is-folder", GINT_TO_POINTER (is_folder)); g_object_set_data (G_OBJECT (combo), "permission-type", GINT_TO_POINTER (type)); if (is_folder) { if (type != PERMISSION_USER) { gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, /* Translators: this is referred to the permissions * the user has in a directory. */ COLUMN_NAME, _("None"), COLUMN_VALUE, 0, COLUMN_ID, "none", -1); } gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COLUMN_NAME, _("List files only"), COLUMN_VALUE, PERMISSION_READ, COLUMN_ID, "r", -1); gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COLUMN_NAME, _("Access files"), COLUMN_VALUE, PERMISSION_READ | PERMISSION_EXEC, COLUMN_ID, "rx", -1); gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COLUMN_NAME, _("Create and delete files"), COLUMN_VALUE, PERMISSION_READ | PERMISSION_EXEC | PERMISSION_WRITE, COLUMN_ID, "rwx", -1); } else { if (type != PERMISSION_USER) { gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COLUMN_NAME, _("None"), COLUMN_VALUE, 0, COLUMN_ID, "none", -1); } gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COLUMN_NAME, _("Read-only"), COLUMN_VALUE, PERMISSION_READ, COLUMN_ID, "r", -1); gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COLUMN_NAME, _("Read and write"), COLUMN_VALUE, PERMISSION_READ | PERMISSION_WRITE, COLUMN_ID, "rw", -1); } g_object_unref (store); cell = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE); gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell, "text", COLUMN_NAME, NULL); } 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) { guint32 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_simple_permissions (NautilusPropertiesWindow *window, GtkGrid *page_grid) { gboolean has_directory; gboolean has_file; GtkWidget *owner_combo_box; GtkWidget *owner_value_label; GtkWidget *group_combo_box; GtkWidget *group_value_label; has_directory = files_has_directory (window); has_file = files_has_file (window); if (!is_multi_file_window (window) && nautilus_file_can_set_owner (get_target_file (window))) { /* Combo box in this case. */ owner_combo_box = gtk_stack_get_child_by_name (GTK_STACK (window->owner_value_stack), "combo_box"); gtk_stack_set_visible_child (GTK_STACK (window->owner_value_stack), owner_combo_box); setup_owner_combo_box (owner_combo_box, get_target_file (window)); } else { /* Static text in this case. */ owner_value_label = gtk_stack_get_child_by_name (GTK_STACK (window->owner_value_stack), "label"); gtk_stack_set_visible_child (GTK_STACK (window->owner_value_stack), owner_value_label); /* Stash a copy of the file attribute name in this field for the callback's sake. */ g_object_set_data_full (G_OBJECT (owner_value_label), "file_attribute", g_strdup ("owner"), g_free); window->value_fields = g_list_prepend (window->value_fields, owner_value_label); } if (has_directory && has_file) { gtk_widget_show (window->owner_folder_access_label); gtk_widget_show (window->owner_folder_access_combo); setup_permissions_combo_box (GTK_COMBO_BOX (window->owner_folder_access_combo), PERMISSION_USER, TRUE); window->permission_combos = g_list_prepend (window->permission_combos, window->owner_folder_access_combo); g_signal_connect (window->owner_folder_access_combo, "changed", G_CALLBACK (permission_combo_changed), window); gtk_widget_show (window->owner_file_access_label); gtk_widget_show (window->owner_file_access_combo); setup_permissions_combo_box (GTK_COMBO_BOX (window->owner_file_access_combo), PERMISSION_USER, FALSE); window->permission_combos = g_list_prepend (window->permission_combos, window->owner_file_access_combo); g_signal_connect (window->owner_file_access_combo, "changed", G_CALLBACK (permission_combo_changed), window); } else { gtk_widget_show (window->owner_access_label); gtk_widget_show (window->owner_access_combo); setup_permissions_combo_box (GTK_COMBO_BOX (window->owner_access_combo), PERMISSION_USER, has_directory); window->permission_combos = g_list_prepend (window->permission_combos, window->owner_access_combo); g_signal_connect (window->owner_access_combo, "changed", G_CALLBACK (permission_combo_changed), window); } if (!is_multi_file_window (window) && nautilus_file_can_set_group (get_target_file (window))) { /* Combo box in this case. */ group_combo_box = gtk_stack_get_child_by_name (GTK_STACK (window->group_value_stack), "combo_box"); gtk_stack_set_visible_child (GTK_STACK (window->group_value_stack), group_combo_box); setup_group_combo_box (group_combo_box, get_target_file (window)); } else { group_value_label = gtk_stack_get_child_by_name (GTK_STACK (window->group_value_stack), "label"); gtk_stack_set_visible_child (GTK_STACK (window->group_value_stack), group_value_label); /* Stash a copy of the file attribute name in this field for the callback's sake. */ g_object_set_data_full (G_OBJECT (group_value_label), "file_attribute", g_strdup ("group"), g_free); window->value_fields = g_list_prepend (window->value_fields, group_value_label); } if (has_directory && has_file) { gtk_widget_show (window->group_folder_access_label); gtk_widget_show (window->group_folder_access_combo); setup_permissions_combo_box (GTK_COMBO_BOX (window->group_folder_access_combo), PERMISSION_GROUP, TRUE); window->permission_combos = g_list_prepend (window->permission_combos, window->group_folder_access_combo); g_signal_connect (window->group_folder_access_combo, "changed", G_CALLBACK (permission_combo_changed), window); gtk_widget_show (window->group_file_access_label); gtk_widget_show (window->group_file_access_combo); setup_permissions_combo_box (GTK_COMBO_BOX (window->group_file_access_combo), PERMISSION_GROUP, FALSE); window->permission_combos = g_list_prepend (window->permission_combos, window->group_file_access_combo); g_signal_connect (window->group_file_access_combo, "changed", G_CALLBACK (permission_combo_changed), window); } else { gtk_widget_show (window->group_access_label); gtk_widget_show (window->group_access_combo); setup_permissions_combo_box (GTK_COMBO_BOX (window->group_access_combo), PERMISSION_GROUP, has_directory); window->permission_combos = g_list_prepend (window->permission_combos, window->group_access_combo); g_signal_connect (window->group_access_combo, "changed", G_CALLBACK (permission_combo_changed), window); } /* Others Row */ if (has_directory && has_file) { gtk_widget_show (window->others_folder_access_label); gtk_widget_show (window->others_folder_access_combo); setup_permissions_combo_box (GTK_COMBO_BOX (window->others_folder_access_combo), PERMISSION_OTHER, TRUE); window->permission_combos = g_list_prepend (window->permission_combos, window->others_folder_access_combo); g_signal_connect (window->others_folder_access_combo, "changed", G_CALLBACK (permission_combo_changed), window); gtk_widget_show (window->others_file_access_label); gtk_widget_show (window->others_file_access_combo); setup_permissions_combo_box (GTK_COMBO_BOX (window->others_file_access_combo), PERMISSION_OTHER, FALSE); window->permission_combos = g_list_prepend (window->permission_combos, window->others_file_access_combo); g_signal_connect (window->others_file_access_combo, "changed", G_CALLBACK (permission_combo_changed), window); } else { gtk_widget_show (window->others_access_label); gtk_widget_show (window->others_access_combo); setup_permissions_combo_box (GTK_COMBO_BOX (window->others_access_combo), PERMISSION_OTHER, has_directory); window->permission_combos = g_list_prepend (window->permission_combos, window->others_access_combo); g_signal_connect (window->others_access_combo, "changed", G_CALLBACK (permission_combo_changed), window); } if (!has_directory) { setup_execute_checkbox_with_label (window, UNIX_PERM_USER_EXEC | UNIX_PERM_GROUP_EXEC | UNIX_PERM_OTHER_EXEC); } } static void set_recursive_permissions_done (gboolean success, gpointer callback_data) { NautilusPropertiesWindow *window; window = NAUTILUS_PROPERTIES_WINDOW (callback_data); end_long_operation (window); g_object_unref (window); } static void on_change_permissions_response (GtkDialog *dialog, int response, NautilusPropertiesWindow *window) { guint32 file_permission, file_permission_mask; guint32 dir_permission, dir_permission_mask; guint32 vfs_mask, vfs_new_perm; GtkWidget *combo; gboolean is_folder, use_original; GList *l; GtkTreeModel *model; GtkTreeIter iter; PermissionType type; int new_perm, mask; if (response != GTK_RESPONSE_OK) { g_clear_pointer (&window->change_permission_combos, g_list_free); gtk_widget_destroy (GTK_WIDGET (dialog)); return; } file_permission = 0; file_permission_mask = 0; dir_permission = 0; dir_permission_mask = 0; /* Simple mode, minus exec checkbox */ for (l = window->change_permission_combos; l != NULL; l = l->next) { combo = l->data; if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) { continue; } type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type")); is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder")); model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); gtk_tree_model_get (model, &iter, COLUMN_VALUE, &new_perm, COLUMN_USE_ORIGINAL, &use_original, -1); if (use_original) { continue; } vfs_new_perm = permission_to_vfs (type, new_perm); if (is_folder) { mask = PERMISSION_READ | PERMISSION_WRITE | PERMISSION_EXEC; } else { mask = PERMISSION_READ | PERMISSION_WRITE; } vfs_mask = permission_to_vfs (type, mask); if (is_folder) { dir_permission_mask |= vfs_mask; dir_permission |= vfs_new_perm; } else { file_permission_mask |= vfs_mask; file_permission |= vfs_new_perm; } } for (l = window->target_files; l != NULL; l = l->next) { NautilusFile *file; char *uri; file = NAUTILUS_FILE (l->data); if (nautilus_file_is_directory (file) && nautilus_file_can_set_permissions (file)) { uri = nautilus_file_get_uri (file); start_long_operation (window); g_object_ref (window); nautilus_file_set_permissions_recursive (uri, file_permission, file_permission_mask, dir_permission, dir_permission_mask, set_recursive_permissions_done, window); g_free (uri); } } g_clear_pointer (&window->change_permission_combos, g_list_free); gtk_widget_destroy (GTK_WIDGET (dialog)); } static void set_active_from_umask (GtkComboBox *combo, PermissionType type, gboolean is_folder) { mode_t initial; mode_t mask; mode_t p; const char *id; if (is_folder) { initial = (S_IRWXU | S_IRWXG | S_IRWXO); } else { initial = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); } umask (mask = umask (0)); p = ~mask & initial; if (type == PERMISSION_USER) { p &= ~(S_IRWXG | S_IRWXO); if ((p & S_IRWXU) == S_IRWXU) { id = "rwx"; } else if ((p & (S_IRUSR | S_IWUSR)) == (S_IRUSR | S_IWUSR)) { id = "rw"; } else if ((p & (S_IRUSR | S_IXUSR)) == (S_IRUSR | S_IXUSR)) { id = "rx"; } else if ((p & S_IRUSR) == S_IRUSR) { id = "r"; } else { id = "none"; } } else if (type == PERMISSION_GROUP) { p &= ~(S_IRWXU | S_IRWXO); if ((p & S_IRWXG) == S_IRWXG) { id = "rwx"; } else if ((p & (S_IRGRP | S_IWGRP)) == (S_IRGRP | S_IWGRP)) { id = "rw"; } else if ((p & (S_IRGRP | S_IXGRP)) == (S_IRGRP | S_IXGRP)) { id = "rx"; } else if ((p & S_IRGRP) == S_IRGRP) { id = "r"; } else { id = "none"; } } else { p &= ~(S_IRWXU | S_IRWXG); if ((p & S_IRWXO) == S_IRWXO) { id = "rwx"; } else if ((p & (S_IROTH | S_IWOTH)) == (S_IROTH | S_IWOTH)) { id = "rw"; } else if ((p & (S_IROTH | S_IXOTH)) == (S_IROTH | S_IXOTH)) { id = "rx"; } else if ((p & S_IROTH) == S_IROTH) { id = "r"; } else { id = "none"; } } gtk_combo_box_set_active_id (combo, id); } static void on_change_permissions_clicked (GtkWidget *button, NautilusPropertiesWindow *window) { GtkWidget *dialog; GtkComboBox *combo; GtkBuilder *change_permissions_builder; change_permissions_builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-file-properties-change-permissions.ui"); dialog = GTK_WIDGET (gtk_builder_get_object (change_permissions_builder, "change_permissions_dialog")); gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (window)); /* Owner Permissions */ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "file_owner_combo")); setup_permissions_combo_box (combo, PERMISSION_USER, FALSE); window->change_permission_combos = g_list_prepend (window->change_permission_combos, combo); set_active_from_umask (combo, PERMISSION_USER, FALSE); combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "folder_owner_combo")); setup_permissions_combo_box (combo, PERMISSION_USER, TRUE); window->change_permission_combos = g_list_prepend (window->change_permission_combos, combo); set_active_from_umask (combo, PERMISSION_USER, TRUE); /* Group Permissions */ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "file_group_combo")); setup_permissions_combo_box (combo, PERMISSION_GROUP, FALSE); window->change_permission_combos = g_list_prepend (window->change_permission_combos, combo); set_active_from_umask (combo, PERMISSION_GROUP, FALSE); combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "folder_group_combo")); setup_permissions_combo_box (combo, PERMISSION_GROUP, TRUE); window->change_permission_combos = g_list_prepend (window->change_permission_combos, combo); set_active_from_umask (combo, PERMISSION_GROUP, TRUE); /* Others Permissions */ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "file_other_combo")); setup_permissions_combo_box (combo, PERMISSION_OTHER, FALSE); window->change_permission_combos = g_list_prepend (window->change_permission_combos, combo); set_active_from_umask (combo, PERMISSION_OTHER, FALSE); combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "folder_other_combo")); setup_permissions_combo_box (combo, PERMISSION_OTHER, TRUE); window->change_permission_combos = g_list_prepend (window->change_permission_combos, combo); set_active_from_umask (combo, PERMISSION_OTHER, TRUE); g_signal_connect (dialog, "response", G_CALLBACK (on_change_permissions_response), window); gtk_widget_show_all (dialog); g_object_unref (change_permissions_builder); } static void setup_permissions_page (NautilusPropertiesWindow *window) { char *file_name, *prompt_text; GList *file_list; file_list = window->original_files; window->initial_permissions = NULL; if (all_can_get_permissions (file_list) && all_can_get_permissions (window->target_files)) { window->initial_permissions = get_initial_permissions (window->target_files); window->has_recursive_apply = files_has_changable_permissions_directory (window); if (!all_can_set_permissions (file_list)) { gtk_widget_show (window->not_the_owner_label); gtk_widget_show (window->bottom_prompt_seperator); } gtk_widget_show (window->permissions_grid); create_simple_permissions (window, GTK_GRID (window->permissions_grid)); #ifdef HAVE_SELINUX gtk_widget_show (window->security_context_title_label); gtk_widget_show (window->security_context_value_label); /* Stash a copy of the file attribute name in this field for the callback's sake. */ g_object_set_data_full (G_OBJECT (window->security_context_value_label), "file_attribute", g_strdup ("selinux_context"), g_free); window->value_fields = g_list_prepend (window->value_fields, window->security_context_value_label); #endif if (window->has_recursive_apply) { gtk_widget_show_all (window->change_permissions_button_box); g_signal_connect (window->change_permissions_button, "clicked", G_CALLBACK (on_change_permissions_clicked), window); } } else { /* * This if block only gets executed if its a single file window, * in which case the label text needs to be different from the * default label text. The default label text for a multifile * window is set in nautilus-properties-window.ui so no else block. */ 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); gtk_label_set_text (GTK_LABEL (window->permission_indeterminable_label), prompt_text); g_free (file_name); g_free (prompt_text); } gtk_widget_show (window->permission_indeterminable_label); } } static void append_extension_pages (NautilusPropertiesWindow *window) { GList *providers; GList *p; providers = nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_PROPERTY_PAGE_PROVIDER); for (p = providers; p != NULL; p = p->next) { NautilusPropertyPageProvider *provider; GList *pages; GList *l; provider = NAUTILUS_PROPERTY_PAGE_PROVIDER (p->data); pages = nautilus_property_page_provider_get_pages (provider, window->original_files); for (l = pages; l != NULL; l = l->next) { NautilusPropertyPage *page; GtkWidget *page_widget; GtkWidget *label; page = NAUTILUS_PROPERTY_PAGE (l->data); g_object_get (G_OBJECT (page), "page", &page_widget, "label", &label, NULL); gtk_notebook_append_page (window->notebook, page_widget, label); gtk_container_child_set (GTK_CONTAINER (window->notebook), page_widget, "tab-expand", TRUE, NULL); g_object_set_data (G_OBJECT (page_widget), "is-extension-page", page); g_object_unref (page_widget); g_object_unref (label); g_object_unref (page); } g_list_free (pages); } nautilus_module_extension_list_free (providers); } static gboolean should_show_permissions (NautilusPropertiesWindow *window) { GList *l; /* Don't show permissions for Trash and Computer since they're not * really file system objects. */ for (l = window->original_files; l != NULL; l = l->next) { if (nautilus_file_is_in_trash (NAUTILUS_FILE (l->data)) || nautilus_file_is_in_recent (NAUTILUS_FILE (l->data))) { 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, ";"); } g_list_free_full (uris, g_free); ret = key->str; g_string_free (key, FALSE); return ret; } static StartupData * startup_data_new (GList *original_files, GList *target_files, const char *pending_key, GtkWidget *parent_widget, GtkWindow *parent_window, const char *startup_id, NautilusPropertiesWindowCallback callback, gpointer callback_data, NautilusPropertiesWindow *window) { StartupData *data; GList *l; data = g_new0 (StartupData, 1); data->original_files = nautilus_file_list_copy (original_files); data->target_files = nautilus_file_list_copy (target_files); data->parent_widget = parent_widget; data->parent_window = parent_window; data->startup_id = g_strdup (startup_id); data->pending_key = g_strdup (pending_key); data->pending_files = g_hash_table_new (g_direct_hash, g_direct_equal); data->callback = callback; data->callback_data = callback_data; data->window = window; for (l = data->target_files; l != NULL; l = l->next) { g_hash_table_insert (data->pending_files, l->data, l->data); } return data; } static void startup_data_free (StartupData *data) { 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->startup_id); g_free (data); } static void file_changed_callback (NautilusFile *file, gpointer user_data) { NautilusPropertiesWindow *window = NAUTILUS_PROPERTIES_WINDOW (user_data); if (!g_list_find (window->changed_files, file)) { nautilus_file_ref (file); window->changed_files = g_list_prepend (window->changed_files, file); schedule_files_update (window); } } static gboolean should_show_open_with (NautilusPropertiesWindow *window) { NautilusFile *file; char *mime_type; char *extension; gboolean hide; g_autoptr (GAppInfo) app_info = NULL; /* Don't show open with tab for desktop special icons (trash, etc) * or desktop files. We don't get the open-with menu for these anyway. * * Also don't show it for folders. Changing the default app for folders * leads to all sort of hard to understand errors. */ if (is_multi_file_window (window)) { GList *l; if (!file_list_attributes_identical (window->target_files, "mime_type")) { return FALSE; } for (l = window->target_files; l; l = l->next) { file = NAUTILUS_FILE (l->data); app_info = nautilus_mime_get_default_application_for_file (file); if (nautilus_file_is_directory (file) || !app_info || file == NULL) { return FALSE; } } /* since we just confirmed all the mime types are the * same we only need to test one file */ file = window->target_files->data; } else { file = get_target_file (window); app_info = nautilus_mime_get_default_application_for_file (file); if (nautilus_file_is_directory (file) || !app_info || file == NULL) { return FALSE; } } mime_type = nautilus_file_get_mime_type (file); extension = nautilus_file_get_extension (file); hide = (g_content_type_is_unknown (mime_type) && extension == NULL); g_free (mime_type); g_free (extension); return !hide; } static void add_clicked_cb (GtkButton *button, gpointer user_data) { NautilusPropertiesWindow *window = NAUTILUS_PROPERTIES_WINDOW (user_data); GAppInfo *info; gchar *message; GError *error = NULL; info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (window->app_chooser_widget)); if (info == NULL) { return; } g_app_info_set_as_last_used_for_type (info, window->content_type, &error); if (error != NULL) { message = g_strdup_printf (_("Error while adding “%s”: %s"), g_app_info_get_display_name (info), error->message); show_dialog (_("Could not add application"), message, GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (window))), GTK_MESSAGE_ERROR); g_error_free (error); g_free (message); } else { gtk_app_chooser_refresh (GTK_APP_CHOOSER (window->app_chooser_widget)); g_signal_emit_by_name (nautilus_signaller_get_current (), "mime-data-changed"); } g_object_unref (info); } static void remove_clicked_cb (GtkMenuItem *item, gpointer user_data) { NautilusPropertiesWindow *window = NAUTILUS_PROPERTIES_WINDOW (user_data); GError *error; GAppInfo *info; info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (window->app_chooser_widget)); if (info) { error = NULL; if (!g_app_info_remove_supports_type (info, window->content_type, &error)) { show_dialog (_("Could not forget association"), error->message, GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (window))), GTK_MESSAGE_ERROR); g_error_free (error); } gtk_app_chooser_refresh (GTK_APP_CHOOSER (window->app_chooser_widget)); g_object_unref (info); } g_signal_emit_by_name (nautilus_signaller_get_current (), "mime-data-changed"); } static void populate_popup_cb (GtkAppChooserWidget *widget, GtkMenu *menu, GAppInfo *app, gpointer user_data) { GtkWidget *item; NautilusPropertiesWindow *window = NAUTILUS_PROPERTIES_WINDOW (user_data); if (g_app_info_can_remove_supports_type (app)) { item = gtk_menu_item_new_with_label (_("Forget association")); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); gtk_widget_show (item); g_signal_connect (item, "activate", G_CALLBACK (remove_clicked_cb), window); } } static void reset_clicked_cb (GtkButton *button, gpointer user_data) { NautilusPropertiesWindow *window; window = NAUTILUS_PROPERTIES_WINDOW (user_data); g_app_info_reset_type_associations (window->content_type); gtk_app_chooser_refresh (GTK_APP_CHOOSER (window->app_chooser_widget)); g_signal_emit_by_name (nautilus_signaller_get_current (), "mime-data-changed"); } static void set_as_default_clicked_cb (GtkButton *button, gpointer user_data) { NautilusPropertiesWindow *window = NAUTILUS_PROPERTIES_WINDOW (user_data); GAppInfo *info; GError *error = NULL; gchar *message = NULL; info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (window->app_chooser_widget)); g_app_info_set_as_default_for_type (info, window->content_type, &error); if (error != NULL) { message = g_strdup_printf (_("Error while setting “%s” as default application: %s"), g_app_info_get_display_name (info), error->message); show_dialog (_("Could not set as default"), message, GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (window))), GTK_MESSAGE_ERROR); } g_object_unref (info); gtk_app_chooser_refresh (GTK_APP_CHOOSER (window->app_chooser_widget)); g_signal_emit_by_name (nautilus_signaller_get_current (), "mime-data-changed"); } static gint app_compare (gconstpointer a, gconstpointer b) { return !g_app_info_equal (G_APP_INFO (a), G_APP_INFO (b)); } static gboolean app_info_can_add (GAppInfo *info, const gchar *content_type) { GList *recommended, *fallback; gboolean retval = FALSE; recommended = g_app_info_get_recommended_for_type (content_type); fallback = g_app_info_get_fallback_for_type (content_type); if (g_list_find_custom (recommended, info, app_compare)) { goto out; } if (g_list_find_custom (fallback, info, app_compare)) { goto out; } retval = TRUE; out: g_list_free_full (recommended, g_object_unref); g_list_free_full (fallback, g_object_unref); return retval; } static void application_selected_cb (GtkAppChooserWidget *widget, GAppInfo *info, gpointer user_data) { NautilusPropertiesWindow *window = NAUTILUS_PROPERTIES_WINDOW (user_data); GAppInfo *default_app; default_app = g_app_info_get_default_for_type (window->content_type, FALSE); if (default_app != NULL) { gtk_widget_set_sensitive (window->set_as_default_button, !g_app_info_equal (info, default_app)); g_object_unref (default_app); } gtk_widget_set_sensitive (window->add_button, app_info_can_add (info, window->content_type)); } static void application_chooser_apply_labels (NautilusPropertiesWindow *window) { gchar *label, *extension = NULL, *description = NULL; gint num_files; NautilusFile *file; num_files = g_list_length (window->open_with_files); file = window->open_with_files->data; /* here we assume all files are of the same content type */ if (g_content_type_is_unknown (window->content_type)) { extension = nautilus_file_get_extension (file); /* Translators: the %s here is a file extension */ description = g_strdup_printf (_("%s document"), extension); } else { description = g_content_type_get_description (window->content_type); } if (num_files > 1) { /* Translators; %s here is a mime-type description */ label = g_strdup_printf (_("Open all files of type “%s” with"), description); } else { gchar *display_name; display_name = nautilus_file_get_display_name (file); /* Translators: first %s is filename, second %s is mime-type description */ label = g_strdup_printf (_("Select an application to open “%s” and other files of type “%s”"), display_name, description); g_free (display_name); } gtk_label_set_markup (GTK_LABEL (window->open_with_label), label); g_free (label); g_free (extension); g_free (description); } static void setup_app_chooser_area (NautilusPropertiesWindow *window) { GAppInfo *info; window->app_chooser_widget = gtk_app_chooser_widget_new (window->content_type); gtk_box_pack_start (GTK_BOX (window->app_chooser_widget_box), window->app_chooser_widget, TRUE, TRUE, 0); gtk_app_chooser_widget_set_show_default (GTK_APP_CHOOSER_WIDGET (window->app_chooser_widget), TRUE); gtk_app_chooser_widget_set_show_fallback (GTK_APP_CHOOSER_WIDGET (window->app_chooser_widget), TRUE); gtk_app_chooser_widget_set_show_other (GTK_APP_CHOOSER_WIDGET (window->app_chooser_widget), TRUE); gtk_widget_show (window->app_chooser_widget); g_signal_connect (window->reset_button, "clicked", G_CALLBACK (reset_clicked_cb), window); g_signal_connect (window->add_button, "clicked", G_CALLBACK (add_clicked_cb), window); g_signal_connect (window->set_as_default_button, "clicked", G_CALLBACK (set_as_default_clicked_cb), window); /* initialize sensitivity */ info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (window->app_chooser_widget)); if (info != NULL) { application_selected_cb (GTK_APP_CHOOSER_WIDGET (window->app_chooser_widget), info, window); g_object_unref (info); } g_signal_connect (window->app_chooser_widget, "application-selected", G_CALLBACK (application_selected_cb), window); g_signal_connect (window->app_chooser_widget, "populate-popup", G_CALLBACK (populate_popup_cb), window); application_chooser_apply_labels (window); } static void setup_open_with_page (NautilusPropertiesWindow *window) { GList *files = NULL; NautilusFile *target_file; target_file = get_target_file (window); window->content_type = nautilus_file_get_mime_type (target_file); if (!is_multi_file_window (window)) { files = g_list_prepend (NULL, target_file); } else { files = g_list_copy (window->original_files); if (files == NULL) { return; } } window->open_with_files = files; setup_app_chooser_area (window); } static NautilusPropertiesWindow * create_properties_window (StartupData *startup_data) { NautilusPropertiesWindow *window; GList *l; window = NAUTILUS_PROPERTIES_WINDOW (gtk_widget_new (NAUTILUS_TYPE_PROPERTIES_WINDOW, NULL)); window->original_files = nautilus_file_list_copy (startup_data->original_files); window->target_files = nautilus_file_list_copy (startup_data->target_files); if (startup_data->parent_widget) { gtk_window_set_screen (GTK_WINDOW (window), gtk_widget_get_screen (startup_data->parent_widget)); } if (startup_data->parent_window) { gtk_window_set_transient_for (GTK_WINDOW (window), startup_data->parent_window); } if (startup_data->startup_id) { gtk_window_set_startup_id (GTK_WINDOW (window), startup_data->startup_id); } /* Set initial window title */ 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 files. */ for (l = window->original_files; l != NULL; l = l->next) { NautilusFile *file; NautilusFileAttributes attributes; file = NAUTILUS_FILE (l->data); attributes = NAUTILUS_FILE_ATTRIBUTES_FOR_ICON | NAUTILUS_FILE_ATTRIBUTE_INFO; nautilus_file_monitor_add (file, &window->original_files, attributes); } for (l = window->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_INFO; nautilus_file_monitor_add (file, &window->target_files, attributes); } for (l = window->target_files; l != NULL; l = l->next) { g_signal_connect_object (NAUTILUS_FILE (l->data), "changed", G_CALLBACK (file_changed_callback), G_OBJECT (window), 0); } for (l = window->original_files; l != NULL; l = l->next) { g_signal_connect_object (NAUTILUS_FILE (l->data), "changed", G_CALLBACK (file_changed_callback), G_OBJECT (window), 0); } /* Create the pages. */ setup_basic_page (window); if (should_show_permissions (window)) { setup_permissions_page (window); gtk_widget_show (window->permissions_box); } if (should_show_open_with (window)) { setup_open_with_page (window); gtk_widget_show (window->open_with_box); } /* append pages from available views */ append_extension_pages (window); /* Update from initial state */ properties_window_update (window, NULL); return window; } static GList * get_target_file_list (GList *original_files) { GList *ret; GList *l; ret = NULL; for (l = original_files; l != NULL; l = l->next) { NautilusFile *target; target = get_target_file_for_original_file (NAUTILUS_FILE (l->data)); ret = g_list_prepend (ret, target); } ret = g_list_reverse (ret); return ret; } static void add_window (NautilusPropertiesWindow *window) { 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)); } } static void remove_window (NautilusPropertiesWindow *window) { gpointer key; key = g_object_get_data (G_OBJECT (window), "window_key"); if (key) { g_hash_table_remove (windows, key); } } static NautilusPropertiesWindow * get_existing_window (GList *file_list) { if (!file_list->next) { return g_hash_table_lookup (windows, file_list->data); } return NULL; } static void properties_window_finish (StartupData *data) { gboolean cancel_timed_wait; if (data->parent_widget != NULL) { g_signal_handlers_disconnect_by_data (data->parent_widget, data); } if (data->window != NULL) { g_signal_handlers_disconnect_by_data (data->window, data); } cancel_timed_wait = (data->window == NULL && !data->cancelled); remove_pending (data, TRUE, cancel_timed_wait); startup_data_free (data); } static void cancel_create_properties_window_callback (gpointer callback_data) { StartupData *data; data = callback_data; data->cancelled = TRUE; properties_window_finish (data); } static void parent_widget_destroyed_callback (GtkWidget *widget, gpointer callback_data) { g_assert (widget == ((StartupData *) callback_data)->parent_widget); properties_window_finish ((StartupData *) callback_data); } 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 (StartupData *startup_data, gboolean cancel_call_when_ready, gboolean cancel_timed_wait) { if (cancel_call_when_ready) { g_hash_table_foreach (startup_data->pending_files, cancel_call_when_ready_callback, startup_data); } if (cancel_timed_wait) { eel_timed_wait_stop (cancel_create_properties_window_callback, startup_data); } if (startup_data->pending_key != NULL) { g_hash_table_remove (pending_lists, startup_data->pending_key); } } static gboolean widget_on_destroy (GtkWidget *widget, gpointer user_data) { StartupData *data = (StartupData *) user_data; if (data->callback != NULL) { data->callback (data->callback_data); } properties_window_finish (data); return GDK_EVENT_PROPAGATE; } 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) { NautilusPropertiesWindow *new_window; new_window = create_properties_window (startup_data); add_window (new_window); startup_data->window = new_window; remove_pending (startup_data, FALSE, TRUE); gtk_window_present (GTK_WINDOW (new_window)); g_signal_connect (GTK_WIDGET (new_window), "destroy", G_CALLBACK (widget_on_destroy), startup_data); } } void nautilus_properties_window_present (GList *original_files, GtkWidget *parent_widget, const gchar *startup_id, NautilusPropertiesWindowCallback callback, gpointer callback_data) { GList *l, *next; GtkWindow *parent_window; StartupData *startup_data; GList *target_files; NautilusPropertiesWindow *existing_window; char *pending_key; g_return_if_fail (original_files != NULL); g_return_if_fail (parent_widget == NULL || GTK_IS_WIDGET (parent_widget)); /* Create the hash tables first time through. */ if (windows == NULL) { windows = g_hash_table_new (NULL, NULL); } if (pending_lists == NULL) { pending_lists = g_hash_table_new (g_str_hash, g_str_equal); } /* Look to see if there's already a window for this file. */ existing_window = get_existing_window (original_files); if (existing_window != NULL) { if (parent_widget) { gtk_window_set_screen (GTK_WINDOW (existing_window), gtk_widget_get_screen (parent_widget)); } else if (startup_id) { gtk_window_set_startup_id (GTK_WINDOW (existing_window), startup_id); } gtk_window_present (GTK_WINDOW (existing_window)); startup_data = startup_data_new (NULL, NULL, NULL, NULL, NULL, NULL, callback, callback_data, existing_window); g_signal_connect (GTK_WIDGET (existing_window), "destroy", G_CALLBACK (widget_on_destroy), startup_data); 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_lists, pending_key) != NULL) { /* FIXME: No callback is done if this happen. In practice, it's a quite * corner case */ return; } target_files = get_target_file_list (original_files); if (parent_widget) { parent_window = GTK_WINDOW (gtk_widget_get_ancestor (parent_widget, GTK_TYPE_WINDOW)); } else { parent_window = NULL; } startup_data = startup_data_new (original_files, target_files, pending_key, parent_widget, parent_window, startup_id, callback, callback_data, NULL); nautilus_file_list_free (target_files); g_free (pending_key); /* 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_lists, startup_data->pending_key, startup_data->pending_key); if (parent_widget) { g_signal_connect (parent_widget, "destroy", G_CALLBACK (parent_widget_destroyed_callback), startup_data); } eel_timed_wait_start (cancel_create_properties_window_callback, startup_data, _("Creating Properties window."), parent_window == NULL ? NULL : GTK_WINDOW (parent_window)); for (l = startup_data->target_files; l != NULL; l = next) { next = l->next; nautilus_file_call_when_ready (NAUTILUS_FILE (l->data), NAUTILUS_FILE_ATTRIBUTE_INFO, is_directory_ready_callback, startup_data); } } static void real_destroy (GtkWidget *object) { NautilusPropertiesWindow *window; GList *l; window = NAUTILUS_PROPERTIES_WINDOW (object); remove_window (window); unschedule_or_cancel_group_change (window); unschedule_or_cancel_owner_change (window); for (l = window->original_files; l != NULL; l = l->next) { nautilus_file_monitor_remove (NAUTILUS_FILE (l->data), &window->original_files); } nautilus_file_list_free (window->original_files); window->original_files = NULL; for (l = window->target_files; l != NULL; l = l->next) { nautilus_file_monitor_remove (NAUTILUS_FILE (l->data), &window->target_files); } nautilus_file_list_free (window->target_files); window->target_files = NULL; nautilus_file_list_free (window->changed_files); window->changed_files = NULL; if (window->deep_count_spinner_timeout_id > 0) { g_source_remove (window->deep_count_spinner_timeout_id); } while (window->deep_count_files) { stop_deep_count_for_file (window, window->deep_count_files->data); } g_list_free (window->permission_buttons); window->permission_buttons = NULL; g_list_free (window->permission_combos); window->permission_combos = NULL; g_list_free (window->change_permission_combos); window->change_permission_combos = NULL; if (window->initial_permissions) { g_hash_table_destroy (window->initial_permissions); window->initial_permissions = NULL; } g_list_free (window->value_fields); window->value_fields = NULL; if (window->update_directory_contents_timeout_id != 0) { g_source_remove (window->update_directory_contents_timeout_id); window->update_directory_contents_timeout_id = 0; } if (window->update_files_timeout_id != 0) { g_source_remove (window->update_files_timeout_id); window->update_files_timeout_id = 0; } GTK_WIDGET_CLASS (nautilus_properties_window_parent_class)->destroy (object); } static void real_finalize (GObject *object) { NautilusPropertiesWindow *window; window = NAUTILUS_PROPERTIES_WINDOW (object); g_list_free_full (window->mime_list, g_free); g_free (window->pending_name); g_free (window->content_type); g_list_free (window->open_with_files); if (window->select_idle_id != 0) { g_source_remove (window->select_idle_id); } G_OBJECT_CLASS (nautilus_properties_window_parent_class)->finalize (object); } /* icon selection callback to set the image of the file object to the selected file */ static void set_icon (const char *icon_uri, NautilusPropertiesWindow *properties_window) { NautilusFile *file; char *file_uri; char *icon_path; g_assert (icon_uri != NULL); g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (properties_window)); icon_path = g_filename_from_uri (icon_uri, NULL, NULL); /* we don't allow remote URIs */ if (icon_path != NULL) { GList *l; for (l = properties_window->original_files; l != NULL; l = l->next) { g_autoptr (GFile) file_location = NULL; g_autoptr (GFile) icon_location = NULL; g_autofree gchar *real_icon_uri = NULL; file = NAUTILUS_FILE (l->data); file_uri = nautilus_file_get_uri (file); file_location = nautilus_file_get_location (file); icon_location = g_file_new_for_uri (icon_uri); /* ’Tis a little bit of a misnomer. Actually a path. */ real_icon_uri = g_file_get_relative_path (icon_location, file_location); if (real_icon_uri == NULL) { real_icon_uri = g_strdup (icon_uri); } nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL, real_icon_uri); g_free (file_uri); } g_free (icon_path); } } static void update_preview_callback (GtkFileChooser *icon_chooser, NautilusPropertiesWindow *window) { GtkWidget *preview_widget; GdkPixbuf *pixbuf, *scaled_pixbuf; char *filename; double scale; pixbuf = NULL; filename = gtk_file_chooser_get_filename (icon_chooser); if (filename != NULL) { pixbuf = gdk_pixbuf_new_from_file (filename, NULL); } if (pixbuf != NULL) { preview_widget = gtk_file_chooser_get_preview_widget (icon_chooser); gtk_file_chooser_set_preview_widget_active (icon_chooser, TRUE); if (gdk_pixbuf_get_width (pixbuf) > PREVIEW_IMAGE_WIDTH) { scale = (double) gdk_pixbuf_get_height (pixbuf) / gdk_pixbuf_get_width (pixbuf); scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf, PREVIEW_IMAGE_WIDTH, scale * PREVIEW_IMAGE_WIDTH, GDK_INTERP_BILINEAR); g_object_unref (pixbuf); pixbuf = scaled_pixbuf; } gtk_image_set_from_pixbuf (GTK_IMAGE (preview_widget), pixbuf); } else { gtk_file_chooser_set_preview_widget_active (icon_chooser, FALSE); } g_free (filename); if (pixbuf != NULL) { g_object_unref (pixbuf); } } static void custom_icon_file_chooser_response_cb (GtkDialog *dialog, gint response, NautilusPropertiesWindow *window) { char *uri; switch (response) { case GTK_RESPONSE_NO: { reset_icon (window); } break; case GTK_RESPONSE_OK: { uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog)); if (uri != NULL) { set_icon (uri, window); } else { reset_icon (window); } g_free (uri); } break; default: { } break; } gtk_widget_hide (GTK_WIDGET (dialog)); } static void select_image_button_callback (GtkWidget *widget, NautilusPropertiesWindow *window) { GtkWidget *dialog, *preview; GtkFileFilter *filter; GList *l; NautilusFile *file; char *uri; char *image_path; gboolean revert_is_sensitive; g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window)); dialog = window->icon_chooser; if (dialog == NULL) { dialog = gtk_file_chooser_dialog_new (_("Select Custom Icon"), GTK_WINDOW (window), GTK_FILE_CHOOSER_ACTION_OPEN, _("_Revert"), GTK_RESPONSE_NO, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Open"), GTK_RESPONSE_OK, NULL); gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), g_get_user_special_dir (G_USER_DIRECTORY_PICTURES), NULL); gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); filter = gtk_file_filter_new (); gtk_file_filter_add_pixbuf_formats (filter); gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter); preview = gtk_image_new (); gtk_widget_set_size_request (preview, PREVIEW_IMAGE_WIDTH, -1); gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (dialog), preview); gtk_file_chooser_set_use_preview_label (GTK_FILE_CHOOSER (dialog), FALSE); gtk_file_chooser_set_preview_widget_active (GTK_FILE_CHOOSER (dialog), FALSE); g_signal_connect (dialog, "update-preview", G_CALLBACK (update_preview_callback), window); window->icon_chooser = dialog; g_object_add_weak_pointer (G_OBJECT (dialog), (gpointer *) &window->icon_chooser); } /* it's likely that the user wants to pick an icon that is inside a local directory */ if (g_list_length (window->original_files) == 1) { file = NAUTILUS_FILE (window->original_files->data); if (nautilus_file_is_directory (file)) { uri = nautilus_file_get_uri (file); image_path = g_filename_from_uri (uri, NULL, NULL); if (image_path != NULL) { gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), image_path); g_free (image_path); } g_free (uri); } } revert_is_sensitive = FALSE; for (l = window->original_files; l != NULL; l = l->next) { file = NAUTILUS_FILE (l->data); image_path = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL); revert_is_sensitive = (image_path != NULL); g_free (image_path); if (revert_is_sensitive) { break; } } gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_NO, revert_is_sensitive); g_signal_connect (dialog, "response", G_CALLBACK (custom_icon_file_chooser_response_cb), window); gtk_widget_show (dialog); } static void nautilus_properties_window_class_init (NautilusPropertiesWindowClass *klass) { GtkBindingSet *binding_set; GtkWidgetClass *widget_class; GObjectClass *oclass; widget_class = GTK_WIDGET_CLASS (klass); oclass = G_OBJECT_CLASS (klass); oclass->finalize = real_finalize; widget_class->destroy = real_destroy; binding_set = gtk_binding_set_by_class (klass); g_signal_new ("close", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, "close", 0); gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-properties-window.ui"); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, notebook); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_stack); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_image); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_button); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_button_image); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, basic_grid); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, name_title_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, name_stack); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, name_field); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, type_title_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, type_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, link_target_title_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, link_target_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, contents_title_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, contents_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, contents_spinner); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, size_title_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, size_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, parent_folder_title_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, parent_folder_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, original_folder_title_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, original_folder_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, volume_title_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, volume_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, trashed_on_title_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, trashed_on_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, accessed_title_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, accessed_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, spacer_2); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, modified_title_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, modified_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, spacer_3); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, free_space_title_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, free_space_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, volume_widget_box); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, open_in_disks_button); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, pie_chart); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, used_color); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, used_value); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, free_color); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, free_value); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, total_capacity_value); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, file_system_value); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, permissions_box); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, permissions_grid); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, bottom_prompt_seperator); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, not_the_owner_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, permission_indeterminable_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_value_stack); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_access_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_folder_access_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_file_access_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_access_combo); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_folder_access_combo); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_file_access_combo); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_value_stack); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_access_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_folder_access_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_file_access_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_access_combo); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_folder_access_combo); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_file_access_combo); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_access_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_folder_access_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_file_access_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_access_combo); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_folder_access_combo); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_file_access_combo); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, execute_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, execute_checkbox); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, security_context_title_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, security_context_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, change_permissions_button_box); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, change_permissions_button); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, open_with_box); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, open_with_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, app_chooser_widget_box); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, reset_button); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, add_button); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, set_as_default_button); } static void nautilus_properties_window_init (NautilusPropertiesWindow *window) { gtk_widget_init_template (GTK_WIDGET (window)); g_signal_connect (window, "close", G_CALLBACK (gtk_window_close), NULL); }