/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* fm-properties-window.c - window that lets user modify file properties Copyright (C) 2000 Eazel, Inc. The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. Authors: Darin Adler */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fm-properties-window.h" #include "fm-ditem-page.h" #include "fm-error-reporting.h" #if HAVE_SYS_VFS_H #include #elif HAVE_SYS_MOUNT_H #if HAVE_SYS_PARAM_H #include #endif #include #endif #define USED_FILL_R 0.988235294 #define USED_FILL_G 0.91372549 #define USED_FILL_B 0.309803922 #define FREE_FILL_R 0.447058824 #define FREE_FILL_G 0.623529412 #define FREE_FILL_B 0.811764706 #define PREVIEW_IMAGE_WIDTH 96 #define ROW_PAD 6 static GHashTable *windows; static GHashTable *pending_lists; struct _FMPropertiesWindowPrivate { GList *original_files; GList *target_files; GtkNotebook *notebook; GtkGrid *basic_grid; GtkWidget *icon_button; GtkWidget *icon_image; GtkWidget *icon_chooser; GtkLabel *name_label; GtkWidget *name_field; unsigned int name_row; char *pending_name; GtkLabel *directory_contents_title_field; GtkLabel *directory_contents_value_field; guint update_directory_contents_timeout_id; guint update_files_timeout_id; GList *emblem_buttons; GHashTable *initial_emblems; CajaFile *group_change_file; char *group_change_group; unsigned int group_change_timeout; CajaFile *owner_change_file; char *owner_change_owner; unsigned int owner_change_timeout; GList *permission_buttons; GList *permission_combos; GHashTable *initial_permissions; gboolean has_recursive_apply; GList *value_fields; GList *mime_list; gboolean deep_count_finished; guint total_count; goffset total_size; goffset total_size_on_disk; guint long_operation_underway; GList *changed_files; guint64 volume_capacity; guint64 volume_free; GdkRGBA used_color; GdkRGBA free_color; GdkRGBA used_stroke_color; GdkRGBA free_stroke_color; }; typedef enum { PERMISSIONS_CHECKBOXES_READ, PERMISSIONS_CHECKBOXES_WRITE, PERMISSIONS_CHECKBOXES_EXECUTE } CheckboxType; enum { TITLE_COLUMN, VALUE_COLUMN, COLUMN_COUNT }; typedef struct { GList *original_files; GList *target_files; GtkWidget *parent_widget; char *pending_key; GHashTable *pending_files; } StartupData; /* drag and drop definitions */ enum { TARGET_URI_LIST, TARGET_MATE_URI_LIST, TARGET_RESET_BACKGROUND }; static const GtkTargetEntry target_table[] = { { "text/uri-list", 0, TARGET_URI_LIST }, { "x-special/mate-icon-list", 0, TARGET_MATE_URI_LIST }, { "x-special/mate-reset-background", 0, TARGET_RESET_BACKGROUND } }; #define DIRECTORY_CONTENTS_UPDATE_INTERVAL 200 /* milliseconds */ #define FILES_UPDATE_INTERVAL 200 /* milliseconds */ #define STANDARD_EMBLEM_HEIGHT 52 #define EMBLEM_LABEL_SPACING 2 /* * 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 directory_contents_value_field_update (FMPropertiesWindow *window); static void file_changed_callback (CajaFile *file, gpointer user_data); static void permission_button_update (FMPropertiesWindow *window, GtkToggleButton *button); static void permission_combo_update (FMPropertiesWindow *window, GtkComboBox *combo); static void value_field_update (FMPropertiesWindow *window, GtkLabel *field); static void properties_window_update (FMPropertiesWindow *window, GList *files); static void is_directory_ready_callback (CajaFile *file, gpointer data); static void cancel_group_change_callback (FMPropertiesWindow *window); static void cancel_owner_change_callback (FMPropertiesWindow *window); static void parent_widget_destroyed_callback (GtkWidget *widget, gpointer callback_data); static void select_image_button_callback (GtkWidget *widget, FMPropertiesWindow *properties_window); static void set_icon (const char *icon_path, FMPropertiesWindow *properties_window); static void remove_pending (StartupData *data, gboolean cancel_call_when_ready, gboolean cancel_timed_wait, gboolean cancel_destroy_handler); static void append_extension_pages (FMPropertiesWindow *window); static gboolean name_field_focus_out (CajaEntry *name_field, GdkEventFocus *event, gpointer callback_data); static void name_field_activate (CajaEntry *name_field, gpointer callback_data); static GtkLabel *attach_ellipsizing_value_label (GtkGrid *grid, GtkWidget *sibling, const char *initial_text); static GtkWidget* create_pie_widget (FMPropertiesWindow *window); G_DEFINE_TYPE_WITH_PRIVATE (FMPropertiesWindow, fm_properties_window, GTK_TYPE_DIALOG); static gboolean is_multi_file_window (FMPropertiesWindow *window) { GList *l; int count; count = 0; for (l = window->details->original_files; l != NULL; l = l->next) { if (!caja_file_is_gone (CAJA_FILE (l->data))) { count++; if (count > 1) { return TRUE; } } } return FALSE; } static int get_not_gone_original_file_count (FMPropertiesWindow *window) { GList *l; int count; count = 0; for (l = window->details->original_files; l != NULL; l = l->next) { if (!caja_file_is_gone (CAJA_FILE (l->data))) { count++; } } return count; } static CajaFile * get_original_file (FMPropertiesWindow *window) { g_return_val_if_fail (!is_multi_file_window (window), NULL); if (window->details->original_files == NULL) { return NULL; } return CAJA_FILE (window->details->original_files->data); } static CajaFile * get_target_file_for_original_file (CajaFile *file) { CajaFile *target_file; target_file = NULL; if (CAJA_IS_DESKTOP_ICON_FILE (file)) { CajaDesktopLink *link; link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (file)); if (link != NULL) { GFile *location; /* map to linked URI for these types of links */ location = caja_desktop_link_get_activation_location (link); if (location) { target_file = caja_file_get (location); g_object_unref (location); } g_object_unref (link); } } else { char *uri_to_display; uri_to_display = caja_file_get_activation_uri (file); if (uri_to_display != NULL) { target_file = caja_file_get_by_uri (uri_to_display); g_free (uri_to_display); } } if (target_file != NULL) { return target_file; } /* Ref passed-in file here since we've decided to use it. */ caja_file_ref (file); return file; } static CajaFile * get_target_file (FMPropertiesWindow *window) { return CAJA_FILE (window->details->target_files->data); } static void add_prompt (GtkWidget *vbox, const char *prompt_text, gboolean pack_at_start) { GtkWidget *prompt; prompt = gtk_label_new (prompt_text); gtk_label_set_justify (GTK_LABEL (prompt), GTK_JUSTIFY_LEFT); gtk_label_set_line_wrap (GTK_LABEL (prompt), TRUE); gtk_widget_show (prompt); if (pack_at_start) { gtk_box_pack_start (GTK_BOX (vbox), prompt, FALSE, FALSE, 0); } else { gtk_box_pack_end (GTK_BOX (vbox), prompt, FALSE, FALSE, 0); } } static void add_prompt_and_separator (GtkWidget *vbox, const char *prompt_text) { GtkWidget *separator_line; add_prompt (vbox, prompt_text, FALSE); separator_line = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); gtk_widget_show (separator_line); gtk_box_pack_end (GTK_BOX (vbox), separator_line, TRUE, TRUE, 2*ROW_PAD); } static void get_image_for_properties_window (FMPropertiesWindow *window, char **icon_name, GdkPixbuf **icon_pixbuf) { CajaIconInfo *icon, *new_icon; GList *l; gint icon_scale; icon = NULL; icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (window->details->notebook)); for (l = window->details->original_files; l != NULL; l = l->next) { CajaFile *file; file = CAJA_FILE (l->data); if (!icon) { icon = caja_file_get_icon (file, CAJA_ICON_SIZE_STANDARD, icon_scale, CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS | CAJA_FILE_ICON_FLAGS_IGNORE_VISITING); } else { new_icon = caja_file_get_icon (file, CAJA_ICON_SIZE_STANDARD, icon_scale, CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS | CAJA_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 = caja_icon_info_lookup_from_name ("text-x-generic", CAJA_ICON_SIZE_STANDARD, icon_scale); } if (icon_name != NULL) { *icon_name = g_strdup (caja_icon_info_get_used_name (icon)); } if (icon_pixbuf != NULL) { *icon_pixbuf = caja_icon_info_get_pixbuf_at_size (icon, CAJA_ICON_SIZE_STANDARD); } g_object_unref (icon); } static void update_properties_window_icon (FMPropertiesWindow *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->details->icon_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 (FMPropertiesWindow *properties_window) { GList *l; for (l = properties_window->details->original_files; l != NULL; l = l->next) { CajaFile *file; file = CAJA_FILE (l->data); caja_file_set_metadata (file, CAJA_METADATA_KEY_ICON_SCALE, NULL, NULL); caja_file_set_metadata (file, CAJA_METADATA_KEY_CUSTOM_ICON, NULL, NULL); } } static void fm_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))); if (info == TARGET_RESET_BACKGROUND) { reset_icon (FM_PROPERTIES_WINDOW (window)); return; } uris = g_strsplit (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) { eel_show_error_dialog (_("You cannot assign more than one custom icon at a time!"), _("Please drag just one image to set a custom icon."), window); } else { if (uri_is_local_image (uris[0])) { set_icon (uris[0], FM_PROPERTIES_WINDOW (window)); } else { GFile *f; f = g_file_new_for_uri (uris[0]); if (!g_file_is_native (f)) { eel_show_error_dialog (_("The file that you dropped is not local."), _("You can only use local images as custom icons."), window); } else { eel_show_error_dialog (_("The file that you dropped is not an image."), _("You can only use local images as custom icons."), window); } g_object_unref (f); } } g_strfreev (uris); } static GtkWidget * create_image_widget (FMPropertiesWindow *window, gboolean is_customizable) { GtkWidget *button; GtkWidget *image; image = gtk_image_new (); window->details->icon_image = image; update_properties_window_icon (window); gtk_widget_show (image); button = NULL; if (is_customizable) { button = gtk_button_new (); gtk_container_add (GTK_CONTAINER (button), image); /* prepare the image to receive dropped objects to assign custom images */ gtk_drag_dest_set (GTK_WIDGET (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 (image, "drag_data_received", G_CALLBACK (fm_properties_window_drag_data_received), NULL); g_signal_connect (button, "clicked", G_CALLBACK (select_image_button_callback), window); } window->details->icon_button = button; return button != NULL ? button : image; } static void set_name_field (FMPropertiesWindow *window, const gchar *original_name, const gchar *name) { gboolean new_widget; gboolean use_label; /* There are four cases here: * 1) Changing the text of a label * 2) Changing the text of an entry * 3) Creating label (potentially replacing entry) * 4) Creating entry (potentially replacing label) */ use_label = is_multi_file_window (window) || !caja_file_can_rename (get_original_file (window)); new_widget = !window->details->name_field || (use_label ? CAJA_IS_ENTRY (window->details->name_field) : GTK_IS_LABEL (window->details->name_field)); if (new_widget) { if (window->details->name_field) { gtk_widget_destroy (window->details->name_field); } if (use_label) { window->details->name_field = GTK_WIDGET (attach_ellipsizing_value_label (window->details->basic_grid, GTK_WIDGET (window->details->name_label), name)); } else { window->details->name_field = caja_entry_new (); gtk_entry_set_text (GTK_ENTRY (window->details->name_field), name); gtk_widget_show (window->details->name_field); gtk_grid_attach_next_to (window->details->basic_grid, window->details->name_field, GTK_WIDGET (window->details->name_label), GTK_POS_RIGHT, 1, 1); gtk_label_set_mnemonic_widget (GTK_LABEL (window->details->name_label), window->details->name_field); g_signal_connect_object (window->details->name_field, "focus_out_event", G_CALLBACK (name_field_focus_out), window, 0); g_signal_connect_object (window->details->name_field, "activate", G_CALLBACK (name_field_activate), window, 0); } gtk_widget_show (window->details->name_field); } /* Only replace text if the file's name has changed. */ else if (original_name == NULL || strcmp (original_name, name) != 0) { if (use_label) { gtk_label_set_text (GTK_LABEL (window->details->name_field), name); } else { /* 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->details->name_field), 0, -1); if (strcmp (displayed_name, name) != 0) { gtk_entry_set_text (GTK_ENTRY (window->details->name_field), name); } g_free (displayed_name); } } } static void update_name_field (FMPropertiesWindow *window) { CajaFile *file; gtk_label_set_text_with_mnemonic (window->details->name_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->details->target_files; l != NULL; l = l->next) { file = CAJA_FILE (l->data); if (!caja_file_is_gone (file)) { if (!first) { g_string_append (str, ", "); } first = FALSE; name = caja_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 || caja_file_is_gone (file)) { current_name = g_strdup (""); } else { current_name = caja_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. */ if (window->details->name_field) { original_name = (const char *) g_object_get_data (G_OBJECT (window->details->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->details->name_field), "original_name", current_name, g_free); } else { g_free (current_name); } } } static void name_field_restore_original_name (CajaEntry *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); } caja_entry_select_all (name_field); g_free (displayed_name); } static void rename_callback (CajaFile *file, GFile *res_loc, GError *error, gpointer callback_data) { FMPropertiesWindow *window; window = FM_PROPERTIES_WINDOW (callback_data); /* Complain to user if rename failed. */ if (error != NULL) { fm_report_error_renaming_file (file, window->details->pending_name, error, GTK_WINDOW (window)); if (window->details->name_field != NULL) { name_field_restore_original_name (CAJA_ENTRY (window->details->name_field)); } } g_object_unref (window); } static void set_pending_name (FMPropertiesWindow *window, const char *name) { g_free (window->details->pending_name); window->details->pending_name = g_strdup (name); } static void name_field_done_editing (CajaEntry *name_field, FMPropertiesWindow *window) { CajaFile *file; char *new_name; g_return_if_fail (CAJA_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 || caja_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 (CAJA_ENTRY (name_field)); } else { const char *original_name; original_name = (const char *) g_object_get_data (G_OBJECT (window->details->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); caja_file_rename (file, new_name, rename_callback, window); } } g_free (new_name); } static gboolean name_field_focus_out (CajaEntry *name_field, GdkEventFocus *event, gpointer callback_data) { g_assert (FM_IS_PROPERTIES_WINDOW (callback_data)); if (gtk_widget_get_sensitive (GTK_WIDGET (name_field))) { name_field_done_editing (name_field, FM_PROPERTIES_WINDOW (callback_data)); } return FALSE; } static void name_field_activate (CajaEntry *name_field, gpointer callback_data) { g_assert (CAJA_IS_ENTRY (name_field)); g_assert (FM_IS_PROPERTIES_WINDOW (callback_data)); /* Accept changes. */ name_field_done_editing (name_field, FM_PROPERTIES_WINDOW (callback_data)); caja_entry_select_all_at_idle (name_field); } static gboolean file_has_keyword (CajaFile *file, const char *keyword) { GList *keywords, *word; keywords = caja_file_get_keywords (file); word = g_list_find_custom (keywords, keyword, (GCompareFunc) strcmp); g_list_free_full (keywords, g_free); return (word != NULL); } static void get_initial_emblem_state (FMPropertiesWindow *window, const char *name, GList **on, GList **off) { GList *l; *on = NULL; *off = NULL; for (l = window->details->original_files; l != NULL; l = l->next) { GList *initial_emblems; initial_emblems = g_hash_table_lookup (window->details->initial_emblems, l->data); if (g_list_find_custom (initial_emblems, name, (GCompareFunc) strcmp)) { *on = g_list_prepend (*on, l->data); } else { *off = g_list_prepend (*off, l->data); } } } static void emblem_button_toggled (GtkToggleButton *button, FMPropertiesWindow *window) { GList *l; GList *keywords; GList *word; char *name; GList *files_on; GList *files_off; name = g_object_get_data (G_OBJECT (button), "caja_emblem_name"); files_on = NULL; files_off = NULL; if (gtk_toggle_button_get_active (button) && !gtk_toggle_button_get_inconsistent (button)) { /* Go to the initial state unless the initial state was consistent */ get_initial_emblem_state (window, name, &files_on, &files_off); if (!(files_on && files_off)) { g_list_free (files_on); g_list_free (files_off); files_on = g_list_copy (window->details->original_files); files_off = NULL; } } else if (gtk_toggle_button_get_inconsistent (button) && !gtk_toggle_button_get_active (button)) { files_on = g_list_copy (window->details->original_files); files_off = NULL; } else { files_off = g_list_copy (window->details->original_files); files_on = NULL; } g_signal_handlers_block_by_func (G_OBJECT (button), G_CALLBACK (emblem_button_toggled), window); gtk_toggle_button_set_active (button, files_on != NULL); gtk_toggle_button_set_inconsistent (button, files_on && files_off); g_signal_handlers_unblock_by_func (G_OBJECT (button), G_CALLBACK (emblem_button_toggled), window); for (l = files_on; l != NULL; l = l->next) { CajaFile *file; file = CAJA_FILE (l->data); keywords = caja_file_get_keywords (file); word = g_list_find_custom (keywords, name, (GCompareFunc)strcmp); if (!word) { keywords = g_list_prepend (keywords, g_strdup (name)); } caja_file_set_keywords (file, keywords); g_list_free_full (keywords, g_free); } for (l = files_off; l != NULL; l = l->next) { CajaFile *file; file = CAJA_FILE (l->data); keywords = caja_file_get_keywords (file); word = g_list_find_custom (keywords, name, (GCompareFunc)strcmp); if (word) { keywords = g_list_remove_link (keywords, word); g_list_free_full (word, g_free); } caja_file_set_keywords (file, keywords); g_list_free_full (keywords, g_free); } g_list_free (files_on); g_list_free (files_off); } static void emblem_button_update (FMPropertiesWindow *window, GtkToggleButton *button) { GList *l; char *name; gboolean all_set; gboolean all_unset; name = g_object_get_data (G_OBJECT (button), "caja_emblem_name"); all_set = TRUE; all_unset = TRUE; for (l = window->details->original_files; l != NULL; l = l->next) { gboolean has_keyword; CajaFile *file; file = CAJA_FILE (l->data); has_keyword = file_has_keyword (file, name); if (has_keyword) { all_unset = FALSE; } else { all_set = FALSE; } } g_signal_handlers_block_by_func (G_OBJECT (button), G_CALLBACK (emblem_button_toggled), window); gtk_toggle_button_set_active (button, !all_unset); gtk_toggle_button_set_inconsistent (button, !all_unset && !all_set); g_signal_handlers_unblock_by_func (G_OBJECT (button), G_CALLBACK (emblem_button_toggled), window); } static void update_properties_window_title (FMPropertiesWindow *window) { char *title; g_return_if_fail (GTK_IS_WINDOW (window)); title = g_strdup_printf (_("Properties")); if (!is_multi_file_window (window)) { CajaFile *file; file = get_original_file (window); if (file != NULL) { char *name; g_free (title); name = caja_file_get_display_name (file); title = g_strdup_printf (_("%s Properties"), name); g_free (name); } } gtk_window_set_title (GTK_WINDOW (window), title); g_free (title); } static void clear_extension_pages (FMPropertiesWindow *window) { int i; int num_pages; GtkWidget *page = NULL; num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (window->details->notebook)); for (i = 0; i < num_pages; i++) { page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (window->details->notebook), i); if (g_object_get_data (G_OBJECT (page), "is-extension-page")) { gtk_notebook_remove_page (GTK_NOTEBOOK (window->details->notebook), i); num_pages--; i--; } } } static void refresh_extension_pages (FMPropertiesWindow *window) { clear_extension_pages (window); append_extension_pages (window); } static void remove_from_dialog (FMPropertiesWindow *window, CajaFile *file) { int index; GList *original_link; GList *target_link; CajaFile *original_file; CajaFile *target_file; index = g_list_index (window->details->target_files, file); if (index == -1) { index = g_list_index (window->details->original_files, file); g_return_if_fail (index != -1); } original_link = g_list_nth (window->details->original_files, index); target_link = g_list_nth (window->details->target_files, index); g_return_if_fail (original_link && target_link); original_file = CAJA_FILE (original_link->data); target_file = CAJA_FILE (target_link->data); window->details->original_files = g_list_remove_link (window->details->original_files, original_link); g_list_free (original_link); window->details->target_files = g_list_remove_link (window->details->target_files, target_link); g_list_free (target_link); g_hash_table_remove (window->details->initial_emblems, original_file); g_hash_table_remove (window->details->initial_permissions, target_file); g_signal_handlers_disconnect_by_func (original_file, G_CALLBACK (file_changed_callback), window); g_signal_handlers_disconnect_by_func (target_file, G_CALLBACK (file_changed_callback), window); caja_file_monitor_remove (original_file, &window->details->original_files); caja_file_monitor_remove (target_file, &window->details->target_files); caja_file_unref (original_file); caja_file_unref (target_file); } static gboolean mime_list_equal (GList *a, GList *b) { while (a && b) { if (strcmp (a->data, b->data)) { return FALSE; } a = a->next; b = b->next; } return (a == b); } static GList * get_mime_list (FMPropertiesWindow *window) { GList *ret; GList *l; ret = NULL; for (l = window->details->target_files; l != NULL; l = l->next) { ret = g_list_append (ret, caja_file_get_mime_type (CAJA_FILE (l->data))); } ret = g_list_reverse (ret); return ret; } static void properties_window_update (FMPropertiesWindow *window, GList *files) { GList *l; GList *mime_list; GList *tmp; CajaFile *changed_file = NULL; 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 = CAJA_FILE (tmp->data); if (changed_file && caja_file_is_gone (changed_file)) { /* Remove the file from the property dialog */ remove_from_dialog (window, changed_file); changed_file = NULL; if (window->details->original_files == NULL) { return; } } if (changed_file == NULL || g_list_find (window->details->original_files, changed_file)) { dirty_original = TRUE; } if (changed_file == NULL || g_list_find (window->details->target_files, changed_file)) { dirty_target = TRUE; } } if (dirty_original) { update_properties_window_title (window); update_properties_window_icon (window); update_name_field (window); for (l = window->details->emblem_buttons; l != NULL; l = l->next) { emblem_button_update (window, GTK_TOGGLE_BUTTON (l->data)); } /* If any of the value fields start to depend on the original * value, value_field_updates should be added here */ } if (dirty_target) { for (l = window->details->permission_buttons; l != NULL; l = l->next) { permission_button_update (window, GTK_TOGGLE_BUTTON (l->data)); } for (l = window->details->permission_combos; l != NULL; l = l->next) { permission_combo_update (window, GTK_COMBO_BOX (l->data)); } for (l = window->details->value_fields; l != NULL; l = l->next) { value_field_update (window, GTK_LABEL (l->data)); } } mime_list = get_mime_list (window); if (!window->details->mime_list) { window->details->mime_list = mime_list; } else { if (!mime_list_equal (window->details->mime_list, mime_list)) { refresh_extension_pages (window); } g_list_free_full (window->details->mime_list, g_free); window->details->mime_list = mime_list; } } static gboolean update_files_callback (gpointer data) { FMPropertiesWindow *window; window = FM_PROPERTIES_WINDOW (data); window->details->update_files_timeout_id = 0; properties_window_update (window, window->details->changed_files); if (window->details->original_files == NULL) { /* Close the window if no files are left */ gtk_widget_destroy (GTK_WIDGET (window)); } else { caja_file_list_free (window->details->changed_files); window->details->changed_files = NULL; } return FALSE; } static void schedule_files_update (FMPropertiesWindow *window) { g_assert (FM_IS_PROPERTIES_WINDOW (window)); if (window->details->update_files_timeout_id == 0) { window->details->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) { CajaFile *file; file = CAJA_FILE (l->data); if (caja_file_is_gone (file)) { continue; } if (first_attr == NULL) { first_attr = caja_file_get_string_attribute_with_default (file, attribute_name); } else { char *attr; attr = caja_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) { CajaFile *file; file = CAJA_FILE (l->data); if (!caja_file_is_gone (file)) { return caja_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 (!caja_file_is_directory (CAJA_FILE (l->data))) { return FALSE; } } return TRUE; } static void value_field_update_internal (GtkLabel *label, GList *file_list) { const char *attribute_name; char *attribute_value; char *inconsistent_string; g_assert (GTK_IS_LABEL (label)); attribute_name = g_object_get_data (G_OBJECT (label), "file_attribute"); inconsistent_string = g_object_get_data (G_OBJECT (label), "inconsistent_string"); attribute_value = file_list_get_string_attribute (file_list, attribute_name, inconsistent_string); if (!strcmp (attribute_name, "type") && strcmp (attribute_value, inconsistent_string)) { char *mime_type; mime_type = file_list_get_string_attribute (file_list, "mime_type", inconsistent_string); if (strcmp (mime_type, inconsistent_string)) { char *tmp; 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 value_field_update (FMPropertiesWindow *window, GtkLabel *label) { gboolean use_original; use_original = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (label), "show_original")); value_field_update_internal (label, (use_original ? window->details->original_files : window->details->target_files)); } static GtkLabel * attach_label (GtkGrid *grid, GtkWidget *sibling, const char *initial_text, gboolean ellipsize_text, gboolean selectable, gboolean mnemonic) { GtkWidget *label_field; if (ellipsize_text) { label_field = gtk_label_new (initial_text); gtk_label_set_ellipsize (GTK_LABEL (label_field), PANGO_ELLIPSIZE_END); } else if (mnemonic) { label_field = gtk_label_new_with_mnemonic (initial_text); } else { label_field = gtk_label_new (initial_text); } if (selectable) { gtk_label_set_selectable (GTK_LABEL (label_field), TRUE); } gtk_label_set_xalign (GTK_LABEL (label_field), 0); gtk_widget_show (label_field); if (ellipsize_text) { gtk_widget_set_hexpand (label_field, TRUE); gtk_label_set_max_width_chars (GTK_LABEL (label_field), 24); } if (sibling != NULL) { gtk_grid_attach_next_to (grid, label_field, sibling, GTK_POS_RIGHT, 1, 1); } else { gtk_container_add (GTK_CONTAINER (grid), label_field); } return GTK_LABEL (label_field); } static GtkLabel * attach_value_label (GtkGrid *grid, GtkWidget *sibling, const char *initial_text) { return attach_label (grid, sibling, initial_text, FALSE, TRUE, FALSE); } static GtkLabel * attach_ellipsizing_value_label (GtkGrid *grid, GtkWidget *sibling, const char *initial_text) { return attach_label (grid, sibling, initial_text, TRUE, TRUE, FALSE); } static GtkWidget* attach_value_field_internal (FMPropertiesWindow *window, GtkGrid *grid, GtkWidget *sibling, const char *file_attribute_name, const char *inconsistent_string, gboolean show_original, gboolean ellipsize_text) { GtkLabel *value_field; if (ellipsize_text) { value_field = attach_ellipsizing_value_label (grid, sibling, ""); } else { value_field = attach_value_label (grid, sibling, ""); } /* Stash a copy of the file attribute name in this field for the callback's sake. */ g_object_set_data_full (G_OBJECT (value_field), "file_attribute", g_strdup (file_attribute_name), g_free); g_object_set_data_full (G_OBJECT (value_field), "inconsistent_string", g_strdup (inconsistent_string), g_free); g_object_set_data (G_OBJECT (value_field), "show_original", GINT_TO_POINTER (show_original)); window->details->value_fields = g_list_prepend (window->details->value_fields, value_field); return GTK_WIDGET(value_field); } static GtkWidget* attach_value_field (FMPropertiesWindow *window, GtkGrid *grid, GtkWidget *sibling, const char *file_attribute_name, const char *inconsistent_string, gboolean show_original) { return attach_value_field_internal (window, grid, sibling, file_attribute_name, inconsistent_string, show_original, FALSE); } static GtkWidget* attach_ellipsizing_value_field (FMPropertiesWindow *window, GtkGrid *grid, GtkWidget *sibling, const char *file_attribute_name, const char *inconsistent_string, gboolean show_original) { return attach_value_field_internal (window, grid, sibling, file_attribute_name, inconsistent_string, show_original, TRUE); } static void group_change_callback (CajaFile *file, GFile *res_loc, GError *error, FMPropertiesWindow *window) { char *group; g_assert (FM_IS_PROPERTIES_WINDOW (window)); g_assert (window->details->group_change_file == file); group = window->details->group_change_group; g_assert (group != NULL); /* Report the error if it's an error. */ eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, window); fm_report_error_setting_group (file, error, GTK_WINDOW (window)); caja_file_unref (file); g_free (group); window->details->group_change_file = NULL; window->details->group_change_group = NULL; g_object_unref (G_OBJECT (window)); } static void cancel_group_change_callback (FMPropertiesWindow *window) { CajaFile *file; char *group; file = window->details->group_change_file; g_assert (CAJA_IS_FILE (file)); group = window->details->group_change_group; g_assert (group != NULL); caja_file_cancel (file, (CajaFileOperationCallback) group_change_callback, window); g_free (group); caja_file_unref (file); window->details->group_change_file = NULL; window->details->group_change_group = NULL; g_object_unref (window); } static gboolean schedule_group_change_timeout (FMPropertiesWindow *window) { CajaFile *file; char *group; g_assert (FM_IS_PROPERTIES_WINDOW (window)); file = window->details->group_change_file; g_assert (CAJA_IS_FILE (file)); group = window->details->group_change_group; g_assert (group != NULL); eel_timed_wait_start ((EelCancelCallback) cancel_group_change_callback, window, _("Cancel Group Change?"), GTK_WINDOW (window)); caja_file_set_group (file, group, (CajaFileOperationCallback) group_change_callback, window); window->details->group_change_timeout = 0; return FALSE; } static void schedule_group_change (FMPropertiesWindow *window, CajaFile *file, const char *group) { g_assert (FM_IS_PROPERTIES_WINDOW (window)); g_assert (window->details->group_change_group == NULL); g_assert (window->details->group_change_file == NULL); g_assert (CAJA_IS_FILE (file)); window->details->group_change_file = caja_file_ref (file); window->details->group_change_group = g_strdup (group); g_object_ref (G_OBJECT (window)); window->details->group_change_timeout = g_timeout_add (CHOWN_CHGRP_TIMEOUT, (GSourceFunc) schedule_group_change_timeout, window); } static void unschedule_or_cancel_group_change (FMPropertiesWindow *window) { CajaFile *file; char *group; g_assert (FM_IS_PROPERTIES_WINDOW (window)); file = window->details->group_change_file; group = window->details->group_change_group; g_assert ((file == NULL && group == NULL) || (file != NULL && group != NULL)); if (file != NULL) { g_assert (CAJA_IS_FILE (file)); if (window->details->group_change_timeout == 0) { caja_file_cancel (file, (CajaFileOperationCallback) group_change_callback, window); eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, window); } caja_file_unref (file); g_free (group); window->details->group_change_file = NULL; window->details->group_change_group = NULL; g_object_unref (G_OBJECT (window)); } if (window->details->group_change_timeout > 0) { g_assert (file != NULL); g_source_remove (window->details->group_change_timeout); window->details->group_change_timeout = 0; } } static void changed_group_callback (GtkComboBox *combo_box, CajaFile *file) { char *group; char *cur_group; g_assert (GTK_IS_COMBO_BOX (combo_box)); g_assert (CAJA_IS_FILE (file)); group = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (combo_box)); cur_group = caja_file_get_group_name (file); if (group != NULL && strcmp (group, cur_group) != 0) { FMPropertiesWindow *window; /* Try to change file group. If this fails, complain to user. */ window = FM_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) { 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)) { GtkTreeModel *model; 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; 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) { int index; 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, CajaFile *file) { GList *groups; GList *node; GtkTreeModel *model; GtkListStore *store; char *current_group_name; int current_group_index; g_assert (GTK_IS_COMBO_BOX (combo_box)); g_assert (CAJA_IS_FILE (file)); if (caja_file_is_gone (file)) { return; } groups = caja_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)) { int group_index; /* 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) { const char *group_name; group_name = (const char *)node->data; gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo_box), group_name); } } current_group_name = caja_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 GtkComboBox * attach_combo_box (GtkGrid *grid, GtkWidget *sibling, gboolean two_columns) { GtkWidget *combo_box; if (!two_columns) { combo_box = gtk_combo_box_text_new (); } else { GtkTreeModel *model; GtkCellRenderer *renderer; model = GTK_TREE_MODEL (gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING)); combo_box = gtk_combo_box_new_with_model (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_widget_set_halign (combo_box, GTK_ALIGN_START); gtk_widget_show (combo_box); gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo_box), combo_box_row_separator_func, NULL, NULL); gtk_grid_attach_next_to (grid, combo_box, sibling, GTK_POS_RIGHT, 1, 1); return GTK_COMBO_BOX (combo_box); } static GtkComboBox* attach_group_combo_box (GtkGrid *grid, GtkWidget *sibling, CajaFile *file) { GtkComboBox *combo_box; combo_box = attach_combo_box (grid, sibling, FALSE); synch_groups_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), caja_file_ref (file), (GClosureNotify)caja_file_unref, 0); return combo_box; } static void owner_change_callback (CajaFile *file, GFile *result_location, GError *error, FMPropertiesWindow *window) { char *owner; g_assert (FM_IS_PROPERTIES_WINDOW (window)); g_assert (window->details->owner_change_file == file); owner = window->details->owner_change_owner; g_assert (owner != NULL); /* Report the error if it's an error. */ eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, window); fm_report_error_setting_owner (file, error, GTK_WINDOW (window)); caja_file_unref (file); g_free (owner); window->details->owner_change_file = NULL; window->details->owner_change_owner = NULL; g_object_unref (G_OBJECT (window)); } static void cancel_owner_change_callback (FMPropertiesWindow *window) { CajaFile *file; char *owner; file = window->details->owner_change_file; g_assert (CAJA_IS_FILE (file)); owner = window->details->owner_change_owner; g_assert (owner != NULL); caja_file_cancel (file, (CajaFileOperationCallback) owner_change_callback, window); caja_file_unref (file); g_free (owner); window->details->owner_change_file = NULL; window->details->owner_change_owner = NULL; g_object_unref (window); } static gboolean schedule_owner_change_timeout (FMPropertiesWindow *window) { CajaFile *file; char *owner; g_assert (FM_IS_PROPERTIES_WINDOW (window)); file = window->details->owner_change_file; g_assert (CAJA_IS_FILE (file)); owner = window->details->owner_change_owner; g_assert (owner != NULL); eel_timed_wait_start ((EelCancelCallback) cancel_owner_change_callback, window, _("Cancel Owner Change?"), GTK_WINDOW (window)); caja_file_set_owner (file, owner, (CajaFileOperationCallback) owner_change_callback, window); window->details->owner_change_timeout = 0; return FALSE; } static void schedule_owner_change (FMPropertiesWindow *window, CajaFile *file, const char *owner) { g_assert (FM_IS_PROPERTIES_WINDOW (window)); g_assert (window->details->owner_change_owner == NULL); g_assert (window->details->owner_change_file == NULL); g_assert (CAJA_IS_FILE (file)); window->details->owner_change_file = caja_file_ref (file); window->details->owner_change_owner = g_strdup (owner); g_object_ref (G_OBJECT (window)); window->details->owner_change_timeout = g_timeout_add (CHOWN_CHGRP_TIMEOUT, (GSourceFunc) schedule_owner_change_timeout, window); } static void unschedule_or_cancel_owner_change (FMPropertiesWindow *window) { CajaFile *file; char *owner; g_assert (FM_IS_PROPERTIES_WINDOW (window)); file = window->details->owner_change_file; owner = window->details->owner_change_owner; g_assert ((file == NULL && owner == NULL) || (file != NULL && owner != NULL)); if (file != NULL) { g_assert (CAJA_IS_FILE (file)); if (window->details->owner_change_timeout == 0) { caja_file_cancel (file, (CajaFileOperationCallback) owner_change_callback, window); eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, window); } caja_file_unref (file); g_free (owner); window->details->owner_change_file = NULL; window->details->owner_change_owner = NULL; g_object_unref (G_OBJECT (window)); } if (window->details->owner_change_timeout > 0) { g_assert (file != NULL); g_source_remove (window->details->owner_change_timeout); window->details->owner_change_timeout = 0; } } static void changed_owner_callback (GtkComboBox *combo_box, CajaFile* file) { char *owner_text; char **name_array; char *new_owner; char *cur_owner; g_assert (GTK_IS_COMBO_BOX (combo_box)); g_assert (CAJA_IS_FILE (file)); owner_text = combo_box_get_active_entry (combo_box, 0); if (! owner_text) return; name_array = g_strsplit (owner_text, " - ", 2); new_owner = name_array[0]; g_free (owner_text); cur_owner = caja_file_get_owner_name (file); if (strcmp (new_owner, cur_owner) != 0) { FMPropertiesWindow *window; /* Try to change file owner. If this fails, complain to user. */ window = FM_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_strfreev (name_array); g_free (cur_owner); } static void synch_user_menu (GtkComboBox *combo_box, CajaFile *file) { GList *users; GList *node; GtkTreeModel *model; GtkListStore *store; GtkTreeIter iter; char *user_name; char *owner_name; int owner_index; char **name_array; g_assert (GTK_IS_COMBO_BOX (combo_box)); g_assert (CAJA_IS_FILE (file)); if (caja_file_is_gone (file)) { return; } users = caja_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)) { int user_index; /* 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) { char *combo_text; user_name = (char *)node->data; name_array = g_strsplit (user_name, "\n", 2); if (name_array[1] != NULL) { 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, -1); g_strfreev (name_array); g_free (combo_text); } } owner_name = caja_file_get_string_attribute (file, "owner"); owner_index = tree_model_get_entry_index (model, 0, owner_name); /* 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, -1); } name_array = g_strsplit (owner_name, " - ", 2); if (name_array[1] != NULL) { user_name = g_strdup_printf ("%s\n%s", name_array[0], name_array[1]); } else { user_name = g_strdup (name_array[0]); } owner_index = 0; gtk_list_store_prepend (store, &iter); gtk_list_store_set (store, &iter, 0, owner_name, 1, user_name, -1); g_free (user_name); g_strfreev (name_array); } gtk_combo_box_set_active (combo_box, owner_index); g_free (owner_name); g_list_free_full (users, g_free); } static GtkComboBox* attach_owner_combo_box (GtkGrid *grid, GtkWidget *sibling, CajaFile *file) { GtkComboBox *combo_box; combo_box = attach_combo_box (grid, sibling, TRUE); synch_user_menu (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), caja_file_ref (file), (GClosureNotify)caja_file_unref, 0); return combo_box; } static gboolean file_has_prefix (CajaFile *file, GList *prefix_candidates) { GList *p; GFile *location, *candidate_location; location = caja_file_get_location (file); for (p = prefix_candidates; p != NULL; p = p->next) { if (file == p->data) { continue; } candidate_location = caja_file_get_location (CAJA_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 (FMPropertiesWindow *window) { CajaRequestStatus file_status, status; char *text, *temp; guint directory_count; guint file_count; guint total_count; guint unreadable_directory_count; goffset total_size; goffset total_size_on_disk; gboolean used_two_lines; GList *l; guint file_unreadable; goffset file_size; goffset file_size_on_disk; CajaFile *file = NULL; g_assert (FM_IS_PROPERTIES_WINDOW (window)); status = CAJA_REQUEST_DONE; file_status = CAJA_REQUEST_NOT_STARTED; total_count = window->details->total_count; total_size = window->details->total_size; total_size_on_disk = window->details->total_size_on_disk; unreadable_directory_count = FALSE; for (l = window->details->target_files; l; l = l->next) { file = CAJA_FILE (l->data); if (file_has_prefix (file, window->details->target_files)) { /* don't count nested files twice */ continue; } if (caja_file_is_directory (file)) { file_status = caja_file_get_deep_counts (file, &directory_count, &file_count, &file_unreadable, &file_size, &file_size_on_disk, TRUE); total_count += (file_count + directory_count); total_size += file_size; total_size_on_disk += file_size_on_disk; if (file_unreadable) { unreadable_directory_count = TRUE; } if (file_status != CAJA_REQUEST_DONE) { status = file_status; } } else { ++total_count; total_size += caja_file_get_size (file); total_size_on_disk += caja_file_get_size_on_disk (file); } } /* 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->details->deep_count_finished && status != CAJA_REQUEST_DONE) { return; } text = NULL; used_two_lines = FALSE; if (total_count == 0) { switch (status) { case CAJA_REQUEST_DONE: if (unreadable_directory_count == 0) { text = g_strdup (_("nothing")); } else { text = g_strdup (_("unreadable")); } break; default: text = g_strdup ("..."); } } else { char *size_str; char *size_on_disk_str; if (g_settings_get_boolean (caja_preferences, CAJA_PREFERENCES_USE_IEC_UNITS)) { size_str = g_format_size_full (total_size, G_FORMAT_SIZE_IEC_UNITS); size_on_disk_str = g_format_size_full (total_size_on_disk, G_FORMAT_SIZE_IEC_UNITS); } else { size_str = g_format_size (total_size); size_on_disk_str = g_format_size (total_size_on_disk); } text = g_strdup_printf (ngettext("%'d item, with size %s (%s on disk)", "%'d items, totalling %s (%s on disk)", total_count), total_count, size_str, size_on_disk_str); g_free (size_str); g_free (size_on_disk_str); if (unreadable_directory_count != 0) { temp = text; text = g_strconcat (temp, "\n", _("(some contents unreadable)"), NULL); g_free (temp); used_two_lines = TRUE; } } gtk_label_set_text (window->details->directory_contents_value_field, text); g_free (text); /* Also set the title field here, with a trailing carriage return & * space if the value field has two lines. This is a hack to get the * "Contents:" title to line up with the first line of the * 2-line value. Maybe there's a better way to do this, but I * couldn't think of one. */ text = g_strdup (_("Contents:")); if (used_two_lines) { temp = text; text = g_strconcat (temp, "\n ", NULL); g_free (temp); } gtk_label_set_text (window->details->directory_contents_title_field, text); g_free (text); if (status == CAJA_REQUEST_DONE) { window->details->deep_count_finished = TRUE; } } static gboolean update_directory_contents_callback (gpointer data) { FMPropertiesWindow *window; window = FM_PROPERTIES_WINDOW (data); window->details->update_directory_contents_timeout_id = 0; directory_contents_value_field_update (window); return FALSE; } static void schedule_directory_contents_update (FMPropertiesWindow *window) { g_assert (FM_IS_PROPERTIES_WINDOW (window)); if (window->details->update_directory_contents_timeout_id == 0) { window->details->update_directory_contents_timeout_id = g_timeout_add (DIRECTORY_CONTENTS_UPDATE_INTERVAL, update_directory_contents_callback, window); } } static GtkLabel * attach_directory_contents_value_field (FMPropertiesWindow *window, GtkGrid *grid, GtkWidget *sibling) { GtkLabel *value_field; GList *l; CajaFile *file = NULL; value_field = attach_value_label (grid, sibling, ""); g_assert (window->details->directory_contents_value_field == NULL); window->details->directory_contents_value_field = value_field; gtk_label_set_line_wrap (value_field, TRUE); /* Fill in the initial value. */ directory_contents_value_field_update (window); for (l = window->details->target_files; l; l = l->next) { file = CAJA_FILE (l->data); caja_file_recompute_deep_counts (file); g_signal_connect_object (file, "updated_deep_count_in_progress", G_CALLBACK (schedule_directory_contents_update), window, G_CONNECT_SWAPPED); } return value_field; } static GtkLabel * attach_title_field (GtkGrid *grid, const char *title) { return attach_label (grid, NULL, title, FALSE, FALSE, TRUE); } #define INCONSISTENT_STATE_STRING \ "\xE2\x80\x92" static void append_title_value_pair (FMPropertiesWindow *window, GtkGrid *grid, const char *title, const char *file_attribute_name, const char *inconsistent_state, gboolean show_original) { GtkLabel *title_label; GtkWidget *value; title_label = attach_title_field (grid, title); value = attach_value_field (window, grid, GTK_WIDGET (title_label), file_attribute_name, inconsistent_state, show_original); gtk_label_set_mnemonic_widget (title_label, value); } static void append_title_and_ellipsizing_value (FMPropertiesWindow *window, GtkGrid *grid, const char *title, const char *file_attribute_name, const char *inconsistent_state, gboolean show_original) { GtkLabel *title_label; GtkWidget *value; title_label = attach_title_field (grid, title); value = attach_ellipsizing_value_field (window, grid, GTK_WIDGET (title_label), file_attribute_name, inconsistent_state, show_original); gtk_label_set_mnemonic_widget (title_label, value); } static void append_directory_contents_fields (FMPropertiesWindow *window, GtkGrid *grid) { GtkLabel *title_field, *value_field; title_field = attach_title_field (grid, ""); window->details->directory_contents_title_field = title_field; gtk_label_set_line_wrap (title_field, TRUE); value_field = attach_directory_contents_value_field (window, grid, GTK_WIDGET (title_field)); gtk_label_set_mnemonic_widget (title_field, GTK_WIDGET(value_field)); } static GtkWidget * create_page_with_hbox (GtkNotebook *notebook, const char *title) { GtkWidget *hbox; g_assert (GTK_IS_NOTEBOOK (notebook)); g_assert (title != NULL); hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_show (hbox); gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); gtk_box_set_spacing (GTK_BOX (hbox), 12); gtk_notebook_append_page (notebook, hbox, gtk_label_new (title)); return hbox; } static GtkWidget * create_page_with_vbox (GtkNotebook *notebook, const char *title) { GtkWidget *vbox; g_assert (GTK_IS_NOTEBOOK (notebook)); g_assert (title != NULL); vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_widget_show (vbox); gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); gtk_notebook_append_page (notebook, vbox, gtk_label_new (title)); return vbox; } static GtkWidget * append_blank_row (GtkGrid *grid) { return GTK_WIDGET (attach_title_field (grid, "")); } static void append_blank_slim_row (GtkGrid *grid) { GtkWidget *w; PangoAttribute *attribute; PangoAttrList *attr_list; attr_list = pango_attr_list_new (); attribute = pango_attr_scale_new (0.30); pango_attr_list_insert (attr_list, attribute); w = gtk_label_new (NULL); gtk_label_set_attributes (GTK_LABEL (w), attr_list); gtk_widget_show (w); pango_attr_list_unref (attr_list); gtk_container_add (GTK_CONTAINER (grid), w); } static GtkWidget * create_grid_with_standard_properties (void) { GtkWidget *grid; grid = gtk_grid_new (); gtk_container_set_border_width (GTK_CONTAINER (grid), 6); gtk_grid_set_row_spacing (GTK_GRID (grid), ROW_PAD); gtk_grid_set_column_spacing (GTK_GRID (grid), 12); gtk_orientable_set_orientation (GTK_ORIENTABLE (grid), GTK_ORIENTATION_VERTICAL); gtk_widget_show (grid); return grid; } static gboolean is_merged_trash_directory (CajaFile *file) { char *file_uri; gboolean result; file_uri = caja_file_get_uri (file); result = strcmp (file_uri, "trash:///") == 0; g_free (file_uri); return result; } static gboolean is_computer_directory (CajaFile *file) { char *file_uri; gboolean result; file_uri = caja_file_get_uri (file); result = strcmp (file_uri, "computer:///") == 0; g_free (file_uri); return result; } static gboolean is_network_directory (CajaFile *file) { char *file_uri; gboolean result; file_uri = caja_file_get_uri (file); result = strcmp (file_uri, "network:///") == 0; g_free (file_uri); return result; } static gboolean is_burn_directory (CajaFile *file) { char *file_uri; gboolean result; file_uri = caja_file_get_uri (file); result = strcmp (file_uri, "burn:///") == 0; g_free (file_uri); return result; } static gboolean should_show_custom_icon_buttons (FMPropertiesWindow *window) { if (is_multi_file_window (window)) { return FALSE; } return TRUE; } static gboolean should_show_file_type (FMPropertiesWindow *window) { if (!is_multi_file_window (window) && (is_merged_trash_directory (get_target_file (window)) || is_computer_directory (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 (FMPropertiesWindow *window) { if (!is_multi_file_window (window) && (is_merged_trash_directory (get_target_file (window)) || is_computer_directory (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_accessed_date (FMPropertiesWindow *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->details->target_files) || is_multi_file_window (window)) { return FALSE; } return TRUE; } static gboolean should_show_modified_date (FMPropertiesWindow *window) { CajaFile *file; if (is_multi_file_window (window)) return FALSE; file = get_original_file (window); if ((file != NULL) && caja_file_can_unmount (file)) return FALSE; return TRUE; } static gboolean should_show_link_target (FMPropertiesWindow *window) { if (!is_multi_file_window (window) && caja_file_is_symbolic_link (get_target_file (window))) { return TRUE; } return FALSE; } static gboolean should_show_free_space (FMPropertiesWindow *window) { if (!is_multi_file_window (window) && (is_merged_trash_directory (get_target_file (window)) || is_computer_directory (get_target_file (window)) || is_network_directory (get_target_file (window)) || is_burn_directory (get_target_file (window)))) { return FALSE; } if (file_list_all_directories (window->details->target_files)) { return TRUE; } return FALSE; } static gboolean should_show_volume_usage (FMPropertiesWindow *window) { CajaFile *file; gboolean success = FALSE; if (is_multi_file_window (window)) { return FALSE; } file = get_original_file (window); if (file == NULL) { return FALSE; } if (caja_file_can_unmount (file)) { return TRUE; } #ifdef TODO_GIO /* Look at is_mountpoint for activation uri */ #endif return success; } static void paint_used_legend (GtkWidget *widget, cairo_t *cr, gpointer data) { FMPropertiesWindow *window; gint width, height; GtkAllocation allocation; gtk_widget_get_allocation (widget, &allocation); width = allocation.width; height = allocation.height; window = FM_PROPERTIES_WINDOW (data); cairo_rectangle (cr, 2, 2, width - 4, height - 4); gdk_cairo_set_source_rgba (cr, &window->details->used_color); cairo_fill_preserve (cr); gdk_cairo_set_source_rgba (cr, &window->details->used_stroke_color); cairo_stroke (cr); } static void paint_free_legend (GtkWidget *widget, cairo_t *cr, gpointer data) { FMPropertiesWindow *window; gint width, height; GtkAllocation allocation; window = FM_PROPERTIES_WINDOW (data); gtk_widget_get_allocation (widget, &allocation); width = allocation.width; height = allocation.height; cairo_rectangle (cr, 2, 2, width - 4, height - 4); gdk_cairo_set_source_rgba (cr, &window->details->free_color); cairo_fill_preserve(cr); gdk_cairo_set_source_rgba (cr, &window->details->free_stroke_color); cairo_stroke (cr); } static void paint_pie_chart (GtkWidget *widget, cairo_t *cr, gpointer data) { FMPropertiesWindow *window; gint width, height; double free, used; double angle1, angle2, split, xc, yc, radius; GtkAllocation allocation; window = FM_PROPERTIES_WINDOW (data); gtk_widget_get_allocation (widget, &allocation); width = allocation.width; height = allocation.height; free = (double)window->details->volume_free / (double)window->details->volume_capacity; used = 1.0 - free; angle1 = free * 2 * G_PI; angle2 = used * 2 * G_PI; split = (2 * G_PI - angle1) * .5; xc = width / 2; yc = height / 2; if (width < height) { radius = width / 2 - 8; } else { radius = height / 2 - 8; } if (angle1 != 2 * G_PI && angle1 != 0) { angle1 = angle1 + split; } if (angle2 != 2 * G_PI && angle2 != 0) { angle2 = angle2 - split; } if (used > 0) { if (free != 0) { cairo_move_to (cr,xc,yc); } cairo_arc (cr, xc, yc, radius, angle1, angle2); if (free != 0) { cairo_line_to (cr,xc,yc); } gdk_cairo_set_source_rgba (cr, &window->details->used_color); cairo_fill_preserve (cr); gdk_cairo_set_source_rgba (cr, &window->details->used_stroke_color); cairo_stroke (cr); } if (free > 0) { if (used != 0) { cairo_move_to (cr,xc,yc); } cairo_arc_negative (cr, xc, yc, radius, angle1, angle2); if (used != 0) { cairo_line_to (cr,xc,yc); } gdk_cairo_set_source_rgba (cr, &window->details->free_color); cairo_fill_preserve(cr); gdk_cairo_set_source_rgba (cr, &window->details->free_stroke_color); cairo_stroke (cr); } } /* Copied from gtk/gtkstyle.c */ static void rgb_to_hls (gdouble *r, gdouble *g, gdouble *b) { gdouble min; gdouble max; gdouble red; gdouble green; gdouble blue; gdouble h, l, s; gdouble delta; red = *r; green = *g; blue = *b; if (red > green) { if (red > blue) max = red; else max = blue; if (green < blue) min = green; else min = blue; } else { if (green > blue) max = green; else max = blue; if (red < blue) min = red; else min = blue; } l = (max + min) / 2; s = 0; h = 0; if (max != min) { if (l <= 0.5) s = (max - min) / (max + min); else s = (max - min) / (2 - max - min); delta = max -min; if (red == max) h = (green - blue) / delta; else if (green == max) h = 2 + (blue - red) / delta; else if (blue == max) h = 4 + (red - green) / delta; h *= 60; if (h < 0.0) h += 360; } *r = h; *g = l; *b = s; } static void hls_to_rgb (gdouble *h, gdouble *l, gdouble *s) { gdouble hue; gdouble lightness; gdouble saturation; gdouble m1, m2; gdouble r, g, b; lightness = *l; saturation = *s; if (lightness <= 0.5) m2 = lightness * (1 + saturation); else m2 = lightness + saturation - lightness * saturation; m1 = 2 * lightness - m2; if (saturation == 0) { *h = lightness; *l = lightness; *s = lightness; } else { hue = *h + 120; while (hue > 360) hue -= 360; while (hue < 0) hue += 360; if (hue < 60) r = m1 + (m2 - m1) * hue / 60; else if (hue < 180) r = m2; else if (hue < 240) r = m1 + (m2 - m1) * (240 - hue) / 60; else r = m1; hue = *h; while (hue > 360) hue -= 360; while (hue < 0) hue += 360; if (hue < 60) g = m1 + (m2 - m1) * hue / 60; else if (hue < 180) g = m2; else if (hue < 240) g = m1 + (m2 - m1) * (240 - hue) / 60; else g = m1; hue = *h - 120; while (hue > 360) hue -= 360; while (hue < 0) hue += 360; if (hue < 60) b = m1 + (m2 - m1) * hue / 60; else if (hue < 180) b = m2; else if (hue < 240) b = m1 + (m2 - m1) * (240 - hue) / 60; else b = m1; *h = r; *l = g; *s = b; } } static void _pie_style_shade (GdkRGBA *a, GdkRGBA *b, gdouble k) { gdouble red; gdouble green; gdouble blue; red = a->red; green = a->green; blue = a->blue; rgb_to_hls (&red, &green, &blue); green *= k; if (green > 1.0) green = 1.0; else if (green < 0.0) green = 0.0; blue *= k; if (blue > 1.0) blue = 1.0; else if (blue < 0.0) blue = 0.0; hls_to_rgb (&red, &green, &blue); b->red = red; b->green = green; b->blue = blue; b->alpha = a->alpha; } static GtkWidget* create_pie_widget (FMPropertiesWindow *window) { CajaFile *file; GtkGrid *grid; GtkStyleContext *style; GtkWidget *pie_canvas; GtkWidget *used_canvas; GtkWidget *used_label; GtkWidget *free_canvas; GtkWidget *free_label; GtkWidget *capacity_label; GtkWidget *fstype_label; gchar *capacity; gchar *used; gchar *free; gchar *uri; gchar *concat; GFile *location; GFileInfo *info; if (g_settings_get_boolean (caja_preferences, CAJA_PREFERENCES_USE_IEC_UNITS)) { capacity = g_format_size_full(window->details->volume_capacity, G_FORMAT_SIZE_IEC_UNITS); free = g_format_size_full(window->details->volume_free, G_FORMAT_SIZE_IEC_UNITS); used = g_format_size_full(window->details->volume_capacity - window->details->volume_free, G_FORMAT_SIZE_IEC_UNITS); } else { capacity = g_format_size(window->details->volume_capacity); free = g_format_size(window->details->volume_free); used = g_format_size(window->details->volume_capacity - window->details->volume_free); } file = get_original_file (window); uri = caja_file_get_activation_uri (file); grid = GTK_GRID (gtk_grid_new ()); gtk_container_set_border_width (GTK_CONTAINER (grid), 5); gtk_grid_set_column_spacing (GTK_GRID (grid), 5); style = gtk_widget_get_style_context (GTK_WIDGET (grid)); if (!gtk_style_context_lookup_color (style, "chart_rgba_1", &window->details->used_color)) { window->details->used_color.red = USED_FILL_R; window->details->used_color.green = USED_FILL_G; window->details->used_color.blue = USED_FILL_B; window->details->used_color.alpha = 1; } if (!gtk_style_context_lookup_color (style, "chart_rgba_2", &window->details->free_color)) { window->details->free_color.red = FREE_FILL_R; window->details->free_color.green = FREE_FILL_G; window->details->free_color.blue = FREE_FILL_B; window->details->free_color.alpha = 1; } _pie_style_shade (&window->details->used_color, &window->details->used_stroke_color, 0.7); _pie_style_shade (&window->details->free_color, &window->details->free_stroke_color, 0.7); pie_canvas = gtk_drawing_area_new (); gtk_widget_set_size_request (pie_canvas, 200, 200); used_canvas = gtk_drawing_area_new (); gtk_widget_set_valign (used_canvas, GTK_ALIGN_CENTER); gtk_widget_set_halign (used_canvas, GTK_ALIGN_CENTER); gtk_widget_set_size_request (used_canvas, 20, 20); /* Translators: "used" refers to the capacity of the filesystem */ concat = g_strconcat (used, " ", _("used"), NULL); used_label = gtk_label_new (concat); g_free (concat); free_canvas = gtk_drawing_area_new (); gtk_widget_set_valign (free_canvas, GTK_ALIGN_CENTER); gtk_widget_set_halign (free_canvas, GTK_ALIGN_CENTER); gtk_widget_set_size_request (free_canvas, 20, 20); /* Translators: "free" refers to the capacity of the filesystem */ concat = g_strconcat (free, " ", _("free"), NULL); free_label = gtk_label_new (concat); g_free (concat); concat = g_strconcat (_("Total capacity:"), " ", capacity, NULL); capacity_label = gtk_label_new (concat); g_free (concat); fstype_label = gtk_label_new (NULL); location = g_file_new_for_uri (uri); info = g_file_query_filesystem_info (location, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, NULL, NULL); if (info) { const char *fs_type; fs_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE); if (fs_type != NULL) { concat = g_strconcat (_("Filesystem type:"), " ", fs_type, NULL); gtk_label_set_text (GTK_LABEL (fstype_label), concat); g_free (concat); } g_object_unref (info); } g_object_unref (location); g_free (uri); g_free (capacity); g_free (used); g_free (free); gtk_container_add_with_properties (GTK_CONTAINER (grid), pie_canvas, "height", 4, NULL); gtk_grid_attach_next_to (grid, used_canvas, pie_canvas, GTK_POS_RIGHT, 1, 1); gtk_grid_attach_next_to (grid, used_label, used_canvas, GTK_POS_RIGHT, 1, 1); gtk_grid_attach_next_to (grid, free_canvas, used_canvas, GTK_POS_BOTTOM, 1, 1); gtk_grid_attach_next_to (grid, free_label, free_canvas, GTK_POS_RIGHT, 1, 1); gtk_grid_attach_next_to (grid, capacity_label, free_canvas, GTK_POS_BOTTOM, 2, 1); gtk_grid_attach_next_to (grid, fstype_label, capacity_label, GTK_POS_BOTTOM, 2, 1); g_signal_connect (pie_canvas, "draw", G_CALLBACK (paint_pie_chart), window); g_signal_connect (used_canvas, "draw", G_CALLBACK (paint_used_legend), window); g_signal_connect (free_canvas, "draw", G_CALLBACK (paint_free_legend), window); return GTK_WIDGET (grid); } static GtkWidget* create_volume_usage_widget (FMPropertiesWindow *window) { GtkWidget *piewidget; gchar *uri; CajaFile *file; GFile *location; GFileInfo *info; file = get_original_file (window); uri = caja_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->details->volume_capacity = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE); window->details->volume_free = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); g_object_unref (info); } else { window->details->volume_capacity = 0; window->details->volume_free = 0; } g_object_unref (location); piewidget = create_pie_widget (window); gtk_widget_show_all (piewidget); return piewidget; } static void create_basic_page (FMPropertiesWindow *window) { GtkGrid *grid; GtkWidget *icon_pixmap_widget; GtkWidget *hbox, *vbox; hbox = create_page_with_hbox (window->details->notebook, _("Basic")); /* Icon pixmap */ icon_pixmap_widget = create_image_widget ( window, should_show_custom_icon_buttons (window)); gtk_widget_set_halign (icon_pixmap_widget, GTK_ALIGN_END); gtk_widget_set_valign (icon_pixmap_widget, GTK_ALIGN_START); gtk_widget_show (icon_pixmap_widget); gtk_box_pack_start (GTK_BOX (hbox), icon_pixmap_widget, FALSE, FALSE, 0); window->details->icon_chooser = NULL; vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_widget_show (vbox); gtk_container_add (GTK_CONTAINER (hbox), vbox); grid = GTK_GRID (create_grid_with_standard_properties ()); gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (grid), FALSE, FALSE, 0); window->details->basic_grid = grid; /* Name label. The text will be determined in update_name_field */ window->details->name_label = attach_title_field (grid, NULL); /* Name field */ window->details->name_field = NULL; update_name_field (window); /* Start with name field selected, if it's an entry. */ if (CAJA_IS_ENTRY (window->details->name_field)) { caja_entry_select_all (CAJA_ENTRY (window->details->name_field)); gtk_widget_grab_focus (GTK_WIDGET (window->details->name_field)); } if (fm_ditem_page_should_show (window->details->target_files)) { GtkSizeGroup *label_size_group; GtkWidget *box; label_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); gtk_size_group_add_widget (label_size_group, GTK_WIDGET (window->details->name_label)); box = fm_ditem_page_make_box (label_size_group, window->details->target_files); gtk_grid_attach_next_to (window->details->basic_grid, box, GTK_WIDGET (window->details->name_label), GTK_POS_BOTTOM, 2, 1); } if (should_show_file_type (window)) { append_title_value_pair (window, grid, _("Type:"), "type", INCONSISTENT_STATE_STRING, FALSE); } if (should_show_link_target (window)) { append_title_and_ellipsizing_value (window, grid, _("Link target:"), "link_target", INCONSISTENT_STATE_STRING, FALSE); } if (is_multi_file_window (window) || caja_file_is_directory (get_target_file (window))) { append_directory_contents_fields (window, grid); } else { append_title_value_pair (window, grid, _("Size:"), "size_detail", INCONSISTENT_STATE_STRING, FALSE); append_title_value_pair (window, grid, _("Size on Disk:"), "size_on_disk_detail", INCONSISTENT_STATE_STRING, FALSE); } append_blank_row (grid); if (should_show_location_info (window)) { append_title_and_ellipsizing_value (window, grid, _("Location:"), "where", INCONSISTENT_STATE_STRING, TRUE); append_title_and_ellipsizing_value (window, grid, _("Volume:"), "volume", INCONSISTENT_STATE_STRING, FALSE); } if (should_show_accessed_date (window) || should_show_modified_date (window)) { append_blank_row (grid); } if (should_show_accessed_date (window)) { append_title_value_pair (window, grid, _("Accessed:"), "date_accessed", INCONSISTENT_STATE_STRING, FALSE); } if (should_show_modified_date (window)) { append_title_value_pair (window, grid, _("Modified:"), "date_modified", INCONSISTENT_STATE_STRING, FALSE); } if (should_show_free_space (window)) { append_blank_row (grid); append_title_value_pair (window, grid, _("Free space:"), "free_space", INCONSISTENT_STATE_STRING, FALSE); } if (should_show_volume_usage (window)) { GtkWidget *volume_usage; volume_usage = create_volume_usage_widget (window); gtk_container_add_with_properties (GTK_CONTAINER (grid), volume_usage, "width", 2, NULL); } } static GHashTable * get_initial_emblems (GList *files) { GHashTable *ret; GList *l; ret = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) eel_g_list_free_deep); for (l = files; l != NULL; l = l->next) { CajaFile *file; GList *keywords; file = CAJA_FILE (l->data); keywords = caja_file_get_keywords (file); g_hash_table_insert (ret, file, keywords); } return ret; } static gboolean files_has_directory (FMPropertiesWindow *window) { GList *l; for (l = window->details->target_files; l != NULL; l = l->next) { CajaFile *file; file = CAJA_FILE (l->data); if (caja_file_is_directory (file)) { return TRUE; } } return FALSE; } static gboolean files_has_changable_permissions_directory (FMPropertiesWindow *window) { GList *l; for (l = window->details->target_files; l != NULL; l = l->next) { CajaFile *file; file = CAJA_FILE (l->data); if (caja_file_is_directory (file) && caja_file_can_get_permissions (file) && caja_file_can_set_permissions (file)) { return TRUE; } } return FALSE; } static gboolean files_has_file (FMPropertiesWindow *window) { GList *l; for (l = window->details->target_files; l != NULL; l = l->next) { CajaFile *file; file = CAJA_FILE (l->data); if (!caja_file_is_directory (file)) { return TRUE; } } return FALSE; } static void create_emblems_page (FMPropertiesWindow *window) { GtkWidget *emblems_table, *button, *scroller; GdkPixbuf *pixbuf; char *label; GList *icons, *l; CajaIconInfo *info; gint scale; /* The emblems wrapped table */ scroller = eel_scrolled_wrap_table_new (TRUE, GTK_SHADOW_NONE, &emblems_table); gtk_container_set_border_width (GTK_CONTAINER (emblems_table), 12); /* stop GTK 3.22 builds from ballooning the properties dialog to full screen height */ gtk_scrolled_window_set_max_content_height (GTK_SCROLLED_WINDOW (scroller), 300); gtk_widget_show (scroller); gtk_notebook_append_page (window->details->notebook, scroller, gtk_label_new (_("Emblems"))); icons = caja_emblem_list_available (); scale = gtk_widget_get_scale_factor (scroller); window->details->initial_emblems = get_initial_emblems (window->details->original_files); l = icons; while (l != NULL) { char *emblem_name; emblem_name = l->data; l = l->next; if (!caja_emblem_should_show_in_list (emblem_name)) { continue; } info = caja_icon_info_lookup_from_name (emblem_name, CAJA_ICON_SIZE_SMALL, scale); pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, CAJA_ICON_SIZE_SMALL); if (pixbuf == NULL) { continue; } label = g_strdup (caja_icon_info_get_display_name (info)); g_object_unref (info); if (label == NULL) { label = caja_emblem_get_keyword_from_icon_name (emblem_name); } button = eel_labeled_image_check_button_new (label, pixbuf); eel_labeled_image_set_fixed_image_height (EEL_LABELED_IMAGE (gtk_bin_get_child (GTK_BIN (button))), STANDARD_EMBLEM_HEIGHT * scale); eel_labeled_image_set_spacing (EEL_LABELED_IMAGE (gtk_bin_get_child (GTK_BIN (button))), EMBLEM_LABEL_SPACING * scale); g_free (label); g_object_unref (pixbuf); /* Attach parameters and signal handler. */ g_object_set_data_full (G_OBJECT (button), "caja_emblem_name", caja_emblem_get_keyword_from_icon_name (emblem_name), g_free); window->details->emblem_buttons = g_list_append (window->details->emblem_buttons, button); g_signal_connect_object (button, "toggled", G_CALLBACK (emblem_button_toggled), G_OBJECT (window), 0); gtk_container_add (GTK_CONTAINER (emblems_table), button); } g_list_free_full (icons, g_free); gtk_widget_show_all (emblems_table); } static void start_long_operation (FMPropertiesWindow *window) { if (window->details->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->details->long_operation_underway ++; } static void end_long_operation (FMPropertiesWindow *window) { if (gtk_widget_get_window (GTK_WIDGET (window)) != NULL && window->details->long_operation_underway == 1) { /* finished !! */ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), NULL); } window->details->long_operation_underway--; } static void permission_change_callback (CajaFile *file, GFile *res_loc, GError *error, gpointer callback_data) { FMPropertiesWindow *window; g_assert (callback_data != NULL); window = FM_PROPERTIES_WINDOW (callback_data); end_long_operation (window); /* Report the error if it's an error. */ fm_report_error_setting_permissions (file, error, NULL); g_object_unref (window); } static void update_permissions (FMPropertiesWindow *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->details->target_files; l != NULL; l = l->next) { CajaFile *file; guint32 permissions; file = CAJA_FILE (l->data); if (!caja_file_can_get_permissions (file)) { continue; } if (!apply_to_both_folder_and_dir && ((caja_file_is_directory (file) && !is_folder) || (!caja_file_is_directory (file) && is_folder))) { continue; } permissions = caja_file_get_permissions (file); if (use_original) { gpointer ptr; if (g_hash_table_lookup_extended (window->details->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); caja_file_set_permissions (file, permissions, permission_change_callback, window); } } static gboolean initial_permission_state_consistent (FMPropertiesWindow *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->details->target_files; l != NULL; l = l->next) { CajaFile *file; guint32 permissions; file = l->data; if (!both_folder_and_dir && ((caja_file_is_directory (file) && !is_folder) || (!caja_file_is_directory (file) && is_folder))) { continue; } permissions = GPOINTER_TO_INT (g_hash_table_lookup (window->details->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, FMPropertiesWindow *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 (!window->details->has_recursive_apply && 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 (FMPropertiesWindow *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; if (gtk_toggle_button_get_inconsistent (button) && window->details->has_recursive_apply) { /* Never change from an inconsistent state if we have dirs, even * if the current state is now consistent, because its a useful * state for recursive apply. */ return; } 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->details->target_files; l != NULL; l = l->next) { CajaFile *file; guint32 file_permissions; file = CAJA_FILE (l->data); if (!caja_file_can_get_permissions (file)) { continue; } if (!is_special && ((caja_file_is_directory (file) && !is_folder) || (!caja_file_is_directory (file) && is_folder))) { continue; } no_match = FALSE; file_permissions = caja_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 (caja_file_can_set_permissions (file)) { all_cannot_set = FALSE; } } sensitive = !all_cannot_set; if (!is_folder) { /* Don't insitive files when we have recursive apply */ sensitive |= window->details->has_recursive_apply; } 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 set_up_permissions_checkbox (FMPropertiesWindow *window, GtkWidget *check_button, guint32 permission, gboolean is_folder) { /* Load up the check_button with data we'll need when updating its state. */ g_object_set_data (G_OBJECT (check_button), "permission", GINT_TO_POINTER (permission)); g_object_set_data (G_OBJECT (check_button), "properties_window", window); g_object_set_data (G_OBJECT (check_button), "is-folder", GINT_TO_POINTER (is_folder)); window->details->permission_buttons = g_list_prepend (window->details->permission_buttons, check_button); g_signal_connect_object (check_button, "toggled", G_CALLBACK (permission_button_toggled), window, 0); } static GtkWidget * add_permissions_checkbox_with_label (FMPropertiesWindow *window, GtkGrid *grid, GtkWidget *sibling, const char *label, guint32 permission_to_check, GtkLabel *label_for, gboolean is_folder) { GtkWidget *check_button; gboolean a11y_enabled; check_button = gtk_check_button_new_with_mnemonic (label); gtk_widget_show (check_button); if (sibling) { gtk_grid_attach_next_to (grid, check_button, sibling, GTK_POS_RIGHT, 1, 1); } else { gtk_container_add (GTK_CONTAINER (grid), check_button); } set_up_permissions_checkbox (window, check_button, permission_to_check, is_folder); a11y_enabled = GTK_IS_ACCESSIBLE (gtk_widget_get_accessible (check_button)); if (a11y_enabled && label_for != NULL) { eel_accessibility_set_up_label_widget_relation (GTK_WIDGET (label_for), check_button); } return check_button; } static GtkWidget * add_permissions_checkbox (FMPropertiesWindow *window, GtkGrid *grid, GtkWidget *sibling, CheckboxType type, guint32 permission_to_check, GtkLabel *label_for, gboolean is_folder) { const gchar *label; if (type == PERMISSIONS_CHECKBOXES_READ) { label = _("_Read"); } else if (type == PERMISSIONS_CHECKBOXES_WRITE) { label = _("_Write"); } else { label = _("E_xecute"); } return add_permissions_checkbox_with_label (window, grid, sibling, label, permission_to_check, label_for, is_folder); } 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, FMPropertiesWindow *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, 1, &new_perm, 2, &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, 2, &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, 0, "---", 1, 0, 2, TRUE, -1); } } static void permission_combo_update (FMPropertiesWindow *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, 2, &is_multi, -1); } if (is_multi && window->details->has_recursive_apply) { /* Never change from an inconsistent state if we have dirs, even * if the current state is now consistent, because its a useful * state for recursive apply. */ return; } 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->details->target_files; l != NULL; l = l->next) { CajaFile *file; guint32 file_permissions; file = CAJA_FILE (l->data); if (!caja_file_can_get_permissions (file)) { continue; } if (caja_file_is_directory (file)) { mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC; } else { mask = PERMISSION_READ|PERMISSION_WRITE; } file_permissions = caja_file_get_permissions (file); perm = permission_from_vfs (type, file_permissions) & mask; if (caja_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 (caja_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 (caja_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 || window->details->has_recursive_apply; } 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 add_permissions_combo_box (FMPropertiesWindow *window, GtkGrid *grid, PermissionType type, gboolean is_folder, gboolean short_label) { GtkWidget *combo; GtkLabel *label; GtkListStore *store; GtkCellRenderer *cell; GtkTreeIter iter; if (short_label) { label = attach_title_field (grid, _("Access:")); } else if (is_folder) { label = attach_title_field (grid, _("Folder access:")); } else { label = attach_title_field (grid, _("File access:")); } store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN); combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store)); 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); /* Translators: this is referred to the permissions * the user has in a directory. */ gtk_list_store_set (store, &iter, 0, _("None"), 1, 0, -1); } gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, _("List files only"), 1, PERMISSION_READ, -1); gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, _("Access files"), 1, PERMISSION_READ|PERMISSION_EXEC, -1); gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, _("Create and delete files"), 1, PERMISSION_READ|PERMISSION_EXEC|PERMISSION_WRITE, -1); } else { if (type != PERMISSION_USER) { gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, _("None"), 1, 0, -1); } gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, _("Read-only"), 1, PERMISSION_READ, -1); gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, _("Read and write"), 1, PERMISSION_READ|PERMISSION_WRITE, -1); } if (window->details->has_recursive_apply) { permission_combo_add_multiple_choice (GTK_COMBO_BOX (combo), &iter); } g_object_unref (store); window->details->permission_combos = g_list_prepend (window->details->permission_combos, combo); g_signal_connect (combo, "changed", G_CALLBACK (permission_combo_changed), window); 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", 0, NULL); gtk_label_set_mnemonic_widget (label, combo); gtk_widget_show (combo); gtk_grid_attach_next_to (grid, combo, GTK_WIDGET (label), GTK_POS_RIGHT, 1, 1); } static GtkWidget * append_special_execution_checkbox (FMPropertiesWindow *window, GtkGrid *grid, GtkWidget *sibling, const char *label_text, guint32 permission_to_check) { GtkWidget *check_button; check_button = gtk_check_button_new_with_mnemonic (label_text); gtk_widget_show (check_button); if (sibling != NULL) { gtk_grid_attach_next_to (grid, check_button, sibling, GTK_POS_RIGHT, 1, 1); } else { gtk_container_add_with_properties (GTK_CONTAINER (grid), check_button, "left-attach", 1, NULL); } set_up_permissions_checkbox (window, check_button, permission_to_check, FALSE); g_object_set_data (G_OBJECT (check_button), "is-special", GINT_TO_POINTER (TRUE)); return check_button; } static void append_special_execution_flags (FMPropertiesWindow *window, GtkGrid *grid) { GtkWidget *title; append_blank_slim_row (grid); title = GTK_WIDGET (attach_title_field (grid, _("Special flags:"))); append_special_execution_checkbox (window, grid, title, _("Set _user ID"), UNIX_PERM_SUID); append_special_execution_checkbox (window, grid, NULL, _("Set gro_up ID"), UNIX_PERM_SGID); append_special_execution_checkbox (window, grid, NULL, _("_Sticky"), UNIX_PERM_STICKY); } static gboolean all_can_get_permissions (GList *file_list) { GList *l; for (l = file_list; l != NULL; l = l->next) { CajaFile *file; file = CAJA_FILE (l->data); if (!caja_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) { CajaFile *file; file = CAJA_FILE (l->data); if (!caja_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; CajaFile *file; file = CAJA_FILE (l->data); permissions = caja_file_get_permissions (file); g_hash_table_insert (ret, file, GINT_TO_POINTER (permissions)); } return ret; } static void create_simple_permissions (FMPropertiesWindow *window, GtkGrid *page_grid) { gboolean has_file, has_directory; GtkLabel *group_label; GtkLabel *owner_label; GtkLabel *execute_label; GtkWidget *value; has_file = files_has_file (window); has_directory = files_has_directory (window); if (!is_multi_file_window (window) && caja_file_can_set_owner (get_target_file (window))) { GtkComboBox *owner_combo_box; owner_label = attach_title_field (page_grid, _("_Owner:")); /* Combo box in this case. */ owner_combo_box = attach_owner_combo_box (page_grid, GTK_WIDGET (owner_label), get_target_file (window)); gtk_label_set_mnemonic_widget (owner_label, GTK_WIDGET (owner_combo_box)); } else { owner_label = attach_title_field (page_grid, _("Owner:")); /* Static text in this case. */ value = attach_value_field (window, page_grid, GTK_WIDGET (owner_label), "owner", INCONSISTENT_STATE_STRING, FALSE); gtk_label_set_mnemonic_widget (owner_label, value); } if (has_directory) { add_permissions_combo_box (window, page_grid, PERMISSION_USER, TRUE, FALSE); } if (has_file || window->details->has_recursive_apply) { add_permissions_combo_box (window, page_grid, PERMISSION_USER, FALSE, !has_directory); } append_blank_slim_row (page_grid); if (!is_multi_file_window (window) && caja_file_can_set_group (get_target_file (window))) { GtkComboBox *group_combo_box; group_label = attach_title_field (page_grid, _("_Group:")); /* Combo box in this case. */ group_combo_box = attach_group_combo_box (page_grid, GTK_WIDGET (group_label), get_target_file (window)); gtk_label_set_mnemonic_widget (group_label, GTK_WIDGET (group_combo_box)); } else { group_label = attach_title_field (page_grid, _("Group:")); /* Static text in this case. */ value = attach_value_field (window, page_grid, GTK_WIDGET (group_label), "group", INCONSISTENT_STATE_STRING, FALSE); gtk_label_set_mnemonic_widget (group_label, value); } if (has_directory) { add_permissions_combo_box (window, page_grid, PERMISSION_GROUP, TRUE, FALSE); } if (has_file || window->details->has_recursive_apply) { add_permissions_combo_box (window, page_grid, PERMISSION_GROUP, FALSE, !has_directory); } append_blank_slim_row (page_grid); group_label = attach_title_field (page_grid, _("Others")); if (has_directory) { add_permissions_combo_box (window, page_grid, PERMISSION_OTHER, TRUE, FALSE); } if (has_file || window->details->has_recursive_apply) { add_permissions_combo_box (window, page_grid, PERMISSION_OTHER, FALSE, !has_directory); } append_blank_slim_row (page_grid); execute_label = attach_title_field (page_grid, _("Execute:")); add_permissions_checkbox_with_label (window, page_grid, GTK_WIDGET (execute_label), _("Allow _executing file as program"), UNIX_PERM_USER_EXEC|UNIX_PERM_GROUP_EXEC|UNIX_PERM_OTHER_EXEC, execute_label, FALSE); } static void create_permission_checkboxes (FMPropertiesWindow *window, GtkGrid *page_grid, gboolean is_folder) { GtkLabel *owner_perm_label; GtkLabel *group_perm_label; GtkLabel *other_perm_label; GtkGrid *check_button_grid; GtkWidget *w; owner_perm_label = attach_title_field (page_grid, _("Owner:")); group_perm_label = attach_title_field (page_grid, _("Group:")); other_perm_label = attach_title_field (page_grid, _("Others:")); check_button_grid = GTK_GRID (create_grid_with_standard_properties ()); gtk_widget_show (GTK_WIDGET (check_button_grid)); gtk_grid_attach_next_to (page_grid, GTK_WIDGET (check_button_grid), GTK_WIDGET (owner_perm_label), GTK_POS_RIGHT, 1, 3); w = add_permissions_checkbox (window, check_button_grid, NULL, PERMISSIONS_CHECKBOXES_READ, UNIX_PERM_USER_READ, owner_perm_label, is_folder); w = add_permissions_checkbox (window, check_button_grid, w, PERMISSIONS_CHECKBOXES_WRITE, UNIX_PERM_USER_WRITE, owner_perm_label, is_folder); w = add_permissions_checkbox (window, check_button_grid, w, PERMISSIONS_CHECKBOXES_EXECUTE, UNIX_PERM_USER_EXEC, owner_perm_label, is_folder); w = add_permissions_checkbox (window, check_button_grid, NULL, PERMISSIONS_CHECKBOXES_READ, UNIX_PERM_GROUP_READ, group_perm_label, is_folder); w = add_permissions_checkbox (window, check_button_grid, w, PERMISSIONS_CHECKBOXES_WRITE, UNIX_PERM_GROUP_WRITE, group_perm_label, is_folder); w = add_permissions_checkbox (window, check_button_grid, w, PERMISSIONS_CHECKBOXES_EXECUTE, UNIX_PERM_GROUP_EXEC, group_perm_label, is_folder); w = add_permissions_checkbox (window, check_button_grid, NULL, PERMISSIONS_CHECKBOXES_READ, UNIX_PERM_OTHER_READ, other_perm_label, is_folder); w = add_permissions_checkbox (window, check_button_grid, w, PERMISSIONS_CHECKBOXES_WRITE, UNIX_PERM_OTHER_WRITE, other_perm_label, is_folder); add_permissions_checkbox (window, check_button_grid, w, PERMISSIONS_CHECKBOXES_EXECUTE, UNIX_PERM_OTHER_EXEC, other_perm_label, is_folder); } static void create_advanced_permissions (FMPropertiesWindow *window, GtkGrid *page_grid) { GtkLabel *group_label; GtkLabel *owner_label; gboolean has_directory, has_file; if (!is_multi_file_window (window) && caja_file_can_set_owner (get_target_file (window))) { GtkComboBox *owner_combo_box; owner_label = attach_title_field (page_grid, _("_Owner:")); /* Combo box in this case. */ owner_combo_box = attach_owner_combo_box (page_grid, GTK_WIDGET (owner_label), get_target_file (window)); gtk_label_set_mnemonic_widget (owner_label, GTK_WIDGET (owner_combo_box)); } else { GtkWidget *value; owner_label = attach_title_field (page_grid, _("Owner:")); /* Static text in this case. */ value = attach_value_field (window, page_grid, GTK_WIDGET (owner_label), "owner", INCONSISTENT_STATE_STRING, FALSE); gtk_label_set_mnemonic_widget (owner_label, value); } if (!is_multi_file_window (window) && caja_file_can_set_group (get_target_file (window))) { GtkComboBox *group_combo_box; group_label = attach_title_field (page_grid, _("_Group:")); /* Combo box in this case. */ group_combo_box = attach_group_combo_box (page_grid, GTK_WIDGET (group_label), get_target_file (window)); gtk_label_set_mnemonic_widget (group_label, GTK_WIDGET (group_combo_box)); } else { group_label = attach_title_field (page_grid, _("Group:")); /* Static text in this case. */ attach_value_field (window, page_grid, GTK_WIDGET (group_label), "group", INCONSISTENT_STATE_STRING, FALSE); } append_blank_slim_row (page_grid); has_directory = files_has_directory (window); has_file = files_has_file (window); if (has_directory) { if (has_file || window->details->has_recursive_apply) { attach_title_field (page_grid, _("Folder Permissions:")); } create_permission_checkboxes (window, page_grid, TRUE); } if (has_file || window->details->has_recursive_apply) { if (has_directory) { attach_title_field (page_grid, _("File Permissions:")); } create_permission_checkboxes (window, page_grid, FALSE); } append_blank_slim_row (page_grid); append_special_execution_flags (window, page_grid); append_title_value_pair (window, page_grid, _("Text view:"), "permissions", INCONSISTENT_STATE_STRING, FALSE); } static void set_recursive_permissions_done (gpointer callback_data) { FMPropertiesWindow *window; window = FM_PROPERTIES_WINDOW (callback_data); end_long_operation (window); g_object_unref (window); } static void apply_recursive_clicked (GtkWidget *recursive_button, FMPropertiesWindow *window) { guint32 file_permission, file_permission_mask; guint32 dir_permission, dir_permission_mask; guint32 vfs_mask, vfs_new_perm, p; gboolean active, is_folder, is_special, use_original; GList *l; GtkTreeModel *model; GtkTreeIter iter; PermissionType type; int new_perm, mask; GtkWidget *button = NULL; GtkWidget *combo = NULL; file_permission = 0; file_permission_mask = 0; dir_permission = 0; dir_permission_mask = 0; /* Advanced mode and execute checkbox: */ for (l = window->details->permission_buttons; l != NULL; l = l->next) { button = l->data; if (gtk_toggle_button_get_inconsistent (GTK_TOGGLE_BUTTON (button))) { continue; } active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)); p = 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 (is_folder || is_special) { dir_permission_mask |= p; if (active) { dir_permission |= p; } } if (!is_folder || is_special) { file_permission_mask |= p; if (active) { file_permission |= p; } } } /* Simple mode, minus exec checkbox */ for (l = window->details->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, 1, &new_perm, 2, &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->details->target_files; l != NULL; l = l->next) { CajaFile *file; file = CAJA_FILE (l->data); if (caja_file_is_directory (file) && caja_file_can_set_permissions (file)) { char *uri; uri = caja_file_get_uri (file); start_long_operation (window); g_object_ref (window); caja_file_set_permissions_recursive (uri, file_permission, file_permission_mask, dir_permission, dir_permission_mask, set_recursive_permissions_done, window); g_free (uri); } } } static void create_permissions_page (FMPropertiesWindow *window) { GtkWidget *vbox; GList *file_list; vbox = create_page_with_vbox (window->details->notebook, _("Permissions")); file_list = window->details->original_files; window->details->initial_permissions = NULL; if (all_can_get_permissions (file_list) && all_can_get_permissions (window->details->target_files)) { GtkGrid *page_grid; window->details->initial_permissions = get_initial_permissions (window->details->target_files); window->details->has_recursive_apply = files_has_changable_permissions_directory (window); if (!all_can_set_permissions (file_list)) { add_prompt_and_separator ( vbox, _("You are not the owner, so you cannot change these permissions.")); } page_grid = GTK_GRID (create_grid_with_standard_properties ()); gtk_widget_show (GTK_WIDGET (page_grid)); gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (page_grid), TRUE, TRUE, 0); if (g_settings_get_boolean (caja_preferences, CAJA_PREFERENCES_SHOW_ADVANCED_PERMISSIONS)) { create_advanced_permissions (window, page_grid); } else { create_simple_permissions (window, page_grid); } append_blank_slim_row (page_grid); #ifdef HAVE_SELINUX append_title_value_pair (window, page_grid, _("SELinux context:"), "selinux_context", INCONSISTENT_STATE_STRING, FALSE); #endif append_title_value_pair (window, page_grid, _("Last changed:"), "date_permissions", INCONSISTENT_STATE_STRING, FALSE); if (window->details->has_recursive_apply) { GtkWidget *button, *hbox; hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_show (hbox); gtk_container_add_with_properties (GTK_CONTAINER (page_grid), hbox, "width", 2, NULL); button = gtk_button_new_with_mnemonic (_("Apply Permissions to Enclosed Files")); gtk_widget_show (button); gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); g_signal_connect (button, "clicked", G_CALLBACK (apply_recursive_clicked), window); } } else { char *prompt_text; if (!is_multi_file_window (window)) { char *file_name; file_name = caja_file_get_display_name (get_target_file (window)); prompt_text = g_strdup_printf (_("The permissions of \"%s\" could not be determined."), file_name); g_free (file_name); } else { prompt_text = g_strdup (_("The permissions of the selected file could not be determined.")); } add_prompt (vbox, prompt_text, TRUE); g_free (prompt_text); } } static void append_extension_pages (FMPropertiesWindow *window) { GList *providers; GList *module_providers; GList *p; providers = caja_extensions_get_for_type (CAJA_TYPE_PROPERTY_PAGE_PROVIDER); /* FIXME: we also need the property pages from two old modules that * are not registered as proper extensions. This is going to work * this way until some generic solution is introduced. */ module_providers = caja_module_get_extensions_for_type (CAJA_TYPE_PROPERTY_PAGE_PROVIDER); for (p = module_providers; p != NULL; p = p->next) { const gchar *type_name = G_OBJECT_TYPE_NAME (G_OBJECT (p->data)); if (g_strcmp0 (type_name, "CajaNotesViewerProvider") == 0 || g_strcmp0 (type_name, "CajaImagePropertiesPageProvider") == 0) { providers = g_list_prepend (providers, p->data); } } for (p = providers; p != NULL; p = p->next) { CajaPropertyPageProvider *provider; GList *pages; GList *l; provider = CAJA_PROPERTY_PAGE_PROVIDER (p->data); pages = caja_property_page_provider_get_pages (provider, window->details->original_files); for (l = pages; l != NULL; l = l->next) { CajaPropertyPage *page; GtkWidget *page_widget; GtkWidget *label; page = CAJA_PROPERTY_PAGE (l->data); g_object_get (G_OBJECT (page), "page", &page_widget, "label", &label, NULL); gtk_notebook_append_page (window->details->notebook, page_widget, label); 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); } caja_module_extension_list_free (providers); } static gboolean should_show_emblems (FMPropertiesWindow *window) { /* FIXME bugzilla.gnome.org 45643: * Emblems aren't displayed on the the desktop Trash icon, so * we shouldn't pretend that they work by showing them here. * When bug 5643 is fixed we can remove this case. */ if (!is_multi_file_window (window) && is_merged_trash_directory (get_target_file (window))) { return FALSE; } return TRUE; } static gboolean should_show_permissions (FMPropertiesWindow *window) { CajaFile *file; file = get_target_file (window); /* Don't show permissions for Trash and Computer since they're not * really file system objects. */ if (!is_multi_file_window (window) && (is_merged_trash_directory (file) || is_computer_directory (file))) { 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, caja_file_get_uri (CAJA_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) { StartupData *data; GList *l; data = g_new0 (StartupData, 1); data->original_files = caja_file_list_copy (original_files); data->target_files = caja_file_list_copy (target_files); data->parent_widget = parent_widget; data->pending_key = g_strdup (pending_key); data->pending_files = g_hash_table_new (g_direct_hash, g_direct_equal); for (l = data->target_files; l != NULL; l = l->next) { g_hash_table_insert (data->pending_files, l->data, l->data); } return data; } static void startup_data_free (StartupData *data) { caja_file_list_free (data->original_files); caja_file_list_free (data->target_files); g_hash_table_destroy (data->pending_files); g_free (data->pending_key); g_free (data); } static void file_changed_callback (CajaFile *file, gpointer user_data) { FMPropertiesWindow *window = FM_PROPERTIES_WINDOW (user_data); if (!g_list_find (window->details->changed_files, file)) { caja_file_ref (file); window->details->changed_files = g_list_prepend (window->details->changed_files, file); schedule_files_update (window); } } static gboolean is_a_special_file (CajaFile *file) { if (file == NULL || CAJA_IS_DESKTOP_ICON_FILE (file) || is_merged_trash_directory (file) || is_computer_directory (file)) { return TRUE; } return FALSE; } static gboolean should_show_open_with (FMPropertiesWindow *window) { CajaFile *file; /* Don't show open with tab for desktop special icons (trash, etc) * 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)) { if (!file_list_attributes_identical (window->details->original_files, "mime_type")) { return FALSE; } else { GList *l; for (l = window->details->original_files; l; l = l->next) { file = CAJA_FILE (l->data); if (caja_file_is_directory (file) || is_a_special_file (file)) { return FALSE; } } } } else { file = get_original_file (window); if (caja_file_is_directory (file) || is_a_special_file (file)) { return FALSE; } } return TRUE; } static void create_open_with_page (FMPropertiesWindow *window) { GtkWidget *vbox; char *mime_type; mime_type = caja_file_get_mime_type (get_target_file (window)); if (!is_multi_file_window (window)) { char *uri; uri = caja_file_get_uri (get_target_file (window)); if (uri == NULL) { return; } vbox = caja_mime_application_chooser_new (uri, mime_type); g_free (uri); } else { GList *uris; uris = window->details->original_files; if (uris == NULL) { return; } vbox = caja_mime_application_chooser_new_for_multiple_files (uris, mime_type); } gtk_widget_show (vbox); g_free (mime_type); gtk_notebook_append_page (window->details->notebook, vbox, gtk_label_new (_("Open With"))); } static FMPropertiesWindow * create_properties_window (StartupData *startup_data) { FMPropertiesWindow *window; GList *l; GtkWidget *action_area; window = FM_PROPERTIES_WINDOW (gtk_widget_new (fm_properties_window_get_type (), NULL)); window->details->original_files = caja_file_list_copy (startup_data->original_files); window->details->target_files = caja_file_list_copy (startup_data->target_files); gtk_window_set_screen (GTK_WINDOW (window), gtk_widget_get_screen (startup_data->parent_widget)); gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_DIALOG); /* 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->details->original_files; l != NULL; l = l->next) { CajaFile *file; CajaFileAttributes attributes; file = CAJA_FILE (l->data); attributes = CAJA_FILE_ATTRIBUTES_FOR_ICON | CAJA_FILE_ATTRIBUTE_INFO | CAJA_FILE_ATTRIBUTE_LINK_INFO; caja_file_monitor_add (file, &window->details->original_files, attributes); } for (l = window->details->target_files; l != NULL; l = l->next) { CajaFile *file; CajaFileAttributes attributes; file = CAJA_FILE (l->data); attributes = 0; if (caja_file_is_directory (file)) { attributes |= CAJA_FILE_ATTRIBUTE_DEEP_COUNTS; } attributes |= CAJA_FILE_ATTRIBUTE_INFO; caja_file_monitor_add (file, &window->details->target_files, attributes); } for (l = window->details->target_files; l != NULL; l = l->next) { g_signal_connect_object (CAJA_FILE (l->data), "changed", G_CALLBACK (file_changed_callback), G_OBJECT (window), 0); } for (l = window->details->original_files; l != NULL; l = l->next) { g_signal_connect_object (CAJA_FILE (l->data), "changed", G_CALLBACK (file_changed_callback), G_OBJECT (window), 0); } /* Create the notebook tabs. */ window->details->notebook = GTK_NOTEBOOK (gtk_notebook_new ()); gtk_widget_add_events (GTK_WIDGET (window->details->notebook), GDK_SCROLL_MASK); g_signal_connect (window->details->notebook, "scroll-event", G_CALLBACK (eel_dialog_page_scroll_event_callback), window); gtk_widget_show (GTK_WIDGET (window->details->notebook)); gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))), GTK_WIDGET (window->details->notebook), TRUE, TRUE, 0); /* Create the pages. */ create_basic_page (window); if (should_show_emblems (window)) { create_emblems_page (window); } if (should_show_permissions (window)) { create_permissions_page (window); } if (should_show_open_with (window)) { create_open_with_page (window); } /* append pages from available views */ append_extension_pages (window); eel_dialog_add_button (GTK_DIALOG (window), _("_Help"), "help-browser", GTK_RESPONSE_HELP); action_area = gtk_widget_get_parent (eel_dialog_add_button (GTK_DIALOG (window), _("_Close"), "window-close", GTK_RESPONSE_CLOSE)); /* FIXME - HIGificiation, should be done inside GTK+ */ gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (window))), 12); gtk_container_set_border_width (GTK_CONTAINER (action_area), 0); gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))), 12); /* 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) { CajaFile *target; target = get_target_file_for_original_file (CAJA_FILE (l->data)); ret = g_list_prepend (ret, target); } ret = g_list_reverse (ret); return ret; } static void add_window (FMPropertiesWindow *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 (FMPropertiesWindow *window) { gpointer key; key = g_object_get_data (G_OBJECT (window), "window_key"); if (key) { g_hash_table_remove (windows, key); } } static GtkWindow * get_existing_window (GList *file_list) { if (!file_list->next) { return g_hash_table_lookup (windows, file_list->data); } return NULL; } static void cancel_create_properties_window_callback (gpointer callback_data) { remove_pending ((StartupData *)callback_data, TRUE, FALSE, TRUE); } static void parent_widget_destroyed_callback (GtkWidget *widget, gpointer callback_data) { g_assert (widget == ((StartupData *)callback_data)->parent_widget); remove_pending ((StartupData *)callback_data, TRUE, TRUE, FALSE); } static void cancel_call_when_ready_callback (gpointer key, gpointer value, gpointer user_data) { caja_file_cancel_call_when_ready (CAJA_FILE (key), is_directory_ready_callback, user_data); } static void remove_pending (StartupData *startup_data, gboolean cancel_call_when_ready, gboolean cancel_timed_wait, gboolean cancel_destroy_handler) { 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 (cancel_destroy_handler) { g_signal_handlers_disconnect_by_func (startup_data->parent_widget, G_CALLBACK (parent_widget_destroyed_callback), startup_data); } g_hash_table_remove (pending_lists, startup_data->pending_key); startup_data_free (startup_data); } static void is_directory_ready_callback (CajaFile *file, gpointer data) { StartupData *startup_data; startup_data = data; g_hash_table_remove (startup_data->pending_files, file); if (g_hash_table_size (startup_data->pending_files) == 0) { FMPropertiesWindow *new_window; new_window = create_properties_window (startup_data); add_window (new_window); remove_pending (startup_data, FALSE, TRUE, TRUE); gtk_window_present (GTK_WINDOW (new_window)); } } void fm_properties_window_present (GList *original_files, GtkWidget *parent_widget) { GList *l, *next; GtkWidget *parent_window; StartupData *startup_data; GList *target_files; GtkWindow *existing_window; char *pending_key; g_return_if_fail (original_files != NULL); g_return_if_fail (GTK_IS_WIDGET (parent_widget)); /* Create the hash tables first time through. */ if (windows == NULL) { windows = eel_g_hash_table_new_free_at_exit (NULL, NULL, "property windows"); } if (pending_lists == NULL) { pending_lists = eel_g_hash_table_new_free_at_exit (g_str_hash, g_str_equal, "pending property window files"); } /* Look to see if there's already a window for this file. */ existing_window = get_existing_window (original_files); if (existing_window != NULL) { gtk_window_set_screen (existing_window, gtk_widget_get_screen (parent_widget)); gtk_window_present (existing_window); 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) { return; } target_files = get_target_file_list (original_files); startup_data = startup_data_new (original_files, target_files, pending_key, parent_widget); caja_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); g_signal_connect (parent_widget, "destroy", G_CALLBACK (parent_widget_destroyed_callback), startup_data); parent_window = gtk_widget_get_ancestor (parent_widget, GTK_TYPE_WINDOW); 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; caja_file_call_when_ready (CAJA_FILE (l->data), CAJA_FILE_ATTRIBUTE_INFO, is_directory_ready_callback, startup_data); } } static void real_response (GtkDialog *dialog, int response) { GError *error = NULL; switch (response) { case GTK_RESPONSE_HELP: gtk_show_uri_on_window (GTK_WINDOW (dialog), "help:mate-user-guide/goscaja-51", gtk_get_current_event_time (), &error); if (error != NULL) { eel_show_error_dialog (_("There was an error displaying help."), error->message, GTK_WINDOW (dialog)); g_error_free (error); } break; case GTK_RESPONSE_NONE: case GTK_RESPONSE_CLOSE: case GTK_RESPONSE_DELETE_EVENT: gtk_widget_destroy (GTK_WIDGET (dialog)); break; default: g_assert_not_reached (); break; } } static void real_destroy (GtkWidget *object) { FMPropertiesWindow *window; GList *l; window = FM_PROPERTIES_WINDOW (object); remove_window (window); for (l = window->details->original_files; l != NULL; l = l->next) { caja_file_monitor_remove (CAJA_FILE (l->data), &window->details->original_files); } caja_file_list_free (window->details->original_files); window->details->original_files = NULL; for (l = window->details->target_files; l != NULL; l = l->next) { caja_file_monitor_remove (CAJA_FILE (l->data), &window->details->target_files); } caja_file_list_free (window->details->target_files); window->details->target_files = NULL; caja_file_list_free (window->details->changed_files); window->details->changed_files = NULL; window->details->name_field = NULL; g_list_free (window->details->emblem_buttons); window->details->emblem_buttons = NULL; if (window->details->initial_emblems) { g_hash_table_destroy (window->details->initial_emblems); window->details->initial_emblems = NULL; } g_list_free (window->details->permission_buttons); window->details->permission_buttons = NULL; g_list_free (window->details->permission_combos); window->details->permission_combos = NULL; if (window->details->initial_permissions) { g_hash_table_destroy (window->details->initial_permissions); window->details->initial_permissions = NULL; } g_list_free (window->details->value_fields); window->details->value_fields = NULL; if (window->details->update_directory_contents_timeout_id != 0) { g_source_remove (window->details->update_directory_contents_timeout_id); window->details->update_directory_contents_timeout_id = 0; } if (window->details->update_files_timeout_id != 0) { g_source_remove (window->details->update_files_timeout_id); window->details->update_files_timeout_id = 0; } GTK_WIDGET_CLASS (fm_properties_window_parent_class)->destroy (object); } static void real_finalize (GObject *object) { FMPropertiesWindow *window; window = FM_PROPERTIES_WINDOW (object); g_list_free_full (window->details->mime_list, g_free); g_free (window->details->pending_name); G_OBJECT_CLASS (fm_properties_window_parent_class)->finalize (object); } /* converts * file://foo/foobar/foofoo/bar * to * foofoo/bar * if * file://foo/foobar * is the parent * * It does not resolve any symlinks. * */ static char * make_relative_uri_from_full (const char *uri, const char *base_uri) { g_assert (uri != NULL); g_assert (base_uri != NULL); if (g_str_has_prefix (uri, base_uri)) { uri += strlen (base_uri); if (*uri != '/') { return NULL; } while (*uri == '/') { uri++; } if (*uri != '\0') { return g_strdup (uri); } } return NULL; } /* icon selection callback to set the image of the file object to the selected file */ static void set_icon (const char* icon_uri, FMPropertiesWindow *properties_window) { char *icon_path; g_assert (icon_uri != NULL); g_assert (FM_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; CajaFile *file = NULL; for (l = properties_window->details->original_files; l != NULL; l = l->next) { char *file_uri; file = CAJA_FILE (l->data); file_uri = caja_file_get_uri (file); if (caja_file_is_mime_type (file, "application/x-desktop")) { if (caja_link_local_set_icon (file_uri, icon_path)) { caja_file_invalidate_attributes (file, CAJA_FILE_ATTRIBUTE_INFO | CAJA_FILE_ATTRIBUTE_LINK_INFO); } } else { char *real_icon_uri; real_icon_uri = make_relative_uri_from_full (icon_uri, file_uri); if (real_icon_uri == NULL) { real_icon_uri = g_strdup (icon_uri); } caja_file_set_metadata (file, CAJA_METADATA_KEY_CUSTOM_ICON, NULL, real_icon_uri); caja_file_set_metadata (file, CAJA_METADATA_KEY_ICON_SCALE, NULL, NULL); g_free (real_icon_uri); } g_free (file_uri); } g_free (icon_path); } } static void update_preview_callback (GtkFileChooser *icon_chooser, FMPropertiesWindow *window) { GdkPixbuf *pixbuf, *scaled_pixbuf; char *filename; pixbuf = NULL; filename = gtk_file_chooser_get_filename (icon_chooser); if (filename != NULL) { pixbuf = gdk_pixbuf_new_from_file (filename, NULL); } if (pixbuf != NULL) { GtkWidget *preview_widget; 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) { double scale; 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_HYPER); 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, FMPropertiesWindow *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)); set_icon (uri, window); g_free (uri); break; default: break; } gtk_widget_hide (GTK_WIDGET (dialog)); } static void select_image_button_callback (GtkWidget *widget, FMPropertiesWindow *window) { GtkWidget *dialog; GList *l; CajaFile *file; char *image_path; gboolean revert_is_sensitive; g_assert (FM_IS_PROPERTIES_WINDOW (window)); dialog = window->details->icon_chooser; if (dialog == NULL) { GtkWidget *preview; GtkFileFilter *filter; dialog = eel_file_chooser_dialog_new (_("Select Custom Icon"), GTK_WINDOW (window), GTK_FILE_CHOOSER_ACTION_OPEN, "document-revert", GTK_RESPONSE_NO, "process-stop", GTK_RESPONSE_CANCEL, "document-open", GTK_RESPONSE_OK, NULL); gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), "/usr/share/icons", NULL); gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), "/usr/share/pixmaps", NULL); gtk_window_set_destroy_with_parent (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->details->icon_chooser = dialog; g_object_add_weak_pointer (G_OBJECT (dialog), (gpointer *) &window->details->icon_chooser); } /* it's likely that the user wants to pick an icon that is inside a local directory */ if (g_list_length (window->details->original_files) == 1) { file = CAJA_FILE (window->details->original_files->data); if (caja_file_is_directory (file)) { char *uri; uri = caja_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->details->original_files; l != NULL; l = l->next) { file = CAJA_FILE (l->data); image_path = caja_file_get_metadata (file, CAJA_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 fm_properties_window_class_init (FMPropertiesWindowClass *class) { GtkBindingSet *binding_set; G_OBJECT_CLASS (class)->finalize = real_finalize; GTK_WIDGET_CLASS (class)->destroy = real_destroy; GTK_DIALOG_CLASS (class)->response = real_response; binding_set = gtk_binding_set_by_class (class); gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, "close", 0); } static void fm_properties_window_init (FMPropertiesWindow *window) { window->details = fm_properties_window_get_instance_private (window); }