From 9ce068d356fd067c15cd3564ab97b3e83b567049 Mon Sep 17 00:00:00 2001 From: upstream Date: Thu, 24 Sep 2026 18:36:33 +0200 Subject: [PATCH] Upstream linuxmint/nemo state of the touched files before 49a7347363 --- src/nemo-properties-window.c | 5593 ++++++++++++++++++++++++++++++++++ 1 file changed, 5593 insertions(+) create mode 100644 src/nemo-properties-window.c diff --git a/src/nemo-properties-window.c b/src/nemo-properties-window.c new file mode 100644 index 0000000..bee6747 --- /dev/null +++ b/src/nemo-properties-window.c @@ -0,0 +1,5593 @@ +/* -*- 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 Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 51 Franklin Street - Suite 500, + Boston, MA 02110-1335, USA. + + Authors: Darin Adler +*/ + +#include + +#include "nemo-properties-window.h" + +#include "nemo-desktop-item-properties.h" +#include "nemo-error-reporting.h" +#include "nemo-mime-actions.h" + +#include +#include +#include +#include +#include +#include +#include + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 STACK_INNER_BORDER 12 + +#define ROW_PAD 6 + +static GHashTable *windows; +static GHashTable *pending_lists; + +struct NemoPropertiesWindowDetails { + GList *original_files; + GList *target_files; + + GtkStack *stack; + + 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; + GtkLabel *size_value_field; + guint update_directory_contents_timeout_id; + guint update_files_timeout_id; + + NemoFile *group_change_file; + char *group_change_group; + unsigned int group_change_timeout; + NemoFile *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; + guint hidden_count; + + 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 *startup_id; + char *pending_key; + GHashTable *pending_files; +} StartupData; + +/* drag and drop definitions */ + +enum { + TARGET_URI_LIST, + TARGET_GNOME_URI_LIST, +}; + +static const GtkTargetEntry target_table[] = { + { (char *)"text/uri-list", 0, TARGET_URI_LIST }, + { (char *)"x-special/gnome-icon-list", 0, TARGET_GNOME_URI_LIST }, +}; + +#define DIRECTORY_CONTENTS_UPDATE_INTERVAL 200 /* milliseconds */ +#define FILES_UPDATE_INTERVAL 200 /* milliseconds */ + +#define NEMO_RESPONSE_REVERT 10 + +/* + * 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 gboolean is_computer_directory (NemoFile *file); +static gboolean is_network_directory (NemoFile *file); +static gboolean is_burn_directory (NemoFile *file); +static void directory_contents_value_field_update (NemoPropertiesWindow *window); +static void file_changed_callback (NemoFile *file, + gpointer user_data); +static void permission_button_update (NemoPropertiesWindow *window, + GtkToggleButton *button); +static void permission_combo_update (NemoPropertiesWindow *window, + GtkComboBox *combo); +static void value_field_update (NemoPropertiesWindow *window, + GtkLabel *field); +static void properties_window_update (NemoPropertiesWindow *window, + GList *files); +static void size_field_update (NemoPropertiesWindow *window); +static void is_directory_ready_callback (NemoFile *file, + gpointer data); +static void cancel_group_change_callback (NemoPropertiesWindow *window); +static void cancel_owner_change_callback (NemoPropertiesWindow *window); +static void parent_widget_destroyed_callback (GtkWidget *widget, + gpointer callback_data); +static void select_image_button_callback (GtkWidget *widget, + NemoPropertiesWindow *properties_window); +static void set_icon (const char *icon_string, + NemoPropertiesWindow *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 (NemoPropertiesWindow *window); + +static gboolean name_field_focus_out (NemoEntry *name_field, + GdkEventFocus *event, + gpointer callback_data); +static void name_field_activate (NemoEntry *name_field, + gpointer callback_data); +static GtkLabel *attach_ellipsizing_value_label (GtkGrid *grid, + GtkWidget *sibling, + const char *initial_text, + PangoEllipsizeMode ellipsize_mode); + +static GtkWidget* create_pie_widget (NemoPropertiesWindow *window); + +G_DEFINE_TYPE (NemoPropertiesWindow, nemo_properties_window, GTK_TYPE_DIALOG); + +static gboolean +is_multi_file_window (NemoPropertiesWindow *window) +{ + GList *l; + guint count; + + count = 0; + + for (l = window->details->original_files; l != NULL; l = l->next) { + if (!nemo_file_is_gone (NEMO_FILE (l->data))) { + count++; + if (count > 1) { + return TRUE; + } + } + } + + return FALSE; +} + +static int +get_not_gone_original_file_count (NemoPropertiesWindow *window) +{ + GList *l; + int count; + + count = 0; + + for (l = window->details->original_files; l != NULL; l = l->next) { + if (!nemo_file_is_gone (NEMO_FILE (l->data))) { + count++; + } + } + + return count; +} + +static NemoFile * +get_original_file (NemoPropertiesWindow *window) +{ + g_return_val_if_fail (!is_multi_file_window (window), NULL); + + if (window->details->original_files == NULL) { + return NULL; + } + + return NEMO_FILE (window->details->original_files->data); +} + +static NemoFile * +get_target_file_for_original_file (NemoFile *file) +{ + NemoFile *target_file; + GFile *location; + char *uri_to_display; + NemoDesktopLink *link; + + target_file = NULL; + if (NEMO_IS_DESKTOP_ICON_FILE (file)) { + link = nemo_desktop_icon_file_get_link (NEMO_DESKTOP_ICON_FILE (file)); + + if (link != NULL) { + /* map to linked URI for these types of links */ + location = nemo_desktop_link_get_activation_location (link); + if (location) { + target_file = nemo_file_get (location); + g_object_unref (location); + } + + g_object_unref (link); + } + } else { + uri_to_display = nemo_file_get_activation_uri (file); + if (uri_to_display != NULL) { + target_file = nemo_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. */ + nemo_file_ref (file); + return file; +} + +static NemoFile * +get_target_file (NemoPropertiesWindow *window) +{ + return NEMO_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 (NemoPropertiesWindow *window, + char **icon_name, + GdkPixbuf **icon_pixbuf) +{ + NemoIconInfo *icon, *new_icon; + GList *l; + gint icon_scale; + + icon = NULL; + icon_scale = gtk_widget_get_scale_factor(GTK_WIDGET(window->details->stack)); + for (l = window->details->original_files; l != NULL; l = l->next) { + NemoFile *file; + + file = NEMO_FILE (l->data); + + if (!icon) { + icon = nemo_file_get_icon (file, NEMO_ICON_SIZE_STANDARD, 0, icon_scale, + NEMO_FILE_ICON_FLAGS_USE_THUMBNAILS | + NEMO_FILE_ICON_FLAGS_IGNORE_VISITING); + } else { + new_icon = nemo_file_get_icon (file, NEMO_ICON_SIZE_STANDARD, 0, icon_scale, + NEMO_FILE_ICON_FLAGS_USE_THUMBNAILS | + NEMO_FILE_ICON_FLAGS_IGNORE_VISITING); + if (!new_icon || new_icon != icon) { + nemo_icon_info_unref (icon); + nemo_icon_info_unref (new_icon); + icon = NULL; + break; + } + nemo_icon_info_unref (new_icon); + } + } + + if (!icon) { + icon = nemo_icon_info_lookup_from_name ("text-x-generic", NEMO_ICON_SIZE_STANDARD, icon_scale); + } + + if (icon_name != NULL) { + *icon_name = g_strdup (nemo_icon_info_get_used_name (icon)); + } + + if (icon_pixbuf != NULL) { + *icon_pixbuf = nemo_icon_info_get_pixbuf_at_size (icon, NEMO_ICON_SIZE_STANDARD); + } + + nemo_icon_info_unref (icon); +} + + +static void +update_properties_window_icon (NemoPropertiesWindow *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 (NemoPropertiesWindow *properties_window) +{ + GList *l; + + for (l = properties_window->details->original_files; l != NULL; l = l->next) { + NemoFile *file; + + file = NEMO_FILE (l->data); + + nemo_file_set_metadata (file, + NEMO_METADATA_KEY_ICON_SCALE, + NULL, NULL); + nemo_file_set_metadata (file, + NEMO_METADATA_KEY_CUSTOM_ICON, + NULL, NULL); + nemo_file_set_metadata (file, + NEMO_METADATA_KEY_CUSTOM_ICON_NAME, + NULL, NULL); + } +} + + +static void +nemo_properties_window_drag_data_received (GtkWidget *widget, GdkDragContext *context, + int x, int y, + GtkSelectionData *selection_data, + guint info, guint time) +{ + char **uris; + gboolean exactly_one; + GtkImage *image; + GtkWindow *window; + + image = GTK_IMAGE (widget); + window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (image))); + + uris = g_strsplit ((const gchar *) gtk_selection_data_get_data (selection_data), "\r\n", 0); + exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0'); + + + if (!exactly_one) { + 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], NEMO_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 (NemoPropertiesWindow *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 (nemo_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 (NemoPropertiesWindow *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) || !nemo_file_can_rename (get_original_file (window)); + new_widget = !window->details->name_field || (use_label ? NEMO_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, + PANGO_ELLIPSIZE_END)); + } else { + window->details->name_field = nemo_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); + + /* FIXME bugzilla.gnome.org 42151: + * With this (and one place elsewhere in this file, not sure which is the + * trouble-causer) code in place, bug 2151 happens (crash on quit). Since + * we've removed Undo from Nemo for now, I'm just ifdeffing out this + * code rather than trying to fix 2151 now. Note that it might be possible + * to fix 2151 without making Undo actually work, it's just not worth the + * trouble. + */ +#ifdef UNDO_ENABLED + /* Set up name field for undo */ + nemo_undo_set_up_nemo_entry_for_undo ( NEMO_ENTRY (window->details->name_field)); + nemo_undo_editable_set_undo_key (GTK_EDITABLE (window->details->name_field), TRUE); +#endif + + 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 (NemoPropertiesWindow *window) +{ + NemoFile *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 = NEMO_FILE (l->data); + + if (!nemo_file_is_gone (file)) { + if (!first) { + g_string_append (str, ", "); + } + first = FALSE; + + name = nemo_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 || nemo_file_is_gone (file)) { + current_name = g_strdup (""); + } else { + current_name = nemo_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 (NemoEntry *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); + } + nemo_entry_select_all (name_field); + + g_free (displayed_name); +} + +static void +rename_callback (NemoFile *file, GFile *res_loc, GError *error, gpointer callback_data) +{ + NemoPropertiesWindow *window; + + window = NEMO_PROPERTIES_WINDOW (callback_data); + + /* Complain to user if rename failed. */ + if (error != NULL) { + nemo_report_error_renaming_file (file, + window->details->pending_name, + error, + GTK_WINDOW (window)); + if (window->details->name_field != NULL) { + name_field_restore_original_name (NEMO_ENTRY (window->details->name_field)); + } + } + + g_object_unref (window); +} + +static void +set_pending_name (NemoPropertiesWindow *window, const char *name) +{ + g_free (window->details->pending_name); + window->details->pending_name = g_strdup (name); +} + +static void +name_field_done_editing (NemoEntry *name_field, NemoPropertiesWindow *window) +{ + NemoFile *file; + char *new_name; + const char *original_name; + + g_return_if_fail (NEMO_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 || nemo_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 (NEMO_ENTRY (name_field)); + } else { + 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); + nemo_file_rename (file, new_name, + rename_callback, window); + } + } + + g_free (new_name); +} + +static gboolean +name_field_focus_out (NemoEntry *name_field, + GdkEventFocus *event, + gpointer callback_data) +{ + g_assert (NEMO_IS_PROPERTIES_WINDOW (callback_data)); + + if (gtk_widget_get_sensitive (GTK_WIDGET (name_field))) { + name_field_done_editing (name_field, NEMO_PROPERTIES_WINDOW (callback_data)); + } + + return FALSE; +} + +static void +name_field_activate (NemoEntry *name_field, gpointer callback_data) +{ + g_assert (NEMO_IS_ENTRY (name_field)); + g_assert (NEMO_IS_PROPERTIES_WINDOW (callback_data)); + + /* Accept changes. */ + name_field_done_editing (name_field, NEMO_PROPERTIES_WINDOW (callback_data)); + + nemo_entry_select_all_at_idle (name_field); +} + +static void +update_properties_window_title (NemoPropertiesWindow *window) +{ + char *name, *title; + NemoFile *file; + + g_return_if_fail (GTK_IS_WINDOW (window)); + + title = g_strdup_printf (_("Properties")); + + if (!is_multi_file_window (window)) { + file = get_original_file (window); + + if (file != NULL) { + g_free (title); + name = nemo_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_callback (GtkWidget *page, gpointer data) { + if (g_object_get_data (G_OBJECT (page), "is-extension-page")) { + gtk_widget_destroy (page); + } +} + +static void +clear_extension_pages (NemoPropertiesWindow *window) +{ + gtk_container_foreach (GTK_CONTAINER (window->details->stack), + clear_extension_callback, + NULL); +} + +static void +refresh_extension_pages (NemoPropertiesWindow *window) +{ + clear_extension_pages (window); + append_extension_pages (window); +} + +static void +remove_from_dialog (NemoPropertiesWindow *window, + NemoFile *file) +{ + int index; + GList *original_link; + GList *target_link; + NemoFile *original_file; + NemoFile *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 = NEMO_FILE (original_link->data); + target_file = NEMO_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_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); + + nemo_file_monitor_remove (original_file, &window->details->original_files); + nemo_file_monitor_remove (target_file, &window->details->target_files); + + nemo_file_unref (original_file); + nemo_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 (NemoPropertiesWindow *window) +{ + GList *ret; + GList *l; + + ret = NULL; + for (l = window->details->target_files; l != NULL; l = l->next) { + ret = g_list_append (ret, nemo_file_get_mime_type (NEMO_FILE (l->data))); + } + ret = g_list_reverse (ret); + return ret; +} + +static void +properties_window_update (NemoPropertiesWindow *window, + GList *files) +{ + GList *l; + GList *mime_list; + GList *tmp; + NemoFile *changed_file; + gboolean dirty_original = FALSE; + gboolean dirty_target = FALSE; + + if (files == NULL) { + dirty_original = TRUE; + dirty_target = TRUE; + } + + for (tmp = files; tmp != NULL; tmp = tmp->next) { + changed_file = NEMO_FILE (tmp->data); + + if (changed_file && nemo_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); + + /* If any of the value fields start to depend on the original + * value, value_field_updates should be added here */ + } + + if (dirty_target) { + size_field_update (window); + + 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) +{ + NemoPropertiesWindow *window; + + window = NEMO_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 { + nemo_file_list_free (window->details->changed_files); + window->details->changed_files = NULL; + } + + return FALSE; + } + +static void +schedule_files_update (NemoPropertiesWindow *window) + { + g_assert (NEMO_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) { + NemoFile *file; + + file = NEMO_FILE (l->data); + + if (nemo_file_is_gone (file)) { + continue; + } + + if (first_attr == NULL) { + first_attr = nemo_file_get_string_attribute_with_default (file, attribute_name); + } else { + char *attr; + attr = nemo_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) { + NemoFile *file; + + file = NEMO_FILE (l->data); + if (!nemo_file_is_gone (file)) { + return nemo_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 (!nemo_file_is_directory (NEMO_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; + char *mime_type, *tmp; + + 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)) { + mime_type = file_list_get_string_attribute (file_list, + "mime_type", + inconsistent_string); + if (strcmp (mime_type, inconsistent_string)) { + tmp = attribute_value; + attribute_value = g_strdup_printf (C_("MIME type description (MIME type)", "%s (%s)"), attribute_value, mime_type); + g_free (tmp); + } + g_free (mime_type); + } + + gtk_label_set_text (label, attribute_value); + gtk_widget_set_tooltip_text (GTK_WIDGET (label), + attribute_value); + g_free (attribute_value); +} + +static void +value_field_update (NemoPropertiesWindow *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, + PangoEllipsizeMode ellipsize_mode, + gboolean selectable, + gboolean mnemonic) +{ + GtkWidget *label_field; + + if (ellipsize_mode != PANGO_ELLIPSIZE_NONE) { + label_field = gtk_label_new (initial_text); + gtk_label_set_ellipsize (GTK_LABEL (label_field), + ellipsize_mode); + } 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_misc_set_alignment (GTK_MISC (label_field), 0, 0.5); + gtk_widget_show (label_field); + + if (ellipsize_mode != PANGO_ELLIPSIZE_NONE) { + gtk_widget_set_hexpand (label_field, TRUE); + } + + 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, PANGO_ELLIPSIZE_NONE, TRUE, FALSE); +} + +static GtkLabel * +attach_ellipsizing_value_label (GtkGrid *grid, + GtkWidget *sibling, + const char *initial_text, + PangoEllipsizeMode ellipsize_mode) +{ + return attach_label (grid, sibling, initial_text, ellipsize_mode, TRUE, FALSE); +} + +static GtkWidget* +attach_value_field_ellipsizing (NemoPropertiesWindow *window, + GtkGrid *grid, + GtkWidget *sibling, + const char *file_attribute_name, + const char *inconsistent_string, + gboolean show_original, + PangoEllipsizeMode ellipsize_mode) +{ + GtkLabel *value_field; + + value_field = attach_ellipsizing_value_label (grid, sibling, "", ellipsize_mode); + + /* 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 (NemoPropertiesWindow *window, + GtkGrid *grid, + GtkWidget *sibling, + const char *file_attribute_name, + const char *inconsistent_string, + gboolean show_original) +{ + return attach_value_field_ellipsizing (window, + grid, sibling, + file_attribute_name, + inconsistent_string, + show_original, + PANGO_ELLIPSIZE_NONE); +} + +static void +group_change_callback (NemoFile *file, + GFile *res_loc, + GError *error, + NemoPropertiesWindow *window) +{ + char *group; + + g_assert (NEMO_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); + nemo_report_error_setting_group (file, error, GTK_WINDOW (window)); + + nemo_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 (NemoPropertiesWindow *window) +{ + NemoFile *file; + char *group; + + file = window->details->group_change_file; + g_assert (NEMO_IS_FILE (file)); + + group = window->details->group_change_group; + g_assert (group != NULL); + + nemo_file_cancel (file, (NemoFileOperationCallback) group_change_callback, window); + + g_free (group); + nemo_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 (NemoPropertiesWindow *window) +{ + NemoFile *file; + char *group; + + g_assert (NEMO_IS_PROPERTIES_WINDOW (window)); + + file = window->details->group_change_file; + g_assert (NEMO_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)); + + nemo_file_set_group + (file, group, + (NemoFileOperationCallback) group_change_callback, window); + + window->details->group_change_timeout = 0; + return FALSE; +} + +static void +schedule_group_change (NemoPropertiesWindow *window, + NemoFile *file, + const char *group) +{ + g_assert (NEMO_IS_PROPERTIES_WINDOW (window)); + g_assert (window->details->group_change_group == NULL); + g_assert (window->details->group_change_file == NULL); + g_assert (NEMO_IS_FILE (file)); + + window->details->group_change_file = nemo_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 (NemoPropertiesWindow *window) +{ + NemoFile *file; + char *group; + + g_assert (NEMO_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 (NEMO_IS_FILE (file)); + + if (window->details->group_change_timeout == 0) { + nemo_file_cancel (file, + (NemoFileOperationCallback) group_change_callback, window); + eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, window); + } + + nemo_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, NemoFile *file) +{ + NemoPropertiesWindow *window; + char *group; + char *cur_group; + + g_assert (GTK_IS_COMBO_BOX (combo_box)); + g_assert (NEMO_IS_FILE (file)); + + group = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (combo_box)); + cur_group = nemo_file_get_group_name (file); + + if (group != NULL && strcmp (group, cur_group) != 0) { + /* Try to change file group. If this fails, complain to user. */ + window = NEMO_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW)); + + unschedule_or_cancel_group_change (window); + schedule_group_change (window, file, group); + } + g_free (group); + g_free (cur_group); +} + +/* checks whether the given column at the first level + * of model has the specified entries in the given order. */ +static gboolean +tree_model_entries_equal (GtkTreeModel *model, + unsigned int column, + GList *entries) +{ + GtkTreeIter iter; + gboolean empty_model; + + g_assert (GTK_IS_TREE_MODEL (model)); + g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING); + + empty_model = !gtk_tree_model_get_iter_first (model, &iter); + + if (!empty_model && entries != NULL) { + GList *l; + + l = entries; + + do { + char *val; + + gtk_tree_model_get (model, &iter, + column, &val, + -1); + if ((val == NULL && l->data != NULL) || + (val != NULL && l->data == NULL) || + (val != NULL && strcmp (val, l->data))) { + g_free (val); + return FALSE; + } + + g_free (val); + l = l->next; + } while (gtk_tree_model_iter_next (model, &iter)); + + return l == NULL; + } else { + return (empty_model && entries == NULL) || + (!empty_model && entries != NULL); + } +} + +static char * +combo_box_get_active_entry (GtkComboBox *combo_box, + unsigned int column) +{ + GtkTreeModel *model; + GtkTreeIter iter; + char *val; + + g_assert (GTK_IS_COMBO_BOX (combo_box)); + + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter)) { + model = gtk_combo_box_get_model (combo_box); + g_assert (GTK_IS_TREE_MODEL (model)); + + gtk_tree_model_get (model, &iter, + column, &val, + -1); + return val; + } + + return NULL; +} + +/* returns the index of the given entry in the the given column + * at the first level of model. Returns -1 if entry can't be found + * or entry is NULL. + * */ +static int +tree_model_get_entry_index (GtkTreeModel *model, + unsigned int column, + const char *entry) +{ + GtkTreeIter iter; + int index; + gboolean empty_model; + + g_assert (GTK_IS_TREE_MODEL (model)); + g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING); + + empty_model = !gtk_tree_model_get_iter_first (model, &iter); + if (!empty_model && entry != NULL) { + index = 0; + + do { + char *val; + + gtk_tree_model_get (model, &iter, + column, &val, + -1); + if (val != NULL && !strcmp (val, entry)) { + g_free (val); + return index; + } + + g_free (val); + index++; + } while (gtk_tree_model_iter_next (model, &iter)); + } + + return -1; +} + + +static void +synch_groups_combo_box (GtkComboBox *combo_box, NemoFile *file) +{ + GList *groups; + GList *node; + GtkTreeModel *model; + GtkListStore *store; + const char *group_name; + char *current_group_name; + int group_index; + int current_group_index; + + g_assert (GTK_IS_COMBO_BOX (combo_box)); + g_assert (NEMO_IS_FILE (file)); + + if (nemo_file_is_gone (file)) { + return; + } + + groups = nemo_file_get_settable_group_names (file); + + model = gtk_combo_box_get_model (combo_box); + store = GTK_LIST_STORE (model); + g_assert (GTK_IS_LIST_STORE (model)); + + if (!tree_model_entries_equal (model, 0, groups)) { + /* Clear the contents of ComboBox in a wacky way because there + * is no function to clear all items and also no function to obtain + * the number of items in a combobox. + */ + gtk_list_store_clear (store); + + for (node = groups, group_index = 0; node != NULL; node = node->next, ++group_index) { + group_name = (const char *)node->data; + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo_box), group_name); + } + } + + current_group_name = nemo_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; + GtkWidget *aligner; + + 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_show (combo_box); + + gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo_box), + combo_box_row_separator_func, + NULL, + NULL); + + /* Put combo box in alignment to make it left-justified + * but minimally sized. + */ + aligner = gtk_alignment_new (0, 0.5, 0, 0); + gtk_widget_show (aligner); + + gtk_container_add (GTK_CONTAINER (aligner), combo_box); + gtk_grid_attach_next_to (grid, aligner, sibling, + GTK_POS_RIGHT, 1, 1); + + return GTK_COMBO_BOX (combo_box); +} + +static GtkComboBox* +attach_group_combo_box (GtkGrid *grid, + GtkWidget *sibling, + NemoFile *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), + nemo_file_ref (file), + (GClosureNotify)nemo_file_unref, 0); + + return combo_box; +} + +static void +owner_change_callback (NemoFile *file, + GFile *result_location, + GError *error, + NemoPropertiesWindow *window) +{ + char *owner; + + g_assert (NEMO_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); + nemo_report_error_setting_owner (file, error, GTK_WINDOW (window)); + + nemo_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 (NemoPropertiesWindow *window) +{ + NemoFile *file; + char *owner; + + file = window->details->owner_change_file; + g_assert (NEMO_IS_FILE (file)); + + owner = window->details->owner_change_owner; + g_assert (owner != NULL); + + nemo_file_cancel (file, (NemoFileOperationCallback) owner_change_callback, window); + + nemo_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 (NemoPropertiesWindow *window) +{ + NemoFile *file; + char *owner; + + g_assert (NEMO_IS_PROPERTIES_WINDOW (window)); + + file = window->details->owner_change_file; + g_assert (NEMO_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)); + + nemo_file_set_owner + (file, owner, + (NemoFileOperationCallback) owner_change_callback, window); + + window->details->owner_change_timeout = 0; + return FALSE; +} + +static void +schedule_owner_change (NemoPropertiesWindow *window, + NemoFile *file, + const char *owner) +{ + g_assert (NEMO_IS_PROPERTIES_WINDOW (window)); + g_assert (window->details->owner_change_owner == NULL); + g_assert (window->details->owner_change_file == NULL); + g_assert (NEMO_IS_FILE (file)); + + window->details->owner_change_file = nemo_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 (NemoPropertiesWindow *window) +{ + NemoFile *file; + char *owner; + + g_assert (NEMO_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 (NEMO_IS_FILE (file)); + + if (window->details->owner_change_timeout == 0) { + nemo_file_cancel (file, + (NemoFileOperationCallback) owner_change_callback, window); + eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, window); + } + + nemo_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, NemoFile* file) +{ + NemoPropertiesWindow *window; + char *owner_text; + char **name_array; + char *new_owner; + char *cur_owner; + + g_assert (GTK_IS_COMBO_BOX (combo_box)); + g_assert (NEMO_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 = nemo_file_get_owner_name (file); + + if (strcmp (new_owner, cur_owner) != 0) { + /* Try to change file owner. If this fails, complain to user. */ + window = NEMO_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, NemoFile *file) +{ + GList *users; + GList *node; + GtkTreeModel *model; + GtkListStore *store; + GtkTreeIter iter; + char *user_name; + char *owner_name; + int user_index; + int owner_index; + char **name_array; + char *combo_text; + + g_assert (GTK_IS_COMBO_BOX (combo_box)); + g_assert (NEMO_IS_FILE (file)); + + if (nemo_file_is_gone (file)) { + return; + } + + users = nemo_get_user_names (); + + model = gtk_combo_box_get_model (combo_box); + store = GTK_LIST_STORE (model); + g_assert (GTK_IS_LIST_STORE (model)); + + if (!tree_model_entries_equal (model, 1, users)) { + /* Clear the contents of ComboBox in a wacky way because there + * is no function to clear all items and also no function to obtain + * the number of items in a combobox. + */ + gtk_list_store_clear (store); + + for (node = users, user_index = 0; node != NULL; node = node->next, ++user_index) { + user_name = (char *)node->data; + + name_array = g_strsplit (user_name, "\n", 2); + if (name_array[1] != NULL) { + 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 = nemo_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, + NemoFile *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), + nemo_file_ref (file), + (GClosureNotify)nemo_file_unref, 0); + + return combo_box; +} + +static gboolean +file_has_prefix (NemoFile *file, + GList *prefix_candidates) +{ + GList *p; + GFile *location, *candidate_location; + + location = nemo_file_get_location (file); + + for (p = prefix_candidates; p != NULL; p = p->next) { + if (file == p->data) { + continue; + } + + candidate_location = nemo_file_get_location (NEMO_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 gchar * +file_size_for_display (goffset size) +{ + int size_prefix; + gchar *text; + + /* always use the long format for this. */ + size_prefix = nemo_global_preferences_get_size_prefix_preference (); + text = g_format_size_full (size, (size_prefix | G_FORMAT_SIZE_LONG_FORMAT)); + + return text; +} + +static void +directory_contents_value_field_update (NemoPropertiesWindow *window) +{ + NemoRequestStatus file_status, status; + gchar *text, *temp; + guint directory_count; + guint file_count; + guint total_count; + guint unreadable_directory_count; + guint hidden_count; + guint total_hidden; + goffset total_size; + gboolean used_two_lines; + NemoFile *file; + GList *l; + guint file_unreadable; + goffset file_size; + + g_assert (NEMO_IS_PROPERTIES_WINDOW (window)); + + status = NEMO_REQUEST_DONE; + total_count = window->details->total_count; + total_size = window->details->total_size; + total_hidden = window->details->hidden_count; + unreadable_directory_count = FALSE; + + for (l = window->details->target_files; l; l = l->next) { + file = NEMO_FILE (l->data); + + if (file_has_prefix (file, window->details->target_files)) { + /* don't count nested files twice */ + continue; + } + + if (nemo_file_is_directory (file)) { + file_status = nemo_file_get_deep_counts (file, + &directory_count, + &file_count, + &file_unreadable, + &hidden_count, + &file_size, + TRUE); + total_count += (file_count + directory_count); + total_size += file_size; + total_hidden += hidden_count; + if (file_unreadable) { + unreadable_directory_count = TRUE; + } + + if (file_status != NEMO_REQUEST_DONE) { + status = file_status; + } + } else { + ++total_count; + total_size += nemo_file_get_size (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 != NEMO_REQUEST_DONE) { + return; + } + + text = NULL; + used_two_lines = FALSE; + + if (total_count == 0) { + switch (status) { + case NEMO_REQUEST_DONE: + if (unreadable_directory_count == 0) { + text = g_strdup (_("nothing")); + } else { + text = g_strdup (_("unreadable")); + } + + break; + case NEMO_REQUEST_NOT_STARTED: + case NEMO_REQUEST_IN_PROGRESS: + default: + text = g_strdup ("..."); + } + } else { + if (total_hidden > 0) { + text = g_strdup_printf (ngettext("%1$s item (and %2$s hidden)", "%1$s items (and %2$s hidden)", total_count), + g_strdup_printf("%'d", total_count), + g_strdup_printf("%'d", total_hidden)); + } else { + text = g_strdup_printf (ngettext("%1$s item", "%1$s items", total_count), + g_strdup_printf("%'d", total_count)); + } + + 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); + + /* Update the file size field with the selection or directory's total file size. */ + file = get_target_file (window); + + if (!is_multi_file_window (window) + && (is_computer_directory (file) || + is_network_directory (file))) { + temp = g_object_get_data (G_OBJECT (window->details->size_value_field), + "inconsistent_string"); + text = g_strdup (temp); + } else { + text = file_size_for_display (total_size); + } + + gtk_label_set_text (window->details->size_value_field, text); + gtk_widget_set_tooltip_text (GTK_WIDGET (window->details->size_value_field), text); + + g_free (text); + + if (status == NEMO_REQUEST_DONE) { + window->details->deep_count_finished = TRUE; + } +} + +static gboolean +update_directory_contents_callback (gpointer data) +{ + NemoPropertiesWindow *window; + + window = NEMO_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 (NemoPropertiesWindow *window) +{ + g_assert (NEMO_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 (NemoPropertiesWindow *window, + GtkGrid *grid, + GtkWidget *sibling) +{ + GtkLabel *value_field; + GList *l; + NemoFile *file; + + 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); + + for (l = window->details->target_files; l; l = l->next) { + file = NEMO_FILE (l->data); + nemo_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, PANGO_ELLIPSIZE_NONE, FALSE, TRUE); +} + +#define INCONSISTENT_STATE_STRING \ + "\xE2\x80\x92" + +static void +append_title_value_pair (NemoPropertiesWindow *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 (NemoPropertiesWindow *window, + GtkGrid *grid, + const char *title, + const char *file_attribute_name, + const char *inconsistent_state, + gboolean show_original, + PangoEllipsizeMode ellipsize_mode) +{ + GtkLabel *title_label; + GtkWidget *value; + + title_label = attach_title_field (grid, title); + value = attach_value_field_ellipsizing (window, grid, + GTK_WIDGET (title_label), + file_attribute_name, + inconsistent_state, + show_original, + ellipsize_mode); + gtk_label_set_mnemonic_widget (title_label, value); +} + +static void +append_directory_contents_fields (NemoPropertiesWindow *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)); +} + +/* append_size_field () + * Manually add field pair for file sizes to always show them in long format, + * i.e. including the number of bytes. + * This still respects the user's preference for base (decimal/binary) + */ +static void +append_size_field (NemoPropertiesWindow *window, + GtkGrid *grid) +{ + GtkLabel *title_field, *value_field; + + title_field = attach_title_field (grid, _("Size:")); + value_field = attach_value_label (grid, GTK_WIDGET (title_field), NULL); + + g_assert (window->details->size_value_field == NULL); + window->details->size_value_field = GTK_LABEL (value_field); + + g_object_set_data_full (G_OBJECT (value_field), "inconsistent_string", + g_strdup (INCONSISTENT_STATE_STRING), g_free); + + gtk_label_set_mnemonic_widget (title_field, GTK_WIDGET (value_field)); +} + +static GtkWidget * +create_page_with_hbox (GtkStack *stack, + const char *name, + const char *title, + const char *help_uri) +{ + GtkWidget *hbox; + + g_assert (GTK_IS_STACK (stack)); + g_assert (name != NULL); + g_assert (title != NULL); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_show (hbox); + gtk_container_set_border_width (GTK_CONTAINER (hbox), STACK_INNER_BORDER); + gtk_box_set_spacing (GTK_BOX (hbox), 12); + gtk_stack_add_titled(stack, hbox, name, title); + g_object_set_data_full (G_OBJECT (hbox), "help-uri", g_strdup (help_uri), g_free); + + return hbox; +} + +static GtkWidget * +create_page_with_vbox (GtkStack *stack, + const char *name, + const char *title, + const char *help_uri) +{ + GtkWidget *vbox; + g_assert (GTK_IS_STACK (stack)); + g_assert (name != NULL); + g_assert (title != NULL); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_widget_show (vbox); + gtk_container_set_border_width (GTK_CONTAINER (vbox), STACK_INNER_BORDER); + gtk_stack_add_titled(stack, vbox, name, title); + g_object_set_data_full (G_OBJECT (vbox), "help-uri", g_strdup (help_uri), g_free); + + 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_computer_directory (NemoFile *file) +{ + char *file_uri; + gboolean result; + + file_uri = nemo_file_get_uri (file); + result = strcmp (file_uri, "computer:///") == 0; + g_free (file_uri); + + return result; +} + +static gboolean +is_network_directory (NemoFile *file) +{ + char *file_uri; + gboolean result; + + file_uri = nemo_file_get_uri (file); + result = strcmp (file_uri, "network:///") == 0; + g_free (file_uri); + + return result; +} + +static gboolean +is_burn_directory (NemoFile *file) +{ + char *file_uri; + gboolean result; + + file_uri = nemo_file_get_uri (file); + result = strcmp (file_uri, "burn:///") == 0; + g_free (file_uri); + + return result; +} + +static gboolean +should_show_custom_icon_buttons (NemoPropertiesWindow *window) +{ + if (is_multi_file_window (window)) { + return FALSE; + } + + return TRUE; +} + +static gboolean +should_show_file_type (NemoPropertiesWindow *window) +{ + if (!is_multi_file_window (window) + && (nemo_file_is_in_trash (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 (NemoPropertiesWindow *window) +{ + if (!is_multi_file_window (window) + && (nemo_file_is_in_trash (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 (NemoPropertiesWindow *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 (NemoPropertiesWindow *window) +{ + return !is_multi_file_window (window); +} + +static gboolean +should_show_link_target (NemoPropertiesWindow *window) +{ + if (!is_multi_file_window (window) + && nemo_file_is_symbolic_link (get_target_file (window))) { + return TRUE; + } + + return FALSE; +} + +static gboolean +location_show_original (NemoPropertiesWindow *window) +{ + NemoFile *file; + + /* there is no way a recent item will be mixed with + other items so just pick the first file to check */ + file = NEMO_FILE (g_list_nth_data (window->details->original_files, 0)); + return (file != NULL && !nemo_file_is_in_recent (file)); +} + +static gboolean +should_show_free_space (NemoPropertiesWindow *window) +{ + + if (!is_multi_file_window (window) + && (nemo_file_is_in_trash (get_target_file (window)) || + is_computer_directory (get_target_file (window)) || + is_network_directory (get_target_file (window)) || + nemo_file_is_in_recent (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 (NemoPropertiesWindow *window) +{ + NemoFile *file; + gboolean success = FALSE; + + if (is_multi_file_window (window)) { + return FALSE; + } + + file = get_original_file (window); + + if (file == NULL) { + return FALSE; + } + + if (nemo_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) +{ + NemoPropertiesWindow *window; + gint width, height; + GtkAllocation allocation; + + gtk_widget_get_allocation (widget, &allocation); + + width = allocation.width; + height = allocation.height; + + window = NEMO_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) +{ + NemoPropertiesWindow *window; + gint width, height; + GtkAllocation allocation; + + window = NEMO_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) +{ + + NemoPropertiesWindow *window; + gint width, height; + double free, used; + double angle1, angle2, split, xc, yc, radius; + GtkAllocation allocation; + GtkStyleContext *stack_ctx; + GdkRGBA bg_color; + + window = NEMO_PROPERTIES_WINDOW (data); + gtk_widget_get_allocation (widget, &allocation); + + width = allocation.width; + height = allocation.height; + + stack_ctx = gtk_widget_get_style_context (GTK_WIDGET (window->details->stack)); + gtk_style_context_get_background_color (stack_ctx, + gtk_widget_get_state_flags (GTK_WIDGET (window->details->stack)), + &bg_color); + + cairo_save (cr); + gdk_cairo_set_source_rgba (cr, &bg_color); + cairo_paint (cr); + cairo_restore (cr); + + 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 (NemoPropertiesWindow *window) +{ + NemoFile *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; + const char *fs_type; + gchar *uri; + GFile *location; + GFileInfo *info; + int prefix; + + prefix = nemo_global_preferences_get_size_prefix_preference (); + capacity = g_format_size_full (window->details->volume_capacity, prefix); + free = g_format_size_full (window->details->volume_free, prefix); + used = g_format_size_full (window->details->volume_capacity - window->details->volume_free, prefix); + + file = get_original_file (window); + + uri = nemo_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 */ + used_label = gtk_label_new (g_strconcat (used, " ", _("used"), NULL)); + + 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 */ + free_label = gtk_label_new (g_strconcat (free, " ", _("free"), NULL)); + + capacity_label = gtk_label_new (g_strconcat (_("Total capacity:"), " ", capacity, NULL)); + 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) { + fs_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE); + if (fs_type != NULL) { + gchar *str = g_strconcat (_("Filesystem type:"), " ", fs_type, NULL); + gtk_label_set_text (GTK_LABEL (fstype_label), str); + g_free (str); + } + + 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 (NemoPropertiesWindow *window) +{ + GtkWidget *piewidget; + gchar *uri; + NemoFile *file; + GFile *location; + GFileInfo *info; + + file = get_original_file (window); + + uri = nemo_file_get_activation_uri (file); + + location = g_file_new_for_uri (uri); + g_free (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 (NemoPropertiesWindow *window) +{ + GtkGrid *grid; + GtkWidget *icon_aligner; + GtkWidget *icon_pixmap_widget; + GtkWidget *volume_usage; + GtkWidget *hbox, *vbox; + hbox = create_page_with_hbox (window->details->stack, "basic", _("Basic"), + "help:gnome-help/nemo-file-properties-basic"); + + /* Icon pixmap */ + + icon_pixmap_widget = create_image_widget ( + window, should_show_custom_icon_buttons (window)); + gtk_widget_show (icon_pixmap_widget); + + icon_aligner = gtk_alignment_new (1, 0, 0, 0); + gtk_widget_show (icon_aligner); + + gtk_container_add (GTK_CONTAINER (icon_aligner), icon_pixmap_widget); + gtk_box_pack_start (GTK_BOX (hbox), icon_aligner, FALSE, FALSE, 0); + + window->details->icon_chooser = NULL; + + /* Grid */ + + 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 (NEMO_IS_ENTRY (window->details->name_field)) { + nemo_entry_select_all (NEMO_ENTRY (window->details->name_field)); + gtk_widget_grab_focus (GTK_WIDGET (window->details->name_field)); + } + + if (nemo_desktop_item_properties_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 = nemo_desktop_item_properties_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_and_ellipsizing_value (window, grid, + _("Type:"), + "type", + INCONSISTENT_STATE_STRING, + FALSE, + PANGO_ELLIPSIZE_END); + } + + if (should_show_link_target (window)) { + append_title_and_ellipsizing_value (window, grid, + _("Link target:"), + "link_target", + INCONSISTENT_STATE_STRING, + FALSE, + PANGO_ELLIPSIZE_MIDDLE); + } + + if (is_multi_file_window (window) || + nemo_file_is_directory (get_target_file (window))) { + append_directory_contents_fields (window, grid); + } + + append_size_field (window, grid); + + /* Fill in the initial values for directory element counts + * and (total) file size. + */ + if (is_multi_file_window (window) || + nemo_file_is_directory (get_target_file (window))) { + directory_contents_value_field_update (window); + } + + append_blank_row (grid); + + if (should_show_location_info (window)) { + append_title_and_ellipsizing_value (window, grid, _("Location:"), + "where", + INCONSISTENT_STATE_STRING, + location_show_original (window), + PANGO_ELLIPSIZE_MIDDLE); + + append_title_and_ellipsizing_value (window, grid, + _("Volume:"), + "volume", + INCONSISTENT_STATE_STRING, + FALSE, + PANGO_ELLIPSIZE_END); + } + + 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_full", + INCONSISTENT_STATE_STRING, + FALSE); + + append_title_value_pair (window, grid, _("Created:"), + "date_created_full", + INCONSISTENT_STATE_STRING, + FALSE); + } + + if (should_show_modified_date (window)) { + append_title_value_pair (window, grid, _("Modified:"), + "date_modified_full", + 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)) { + volume_usage = create_volume_usage_widget (window); + gtk_container_add_with_properties (GTK_CONTAINER (grid), volume_usage, + "width", 2, + NULL); + } +} + +static gboolean +files_has_directory (NemoPropertiesWindow *window) +{ + GList *l; + + for (l = window->details->target_files; l != NULL; l = l->next) { + NemoFile *file; + file = NEMO_FILE (l->data); + if (nemo_file_is_directory (file)) { + return TRUE; + } + + } + + return FALSE; +} + +static gboolean +files_has_changable_permissions_directory (NemoPropertiesWindow *window) +{ + GList *l; + + for (l = window->details->target_files; l != NULL; l = l->next) { + NemoFile *file; + file = NEMO_FILE (l->data); + if (nemo_file_is_directory (file) && + nemo_file_can_get_permissions (file) && + nemo_file_can_set_permissions (file)) { + return TRUE; + } + + } + + return FALSE; +} + + +static gboolean +files_has_file (NemoPropertiesWindow *window) +{ + GList *l; + + for (l = window->details->target_files; l != NULL; l = l->next) { + NemoFile *file; + file = NEMO_FILE (l->data); + if (!nemo_file_is_directory (file)) { + return TRUE; + } + + } + + return FALSE; +} + +static void +start_long_operation (NemoPropertiesWindow *window) +{ + if (window->details->long_operation_underway == 0) { + /* start long operation */ + GdkCursor * cursor; + + cursor = gdk_cursor_new (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 (NemoPropertiesWindow *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 +size_field_update (NemoPropertiesWindow *window) +{ + NemoFile *file; + goffset size; + g_autofree gchar *size_str = NULL; + + file = get_target_file (window); + + /* Only update the field in case properties of a single file are shown. + * Otherwise directory_contents_value_field_update() will handle this. + */ + if (is_multi_file_window (window) || nemo_file_is_directory (file)) { + return; + } + + size = nemo_file_get_size (file); + size_str = file_size_for_display (size); + + gtk_label_set_text (window->details->size_value_field, size_str); + gtk_widget_set_tooltip_text (GTK_WIDGET (window->details->size_value_field), size_str); +} + +static void +permission_change_callback (NemoFile *file, + GFile *res_loc, + GError *error, + gpointer callback_data) +{ + NemoPropertiesWindow *window; + g_assert (callback_data != NULL); + + window = NEMO_PROPERTIES_WINDOW (callback_data); + end_long_operation (window); + + /* Report the error if it's an error. */ + nemo_report_error_setting_permissions (file, error, NULL); + + g_object_unref (window); +} + +static void +update_permissions (NemoPropertiesWindow *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) { + NemoFile *file; + guint32 permissions; + + file = NEMO_FILE (l->data); + + if (!nemo_file_can_get_permissions (file)) { + continue; + } + + if (!apply_to_both_folder_and_dir && + ((nemo_file_is_directory (file) && !is_folder) || + (!nemo_file_is_directory (file) && is_folder))) { + continue; + } + + permissions = nemo_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); + nemo_file_set_permissions + (file, permissions, + permission_change_callback, + window); + } +} + +static gboolean +initial_permission_state_consistent (NemoPropertiesWindow *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) { + NemoFile *file; + guint32 permissions; + + file = l->data; + + if (!both_folder_and_dir && + ((nemo_file_is_directory (file) && !is_folder) || + (!nemo_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) { + /* Not same permissions as first -> inconsistent */ + return FALSE; + } + } + return TRUE; +} + +static void +permission_button_toggled (GtkToggleButton *button, + NemoPropertiesWindow *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 (NemoPropertiesWindow *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) { + NemoFile *file; + guint32 file_permissions; + + file = NEMO_FILE (l->data); + + if (!nemo_file_can_get_permissions (file)) { + continue; + } + + if (!is_special && + ((nemo_file_is_directory (file) && !is_folder) || + (!nemo_file_is_directory (file) && is_folder))) { + continue; + } + + no_match = FALSE; + + file_permissions = nemo_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 (nemo_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 (NemoPropertiesWindow *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 (NemoPropertiesWindow *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 (NemoPropertiesWindow *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, NemoPropertiesWindow *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 (NemoPropertiesWindow *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) { + NemoFile *file; + guint32 file_permissions; + + file = NEMO_FILE (l->data); + + if (!nemo_file_can_get_permissions (file)) { + continue; + } + + if (nemo_file_is_directory (file)) { + mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC; + } else { + mask = PERMISSION_READ|PERMISSION_WRITE; + } + + file_permissions = nemo_file_get_permissions (file); + + perm = permission_from_vfs (type, file_permissions) & mask; + + if (nemo_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 (nemo_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 (nemo_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 { + PermissionValue 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 (NemoPropertiesWindow *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 (NemoPropertiesWindow *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 (NemoPropertiesWindow *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) { + NemoFile *file; + + file = NEMO_FILE (l->data); + + if (!nemo_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) { + NemoFile *file; + + file = NEMO_FILE (l->data); + + if (!nemo_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; + NemoFile *file; + + file = NEMO_FILE (l->data); + + permissions = nemo_file_get_permissions (file); + g_hash_table_insert (ret, file, + GINT_TO_POINTER (permissions)); + } + + return ret; +} + +static void +create_simple_permissions (NemoPropertiesWindow *window, GtkGrid *page_grid) +{ + gboolean has_file, has_directory; + GtkLabel *group_label; + GtkLabel *owner_label; + GtkWidget *value; + GtkComboBox *group_combo_box; + GtkComboBox *owner_combo_box; + + has_file = files_has_file (window); + has_directory = files_has_directory (window); + + if (!is_multi_file_window (window) && nemo_file_can_set_owner (get_target_file (window))) { + 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) && nemo_file_can_set_group (get_target_file (window))) { + 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);; + + 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); + } + + if (!has_directory) { + GtkLabel *execute_label; + 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 (NemoPropertiesWindow *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 (NemoPropertiesWindow *window, GtkGrid *page_grid) +{ + GtkLabel *group_label; + GtkLabel *owner_label; + GtkComboBox *group_combo_box; + GtkComboBox *owner_combo_box; + gboolean has_directory, has_file; + + if (!is_multi_file_window (window) && nemo_file_can_set_owner (get_target_file (window))) { + + 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) && nemo_file_can_set_group (get_target_file (window))) { + 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 (gboolean success, + gpointer callback_data) +{ + NemoPropertiesWindow *window; + + window = NEMO_PROPERTIES_WINDOW (callback_data); + end_long_operation (window); + + g_object_unref (window); +} + + +static void +apply_recursive_clicked (GtkWidget *recursive_button, + NemoPropertiesWindow *window) +{ + guint32 file_permission, file_permission_mask; + guint32 dir_permission, dir_permission_mask; + guint32 vfs_mask, vfs_new_perm, p; + GtkWidget *button, *combo; + gboolean active, is_folder, is_special, use_original; + GList *l; + GtkTreeModel *model; + GtkTreeIter iter; + PermissionType type; + int new_perm, mask; + + 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) { + NemoFile *file; + char *uri; + + file = NEMO_FILE (l->data); + + if (nemo_file_is_directory (file) && + nemo_file_can_set_permissions (file)) { + uri = nemo_file_get_uri (file); + start_long_operation (window); + g_object_ref (window); + nemo_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 (NemoPropertiesWindow *window) +{ + GtkWidget *vbox, *button, *hbox; + GtkGrid *page_grid; + char *file_name, *prompt_text; + GList *file_list; + + vbox = create_page_with_vbox (window->details->stack, "permissions", + _("Permissions"), + "help:gnome-help/nemo-file-properties-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)) { + 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 (nemo_preferences, NEMO_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) { + 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 { + if (!is_multi_file_window (window)) { + file_name = nemo_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 (NemoPropertiesWindow *window) +{ + GList *providers; + GList *p; + + providers = nemo_module_get_extensions_for_type (NEMO_TYPE_PROPERTY_PAGE_PROVIDER); + + for (p = providers; p != NULL; p = p->next) { + NemoPropertyPageProvider *provider; + GList *pages; + GList *l; + + provider = NEMO_PROPERTY_PAGE_PROVIDER (p->data); + + pages = nemo_property_page_provider_get_pages + (provider, window->details->original_files); + + for (l = pages; l != NULL; l = l->next) { + NemoPropertyPage *page; + GtkWidget *page_widget; + GtkLabel *label; + + page = NEMO_PROPERTY_PAGE (l->data); + + g_object_get (G_OBJECT (page), + "page", &page_widget, "label", &label, + NULL); + + gtk_container_set_border_width (GTK_CONTAINER (page_widget), + STACK_INNER_BORDER); + gtk_stack_add_titled(window->details->stack, + page_widget, + gtk_label_get_text(label), + gtk_label_get_text(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); + } + + nemo_module_extension_list_free (providers); +} + +static gboolean +should_show_permissions (NemoPropertiesWindow *window) +{ + NemoFile *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) + && (nemo_file_is_in_trash (file) || + nemo_file_is_in_recent (file) || + is_computer_directory (file))) { + return FALSE; + } + + return TRUE; +} + +static char * +get_pending_key (GList *file_list) +{ + GList *l; + GList *uris; + GString *key; + + uris = NULL; + for (l = file_list; l != NULL; l = l->next) { + uris = g_list_prepend (uris, nemo_file_get_uri (NEMO_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); + + return g_string_free (key, FALSE); +} + +static StartupData * +startup_data_new (GList *original_files, + GList *target_files, + const char *pending_key, + GtkWidget *parent_widget, + const char *startup_id) +{ + StartupData *data; + GList *l; + + data = g_new0 (StartupData, 1); + data->original_files = nemo_file_list_copy (original_files); + data->target_files = nemo_file_list_copy (target_files); + data->parent_widget = parent_widget; + data->startup_id = g_strdup (startup_id); + data->pending_key = g_strdup (pending_key); + data->pending_files = g_hash_table_new (g_direct_hash, + g_direct_equal); + + 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) +{ + nemo_file_list_free (data->original_files); + nemo_file_list_free (data->target_files); + g_hash_table_destroy (data->pending_files); + g_free (data->pending_key); + g_free (data->startup_id); + g_free (data); +} + +static void +file_changed_callback (NemoFile *file, gpointer user_data) +{ + NemoPropertiesWindow *window = NEMO_PROPERTIES_WINDOW (user_data); + + if (!g_list_find (window->details->changed_files, file)) { + nemo_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 (NemoFile *file) +{ + if (file == NULL || + NEMO_IS_DESKTOP_ICON_FILE (file) || + nemo_file_is_nemo_link (file) || + nemo_file_is_in_trash (file) || + is_computer_directory (file)) { + return TRUE; + } + return FALSE; +} + +static gboolean +should_show_open_with (NemoPropertiesWindow *window) +{ + NemoFile *file; + + /* Don't show open with tab for desktop special icons (trash, etc) + * or desktop files. We don't get the open-with menu for these anyway. + * + * Also don't show it for folders. Changing the default app for folders + * leads to all sort of hard to understand errors. + */ + + if (is_multi_file_window (window)) { + 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 = NEMO_FILE (l->data); + if (nemo_file_is_directory (file) || + is_a_special_file (file)) { + return FALSE; + } + } + } + } else { + file = get_original_file (window); + if (nemo_file_is_directory (file) || + is_a_special_file (file)) { + return FALSE; + } + } + return TRUE; +} + +static void +create_open_with_page (NemoPropertiesWindow *window) +{ + GtkWidget *vbox; + char *mime_type; + char *uri = NULL; + GList *uris = NULL; + + mime_type = nemo_file_get_mime_type (get_target_file (window)); + + if (!is_multi_file_window (window)) { + uri = nemo_file_get_uri (get_target_file (window)); + if (uri == NULL) { + return; + } + } else { + uris = window->details->original_files; + if (uris == NULL) { + return; + } + } + + vbox = nemo_mime_application_chooser_new (uri, uris, mime_type, NULL); + g_free (uri); + + gtk_widget_show (vbox); + g_free (mime_type); + + g_object_set_data_full (G_OBJECT (vbox), "help-uri", g_strdup ("help:gnome-help/files-open"), g_free); + gtk_container_set_border_width (GTK_CONTAINER (vbox), STACK_INNER_BORDER); + gtk_stack_add_titled (window->details->stack, + vbox, "open_with", _("Open With")); +} + + +static NemoPropertiesWindow * +create_properties_window (StartupData *startup_data) +{ + NemoPropertiesWindow *window; + GList *l; + + window = NEMO_PROPERTIES_WINDOW (gtk_widget_new (NEMO_TYPE_PROPERTIES_WINDOW, NULL)); + + window->details->original_files = nemo_file_list_copy (startup_data->original_files); + + window->details->target_files = nemo_file_list_copy (startup_data->target_files); + + gtk_window_set_wmclass (GTK_WINDOW (window), "file_properties", "Nemo"); + + gtk_window_set_default_size (GTK_WINDOW (window), 500, -1); + + if (startup_data->parent_widget) { + gtk_window_set_screen (GTK_WINDOW (window), + gtk_widget_get_screen (startup_data->parent_widget)); + } + + if (startup_data->startup_id) { + gtk_window_set_startup_id (GTK_WINDOW (window), startup_data->startup_id); + } + + 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) { + NemoFile *file; + NemoFileAttributes attributes; + + file = NEMO_FILE (l->data); + + attributes = + NEMO_FILE_ATTRIBUTES_FOR_ICON | + NEMO_FILE_ATTRIBUTE_INFO | + NEMO_FILE_ATTRIBUTE_LINK_INFO; + + nemo_file_monitor_add (file, + &window->details->original_files, + attributes); + } + + for (l = window->details->target_files; l != NULL; l = l->next) { + NemoFile *file; + NemoFileAttributes attributes; + + file = NEMO_FILE (l->data); + + attributes = 0; + if (nemo_file_is_directory (file)) { + attributes |= NEMO_FILE_ATTRIBUTE_DEEP_COUNTS; + } + + attributes |= NEMO_FILE_ATTRIBUTE_INFO; + nemo_file_monitor_add (file, &window->details->target_files, attributes); + } + + for (l = window->details->target_files; l != NULL; l = l->next) { + g_signal_connect_object (NEMO_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 (NEMO_FILE (l->data), + "changed", + G_CALLBACK (file_changed_callback), + G_OBJECT (window), + 0); + } + + /* Create the stack and the stack switcher. */ + GtkWidget *toolbar = gtk_toolbar_new (); + gtk_style_context_add_class (gtk_widget_get_style_context (toolbar), "primary-toolbar"); + gtk_widget_show (toolbar); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area(GTK_DIALOG (window))), + toolbar, + FALSE, FALSE, 0); + + GtkToolItem * tool_item = gtk_tool_item_new (); + gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (tool_item)), "raised"); + gtk_tool_item_set_expand (tool_item, 1); + gtk_widget_show (GTK_WIDGET (tool_item)); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tool_item, 0); + + window->details->stack = GTK_STACK (gtk_stack_new ()); + GtkStackSwitcher *stack_switcher; + stack_switcher = GTK_STACK_SWITCHER (gtk_stack_switcher_new ()); + gtk_stack_switcher_set_stack (stack_switcher, + window->details->stack); + + gtk_stack_set_transition_type (window->details->stack, + GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT); + gtk_widget_set_halign (GTK_WIDGET (stack_switcher), GTK_ALIGN_CENTER); + + gtk_widget_show (GTK_WIDGET (window->details->stack)); + gtk_widget_show (GTK_WIDGET (stack_switcher)); + gtk_container_add(GTK_CONTAINER (tool_item), GTK_WIDGET (stack_switcher)); + + GtkWidget * frame = gtk_frame_new (NULL); + gtk_widget_show (frame); + gtk_style_context_add_class (gtk_widget_get_style_context (frame), "view"); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))), + GTK_WIDGET (frame), + TRUE, TRUE, 0); + + gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (window->details->stack)); + + /* Create the pages. */ + create_basic_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); + + gtk_dialog_add_buttons (GTK_DIALOG (window), + _("Help"), GTK_RESPONSE_HELP, + _("Close"), GTK_RESPONSE_CLOSE, + NULL); + + /* FIXME - HIGificiation, should be done inside GTK+ */ + gtk_widget_set_margin_left (frame, 6); + gtk_widget_set_margin_right (frame, 6); + gtk_widget_set_margin_top (frame, 8); + gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (window))), 0); + gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (window))), 5); + + /* 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) { + NemoFile *target; + + target = get_target_file_for_original_file (NEMO_FILE (l->data)); + + ret = g_list_prepend (ret, target); + } + + ret = g_list_reverse (ret); + + return ret; +} + +static void +add_window (NemoPropertiesWindow *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 (NemoPropertiesWindow *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) +{ + nemo_file_cancel_call_when_ready + (NEMO_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 && startup_data->parent_widget) { + 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 (NemoFile *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) { + NemoPropertiesWindow *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 +nemo_properties_window_present (GList *original_files, + GtkWidget *parent_widget, + const gchar *startup_id) +{ + 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 (parent_widget == NULL || GTK_IS_WIDGET (parent_widget)); + + /* Create the hash tables first time through. */ + if (windows == NULL) { + windows = g_hash_table_new (NULL, NULL); + } + + if (pending_lists == NULL) { + pending_lists = g_hash_table_new (g_str_hash, g_str_equal); + } + + /* Look to see if there's already a window for this file. */ + existing_window = get_existing_window (original_files); + if (existing_window != NULL) { + if (parent_widget) + gtk_window_set_screen (existing_window, + gtk_widget_get_screen (parent_widget)); + else if (startup_id) + gtk_window_set_startup_id (existing_window, startup_id); + + 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, + startup_id); + + nemo_file_list_free (target_files); + g_free(pending_key); + + /* Wait until we can tell whether it's a directory before showing, since + * some one-time layout decisions depend on that info. + */ + + g_hash_table_insert (pending_lists, startup_data->pending_key, startup_data->pending_key); + if (parent_widget) { + g_signal_connect (parent_widget, "destroy", + G_CALLBACK (parent_widget_destroyed_callback), startup_data); + + parent_window = gtk_widget_get_ancestor (parent_widget, GTK_TYPE_WINDOW); + } else + parent_window = NULL; + + 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; + nemo_file_call_when_ready + (NEMO_FILE (l->data), + NEMO_FILE_ATTRIBUTE_INFO, + is_directory_ready_callback, + startup_data); + } +} + +static void +real_response (GtkDialog *dialog, + int response) +{ + GError *error = NULL; + NemoPropertiesWindow *window = NEMO_PROPERTIES_WINDOW (dialog); + GtkWidget *curpage; + const char *helpuri; + + switch (response) { + case GTK_RESPONSE_HELP: + curpage = gtk_stack_get_visible_child (window->details->stack); + helpuri = g_object_get_data (G_OBJECT (curpage), "help-uri"); + gtk_show_uri (gtk_window_get_screen (GTK_WINDOW (dialog)), + helpuri ? helpuri : "help:gnome-help/files", + 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) +{ + NemoPropertiesWindow *window; + GList *l; + + window = NEMO_PROPERTIES_WINDOW (object); + + remove_window (window); + + for (l = window->details->original_files; l != NULL; l = l->next) { + nemo_file_monitor_remove (NEMO_FILE (l->data), &window->details->original_files); + } + nemo_file_list_free (window->details->original_files); + window->details->original_files = NULL; + + for (l = window->details->target_files; l != NULL; l = l->next) { + nemo_file_monitor_remove (NEMO_FILE (l->data), &window->details->target_files); + } + nemo_file_list_free (window->details->target_files); + window->details->target_files = NULL; + + nemo_file_list_free (window->details->changed_files); + window->details->changed_files = NULL; + + window->details->name_field = 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; + } + + window->details->icon_chooser = NULL; + + GTK_WIDGET_CLASS (nemo_properties_window_parent_class)->destroy (object); +} + +static void +real_finalize (GObject *object) +{ + NemoPropertiesWindow *window; + + window = NEMO_PROPERTIES_WINDOW (object); + + g_list_free_full (window->details->mime_list, g_free); + + g_free (window->details->pending_name); + + G_OBJECT_CLASS (nemo_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_string, NemoPropertiesWindow *properties_window) +{ + NemoFile *file; + char *file_uri; + GList *l; + + g_assert (icon_string != NULL); + g_assert (NEMO_IS_PROPERTIES_WINDOW (properties_window)); + + if (g_path_is_absolute (icon_string)) { + GFile *icon_file; + char *icon_path; + char *icon_uri; + char *real_icon_uri; + + icon_file = g_file_new_for_path (icon_string); + + if (!g_file_is_native (icon_file)) { + g_object_unref (icon_file); + return; + } + + icon_path = g_strdup (icon_string); + icon_uri = g_file_get_uri (icon_file); + + g_object_unref (icon_file); + + + for (l = properties_window->details->original_files; l != NULL; l = l->next) { + file = NEMO_FILE (l->data); + file_uri = nemo_file_get_uri (file); + + if (nemo_file_is_mime_type (file, "application/x-desktop")) { + if (nemo_link_local_set_icon (file_uri, icon_path)) { + nemo_file_invalidate_attributes (file, + NEMO_FILE_ATTRIBUTE_INFO | + NEMO_FILE_ATTRIBUTE_LINK_INFO); + } + } else { + real_icon_uri = make_relative_uri_from_full (icon_uri, file_uri); + if (real_icon_uri == NULL) { + real_icon_uri = g_strdup (icon_uri); + } + + nemo_file_set_metadata (file, NEMO_METADATA_KEY_CUSTOM_ICON_NAME, NULL, NULL); + nemo_file_set_metadata (file, NEMO_METADATA_KEY_CUSTOM_ICON, NULL, real_icon_uri); + nemo_file_set_metadata (file, NEMO_METADATA_KEY_ICON_SCALE, NULL, NULL); + + g_free (real_icon_uri); + } + + g_free (file_uri); + } + + g_free (icon_path); + g_free (icon_uri); + } else { + for (l = properties_window->details->original_files; l != NULL; l = l->next) { + file = NEMO_FILE (l->data); + + file_uri = nemo_file_get_uri (file); + + if (nemo_file_is_mime_type (file, "application/x-desktop")) { + if (nemo_link_local_set_icon (file_uri, icon_string)) { + nemo_file_invalidate_attributes (file, + NEMO_FILE_ATTRIBUTE_INFO | + NEMO_FILE_ATTRIBUTE_LINK_INFO); + } + } else { + nemo_file_set_metadata (file, NEMO_METADATA_KEY_CUSTOM_ICON, NULL, NULL); + nemo_file_set_metadata (file, NEMO_METADATA_KEY_CUSTOM_ICON_NAME, NULL, icon_string); + nemo_file_set_metadata (file, NEMO_METADATA_KEY_ICON_SCALE, NULL, NULL); + } + + g_free (file_uri); + } + } +} + +static void +select_image_button_callback (GtkWidget *widget, + NemoPropertiesWindow *window) +{ + GtkWidget *dialog; + gchar *image_uri, *icon_name; + char *return_string; + gint response; + + g_assert (NEMO_IS_PROPERTIES_WINDOW (window)); + + dialog = window->details->icon_chooser; + + image_uri = icon_name = NULL; + + if (dialog == NULL) { + GtkWidget *revert_button; + GList *l; + gboolean revert_is_sensitive; + + dialog = GTK_WIDGET (xapp_icon_chooser_dialog_new ()); + + g_object_set (G_OBJECT (dialog), + "allow-paths", TRUE, + NULL); + + revert_button = gtk_button_new_with_label (_("Revert")); + + gtk_widget_show (revert_button); + + revert_is_sensitive = FALSE; + + for (l = window->details->original_files; l != NULL; l = l->next) { + NemoFile *file; + + file = NEMO_FILE (l->data); + + image_uri = nemo_file_get_metadata (file, NEMO_METADATA_KEY_CUSTOM_ICON, NULL); + icon_name = nemo_file_get_metadata (file, NEMO_METADATA_KEY_CUSTOM_ICON_NAME, NULL); + + revert_is_sensitive = (image_uri != NULL || icon_name != NULL); + + if (revert_is_sensitive) { + break; + } + + g_free (image_uri); + g_free (icon_name); + } + + gtk_widget_set_sensitive (GTK_WIDGET (revert_button), revert_is_sensitive); + + xapp_icon_chooser_dialog_add_button (XAPP_ICON_CHOOSER_DIALOG (dialog), + revert_button, + GTK_PACK_START, + NEMO_RESPONSE_REVERT); + + window->details->icon_chooser = dialog; + + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (window)); + + g_object_add_weak_pointer (G_OBJECT (dialog), + (gpointer *) &window->details->icon_chooser); + + } + + if (image_uri == NULL && icon_name == NULL) { + response = xapp_icon_chooser_dialog_run (XAPP_ICON_CHOOSER_DIALOG (dialog)); + } else { + if (image_uri) { + GFile *icon_location; + gchar *path; + + icon_location = g_file_new_for_uri (image_uri); + path = g_file_get_path (icon_location); + + g_object_unref (icon_location); + + response = xapp_icon_chooser_dialog_run_with_icon (XAPP_ICON_CHOOSER_DIALOG (dialog), + path); + + g_free (path); + } else { + response = xapp_icon_chooser_dialog_run_with_icon (XAPP_ICON_CHOOSER_DIALOG (dialog), + icon_name); + } + } + + switch (response) { + case NEMO_RESPONSE_REVERT: + reset_icon (window); + break; + case GTK_RESPONSE_OK: + return_string = xapp_icon_chooser_dialog_get_icon_string (XAPP_ICON_CHOOSER_DIALOG (dialog)); + set_icon (return_string, window); + g_free (return_string); + break; + default: + break; + } + + g_free (image_uri); + g_free (icon_name); + + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +static void +nemo_properties_window_class_init (NemoPropertiesWindowClass *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); + + g_type_class_add_private (class, sizeof (NemoPropertiesWindowDetails)); +} + +static void +nemo_properties_window_init (NemoPropertiesWindow *window) +{ + window->details = G_TYPE_INSTANCE_GET_PRIVATE (window, NEMO_TYPE_PROPERTIES_WINDOW, + NemoPropertiesWindowDetails); +}