summaryrefslogtreecommitdiff
path: root/src/file-manager/fm-properties-window.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/file-manager/fm-properties-window.c')
-rw-r--r--src/file-manager/fm-properties-window.c5835
1 files changed, 5835 insertions, 0 deletions
diff --git a/src/file-manager/fm-properties-window.c b/src/file-manager/fm-properties-window.c
new file mode 100644
index 00000000..6310b407
--- /dev/null
+++ b/src/file-manager/fm-properties-window.c
@@ -0,0 +1,5835 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-properties-window.c - window that lets user modify file properties
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Darin Adler <[email protected]>
+*/
+
+#include <config.h>
+#include "fm-properties-window.h"
+#include "fm-ditem-page.h"
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+
+#include "fm-error-reporting.h"
+#include "libcaja-private/caja-mime-application-chooser.h"
+#include <eel/eel-accessibility.h>
+#include <eel/eel-gdk-pixbuf-extensions.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-mate-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-labeled-image.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-string.h>
+#include <eel/eel-vfs-extensions.h>
+#include <eel/eel-wrap-table.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <libmateui/mate-desktop-thumbnail.h>
+#include <libcaja-extension/caja-property-page-provider.h>
+#include <libcaja-private/caja-customization-data.h>
+#include <libcaja-private/caja-entry.h>
+#include <libcaja-private/caja-file-attributes.h>
+#include <libcaja-private/caja-file-operations.h>
+#include <libcaja-private/caja-desktop-icon-file.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-emblem-utils.h>
+#include <libcaja-private/caja-link.h>
+#include <libcaja-private/caja-metadata.h>
+#include <libcaja-private/caja-module.h>
+#include <libcaja-private/caja-undo-signal-handlers.h>
+#include <libcaja-private/caja-mime-actions.h>
+#include <libcaja-private/caja-undo.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <cairo.h>
+
+#if HAVE_SYS_VFS_H
+#include <sys/vfs.h>
+#elif HAVE_SYS_MOUNT_H
+#if HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#include <sys/mount.h>
+#endif
+
+#define USED_FILL_R (0.988235294 * 65535)
+#define USED_FILL_G (0.91372549 * 65535)
+#define USED_FILL_B (0.309803922 * 65535)
+
+#define FREE_FILL_R (0.447058824 * 65535)
+#define FREE_FILL_G (0.623529412 * 65535)
+#define FREE_FILL_B (0.811764706 * 65535)
+
+
+#define PREVIEW_IMAGE_WIDTH 96
+
+#define ROW_PAD 6
+
+static GHashTable *windows;
+static GHashTable *pending_lists;
+
+struct FMPropertiesWindowDetails {
+ GList *original_files;
+ GList *target_files;
+
+ GtkNotebook *notebook;
+
+ GtkTable *basic_table;
+ GtkTable *permissions_table;
+ gboolean advanced_permissions;
+
+ GtkWidget *icon_button;
+ GtkWidget *icon_image;
+ GtkWidget *icon_chooser;
+
+ GtkLabel *name_label;
+ GtkWidget *name_field;
+ unsigned int name_row;
+ char *pending_name;
+
+ GtkLabel *directory_contents_title_field;
+ GtkLabel *directory_contents_value_field;
+ guint update_directory_contents_timeout_id;
+ guint update_files_timeout_id;
+
+ GList *emblem_buttons;
+ GHashTable *initial_emblems;
+
+ CajaFile *group_change_file;
+ char *group_change_group;
+ unsigned int group_change_timeout;
+ CajaFile *owner_change_file;
+ char *owner_change_owner;
+ unsigned int owner_change_timeout;
+
+ GList *permission_buttons;
+ GList *permission_combos;
+ GHashTable *initial_permissions;
+ gboolean has_recursive_apply;
+
+ GList *value_fields;
+
+ GList *mime_list;
+
+ gboolean deep_count_finished;
+
+ guint total_count;
+ goffset total_size;
+
+ guint long_operation_underway;
+
+ GList *changed_files;
+
+ guint64 volume_capacity;
+ guint64 volume_free;
+
+ GdkColor used_color;
+ GdkColor free_color;
+ GdkColor used_stroke_color;
+ GdkColor free_stroke_color;
+};
+
+enum {
+ PERMISSIONS_CHECKBOXES_OWNER_ROW,
+ PERMISSIONS_CHECKBOXES_GROUP_ROW,
+ PERMISSIONS_CHECKBOXES_OTHERS_ROW,
+ PERMISSIONS_CHECKBOXES_ROW_COUNT
+};
+
+enum {
+ PERMISSIONS_CHECKBOXES_READ_COLUMN,
+ PERMISSIONS_CHECKBOXES_WRITE_COLUMN,
+ PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN,
+ PERMISSIONS_CHECKBOXES_COLUMN_COUNT
+};
+
+enum {
+ TITLE_COLUMN,
+ VALUE_COLUMN,
+ COLUMN_COUNT
+};
+
+typedef struct {
+ GList *original_files;
+ GList *target_files;
+ GtkWidget *parent_widget;
+ char *pending_key;
+ GHashTable *pending_files;
+} StartupData;
+
+/* drag and drop definitions */
+
+enum {
+ TARGET_URI_LIST,
+ TARGET_MATE_URI_LIST,
+ TARGET_RESET_BACKGROUND
+};
+
+static const GtkTargetEntry target_table[] = {
+ { "text/uri-list", 0, TARGET_URI_LIST },
+ { "x-special/mate-icon-list", 0, TARGET_MATE_URI_LIST },
+ { "x-special/mate-reset-background", 0, TARGET_RESET_BACKGROUND }
+};
+
+#define DIRECTORY_CONTENTS_UPDATE_INTERVAL 200 /* milliseconds */
+#define FILES_UPDATE_INTERVAL 200 /* milliseconds */
+#define STANDARD_EMBLEM_HEIGHT 52
+#define EMBLEM_LABEL_SPACING 2
+
+/*
+ * A timeout before changes through the user/group combo box will be applied.
+ * When quickly changing owner/groups (i.e. by keyboard or scroll wheel),
+ * this ensures that the GUI doesn't end up unresponsive.
+ *
+ * Both combos react on changes by scheduling a new change and unscheduling
+ * or cancelling old pending changes.
+ */
+#define CHOWN_CHGRP_TIMEOUT 300 /* milliseconds */
+
+static void directory_contents_value_field_update (FMPropertiesWindow *window);
+static void file_changed_callback (CajaFile *file,
+ gpointer user_data);
+static void permission_button_update (FMPropertiesWindow *window,
+ GtkToggleButton *button);
+static void permission_combo_update (FMPropertiesWindow *window,
+ GtkComboBox *combo);
+static void value_field_update (FMPropertiesWindow *window,
+ GtkLabel *field);
+static void properties_window_update (FMPropertiesWindow *window,
+ GList *files);
+static void is_directory_ready_callback (CajaFile *file,
+ gpointer data);
+static void cancel_group_change_callback (FMPropertiesWindow *window);
+static void cancel_owner_change_callback (FMPropertiesWindow *window);
+static void parent_widget_destroyed_callback (GtkWidget *widget,
+ gpointer callback_data);
+static void select_image_button_callback (GtkWidget *widget,
+ FMPropertiesWindow *properties_window);
+static void set_icon (const char *icon_path,
+ FMPropertiesWindow *properties_window);
+static void remove_pending (StartupData *data,
+ gboolean cancel_call_when_ready,
+ gboolean cancel_timed_wait,
+ gboolean cancel_destroy_handler);
+static void append_extension_pages (FMPropertiesWindow *window);
+
+static gboolean name_field_focus_out (CajaEntry *name_field,
+ GdkEventFocus *event,
+ gpointer callback_data);
+static void name_field_activate (CajaEntry *name_field,
+ gpointer callback_data);
+static GtkLabel *attach_ellipsizing_value_label (GtkTable *table,
+ int row,
+ int column,
+ const char *initial_text);
+
+static GtkWidget* create_pie_widget (FMPropertiesWindow *window);
+
+G_DEFINE_TYPE (FMPropertiesWindow, fm_properties_window, GTK_TYPE_DIALOG);
+#define parent_class fm_properties_window_parent_class
+
+static gboolean
+is_multi_file_window (FMPropertiesWindow *window)
+{
+ GList *l;
+ int count;
+
+ count = 0;
+
+ for (l = window->details->original_files; l != NULL; l = l->next) {
+ if (!caja_file_is_gone (CAJA_FILE (l->data))) {
+ count++;
+ if (count > 1) {
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+static int
+get_not_gone_original_file_count (FMPropertiesWindow *window)
+{
+ GList *l;
+ int count;
+
+ count = 0;
+
+ for (l = window->details->original_files; l != NULL; l = l->next) {
+ if (!caja_file_is_gone (CAJA_FILE (l->data))) {
+ count++;
+ }
+ }
+
+ return count;
+}
+
+static CajaFile *
+get_original_file (FMPropertiesWindow *window)
+{
+ g_return_val_if_fail (!is_multi_file_window (window), NULL);
+
+ if (window->details->original_files == NULL) {
+ return NULL;
+ }
+
+ return CAJA_FILE (window->details->original_files->data);
+}
+
+static CajaFile *
+get_target_file_for_original_file (CajaFile *file)
+{
+ CajaFile *target_file;
+ GFile *location;
+ char *uri_to_display;
+ CajaDesktopLink *link;
+
+ target_file = NULL;
+ if (CAJA_IS_DESKTOP_ICON_FILE (file)) {
+ link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (file));
+
+ if (link != NULL) {
+ /* map to linked URI for these types of links */
+ location = caja_desktop_link_get_activation_location (link);
+ if (location) {
+ target_file = caja_file_get (location);
+ g_object_unref (location);
+ }
+
+ g_object_unref (link);
+ }
+ } else {
+ uri_to_display = caja_file_get_activation_uri (file);
+ if (uri_to_display != NULL) {
+ target_file = caja_file_get_by_uri (uri_to_display);
+ g_free (uri_to_display);
+ }
+ }
+
+ if (target_file != NULL) {
+ return target_file;
+ }
+
+ /* Ref passed-in file here since we've decided to use it. */
+ caja_file_ref (file);
+ return file;
+}
+
+static CajaFile *
+get_target_file (FMPropertiesWindow *window)
+{
+ return CAJA_FILE (window->details->target_files->data);
+}
+
+static void
+add_prompt (GtkVBox *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 (GtkVBox *vbox, const char *prompt_text)
+{
+ GtkWidget *separator_line;
+
+ add_prompt (vbox, prompt_text, FALSE);
+
+ separator_line = gtk_hseparator_new ();
+ gtk_widget_show (separator_line);
+ gtk_box_pack_end (GTK_BOX (vbox), separator_line, TRUE, TRUE, 2*ROW_PAD);
+}
+
+static void
+get_image_for_properties_window (FMPropertiesWindow *window,
+ char **icon_name,
+ GdkPixbuf **icon_pixbuf)
+{
+ CajaIconInfo *icon, *new_icon;
+ GList *l;
+
+ icon = NULL;
+ for (l = window->details->original_files; l != NULL; l = l->next) {
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+
+ if (!icon) {
+ icon = caja_file_get_icon (file, CAJA_ICON_SIZE_STANDARD, CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS | CAJA_FILE_ICON_FLAGS_IGNORE_VISITING);
+ } else {
+ new_icon = caja_file_get_icon (file, CAJA_ICON_SIZE_STANDARD, CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS | CAJA_FILE_ICON_FLAGS_IGNORE_VISITING);
+ if (!new_icon || new_icon != icon) {
+ g_object_unref (icon);
+ g_object_unref (new_icon);
+ icon = NULL;
+ break;
+ }
+ g_object_unref (new_icon);
+ }
+ }
+
+ if (!icon) {
+ icon = caja_icon_info_lookup_from_name ("text-x-generic", CAJA_ICON_SIZE_STANDARD);
+ }
+
+ if (icon_name != NULL) {
+ *icon_name = g_strdup (caja_icon_info_get_used_name (icon));
+ }
+
+ if (icon_pixbuf != NULL) {
+ *icon_pixbuf = caja_icon_info_get_pixbuf_at_size (icon, CAJA_ICON_SIZE_STANDARD);
+ }
+
+ g_object_unref (icon);
+}
+
+
+static void
+update_properties_window_icon (GtkImage *image)
+{
+ FMPropertiesWindow *window;
+ GdkPixbuf *pixbuf;
+ char *name;
+
+ window = g_object_get_data (G_OBJECT (image), "properties_window");
+
+ 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);
+ }
+
+ gtk_image_set_from_pixbuf (image, pixbuf);
+
+ g_free (name);
+ g_object_unref (pixbuf);
+}
+
+/* utility to test if a uri refers to a local image */
+static gboolean
+uri_is_local_image (const char *uri)
+{
+ GdkPixbuf *pixbuf;
+ char *image_path;
+
+ image_path = g_filename_from_uri (uri, NULL, NULL);
+ if (image_path == NULL) {
+ return FALSE;
+ }
+
+ pixbuf = gdk_pixbuf_new_from_file (image_path, NULL);
+ g_free (image_path);
+
+ if (pixbuf == NULL) {
+ return FALSE;
+ }
+ g_object_unref (pixbuf);
+ return TRUE;
+}
+
+
+static void
+reset_icon (FMPropertiesWindow *properties_window)
+{
+ GList *l;
+
+ for (l = properties_window->details->original_files; l != NULL; l = l->next) {
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+
+ caja_file_set_metadata (file,
+ CAJA_METADATA_KEY_ICON_SCALE,
+ NULL, NULL);
+ caja_file_set_metadata (file,
+ CAJA_METADATA_KEY_CUSTOM_ICON,
+ NULL, NULL);
+ }
+}
+
+
+static void
+fm_properties_window_drag_data_received (GtkWidget *widget, GdkDragContext *context,
+ int x, int y,
+ GtkSelectionData *selection_data,
+ guint info, guint time)
+{
+ char **uris;
+ gboolean exactly_one;
+ GtkImage *image;
+ GtkWindow *window;
+
+ image = GTK_IMAGE (widget);
+ window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (image)));
+
+ if (info == TARGET_RESET_BACKGROUND) {
+ reset_icon (FM_PROPERTIES_WINDOW (window));
+
+ return;
+ }
+
+ uris = g_strsplit (gtk_selection_data_get_data (selection_data), "\r\n", 0);
+ exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0');
+
+
+ if (!exactly_one) {
+ eel_show_error_dialog
+ (_("You cannot assign more than one custom icon at a time!"),
+ _("Please drag just one image to set a custom icon."),
+ window);
+ } else {
+ if (uri_is_local_image (uris[0])) {
+ set_icon (uris[0], FM_PROPERTIES_WINDOW (window));
+ } else {
+ GFile *f;
+
+ f = g_file_new_for_uri (uris[0]);
+ if (!g_file_is_native (f)) {
+ eel_show_error_dialog
+ (_("The file that you dropped is not local."),
+ _("You can only use local images as custom icons."),
+ window);
+
+ } else {
+ eel_show_error_dialog
+ (_("The file that you dropped is not an image."),
+ _("You can only use local images as custom icons."),
+ window);
+ }
+ g_object_unref (f);
+ }
+ }
+ g_strfreev (uris);
+}
+
+static GtkWidget *
+create_image_widget (FMPropertiesWindow *window,
+ gboolean is_customizable)
+{
+ GtkWidget *button;
+ GtkWidget *image;
+ GdkPixbuf *pixbuf;
+
+ get_image_for_properties_window (window, NULL, &pixbuf);
+
+ image = gtk_image_new ();
+ gtk_widget_show (image);
+
+ button = NULL;
+ if (is_customizable) {
+ button = gtk_button_new ();
+ gtk_container_add (GTK_CONTAINER (button), image);
+
+ /* prepare the image to receive dropped objects to assign custom images */
+ gtk_drag_dest_set (GTK_WIDGET (image),
+ GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
+ target_table, G_N_ELEMENTS (target_table),
+ GDK_ACTION_COPY | GDK_ACTION_MOVE);
+
+ g_signal_connect (image, "drag_data_received",
+ G_CALLBACK (fm_properties_window_drag_data_received), NULL);
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (select_image_button_callback), window);
+ }
+
+ gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf);
+
+ g_object_unref (pixbuf);
+
+ g_object_set_data (G_OBJECT (image), "properties_window", window);
+
+ window->details->icon_image = image;
+ window->details->icon_button = button;
+
+ return button != NULL ? button : image;
+}
+
+static void
+set_name_field (FMPropertiesWindow *window, const gchar *original_name,
+ const gchar *name)
+{
+ gboolean new_widget;
+ gboolean use_label;
+
+ /* There are four cases here:
+ * 1) Changing the text of a label
+ * 2) Changing the text of an entry
+ * 3) Creating label (potentially replacing entry)
+ * 4) Creating entry (potentially replacing label)
+ */
+ use_label = is_multi_file_window (window) || !caja_file_can_rename (get_original_file (window));
+ new_widget = !window->details->name_field || (use_label ? CAJA_IS_ENTRY (window->details->name_field) : GTK_IS_LABEL (window->details->name_field));
+
+ if (new_widget) {
+ if (window->details->name_field) {
+ gtk_widget_destroy (window->details->name_field);
+ }
+
+ if (use_label) {
+ window->details->name_field =
+ GTK_WIDGET (attach_ellipsizing_value_label
+ (window->details->basic_table,
+ window->details->name_row,
+ VALUE_COLUMN, name));
+ } else {
+ window->details->name_field = caja_entry_new ();
+ gtk_entry_set_text (GTK_ENTRY (window->details->name_field), name);
+ gtk_widget_show (window->details->name_field);
+ gtk_table_attach (window->details->basic_table,
+ window->details->name_field,
+ VALUE_COLUMN,
+ VALUE_COLUMN + 1,
+ window->details->name_row,
+ window->details->name_row + 1,
+ GTK_FILL, 0,
+ 0, 0);
+ 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 Caja 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 */
+ caja_undo_set_up_caja_entry_for_undo ( CAJA_ENTRY (window->details->name_field));
+ caja_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 (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+
+ gtk_label_set_text_with_mnemonic (window->details->name_label,
+ ngettext ("_Name:", "_Names:",
+ get_not_gone_original_file_count (window)));
+
+ if (is_multi_file_window (window)) {
+ /* Multifile property dialog, show all names */
+ GString *str;
+ char *name;
+ gboolean first;
+ GList *l;
+
+ str = g_string_new ("");
+
+ first = TRUE;
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ file = CAJA_FILE (l->data);
+
+ if (!caja_file_is_gone (file)) {
+ if (!first) {
+ g_string_append (str, ", ");
+ }
+ first = FALSE;
+
+ name = caja_file_get_display_name (file);
+ g_string_append (str, name);
+ g_free (name);
+ }
+ }
+ set_name_field (window, NULL, str->str);
+ g_string_free (str, TRUE);
+ } else {
+ const char *original_name = NULL;
+ char *current_name;
+
+ file = get_original_file (window);
+
+ if (file == NULL || caja_file_is_gone (file)) {
+ current_name = g_strdup ("");
+ } else {
+ current_name = caja_file_get_display_name (file);
+ }
+
+ /* If the file name has changed since the original name was stored,
+ * update the text in the text field, possibly (deliberately) clobbering
+ * an edit in progress. If the name hasn't changed (but some other
+ * aspect of the file might have), then don't clobber changes.
+ */
+ if (window->details->name_field) {
+ original_name = (const char *) g_object_get_data (G_OBJECT (window->details->name_field), "original_name");
+ }
+
+ set_name_field (window, original_name, current_name);
+
+ if (original_name == NULL ||
+ eel_strcmp (original_name, current_name) != 0) {
+ g_object_set_data_full (G_OBJECT (window->details->name_field),
+ "original_name",
+ current_name,
+ g_free);
+ } else {
+ g_free (current_name);
+ }
+ }
+}
+
+static void
+name_field_restore_original_name (CajaEntry *name_field)
+{
+ const char *original_name;
+ char *displayed_name;
+
+ original_name = (const char *) g_object_get_data (G_OBJECT (name_field),
+ "original_name");
+
+ if (!original_name) {
+ return;
+ }
+
+ displayed_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1);
+
+ if (strcmp (original_name, displayed_name) != 0) {
+ gtk_entry_set_text (GTK_ENTRY (name_field), original_name);
+ }
+ caja_entry_select_all (name_field);
+
+ g_free (displayed_name);
+}
+
+static void
+rename_callback (CajaFile *file, GFile *res_loc, GError *error, gpointer callback_data)
+{
+ FMPropertiesWindow *window;
+ char *new_name;
+
+ window = FM_PROPERTIES_WINDOW (callback_data);
+
+ /* Complain to user if rename failed. */
+ if (error != NULL) {
+ new_name = window->details->pending_name;
+ fm_report_error_renaming_file (file,
+ window->details->pending_name,
+ error,
+ GTK_WINDOW (window));
+ if (window->details->name_field != NULL) {
+ name_field_restore_original_name (CAJA_ENTRY (window->details->name_field));
+ }
+ }
+
+ g_object_unref (window);
+}
+
+static void
+set_pending_name (FMPropertiesWindow *window, const char *name)
+{
+ g_free (window->details->pending_name);
+ window->details->pending_name = g_strdup (name);
+}
+
+static void
+name_field_done_editing (CajaEntry *name_field, FMPropertiesWindow *window)
+{
+ CajaFile *file;
+ char *new_name;
+ const char *original_name;
+
+ g_return_if_fail (CAJA_IS_ENTRY (name_field));
+
+ /* Don't apply if the dialog has more than one file */
+ if (is_multi_file_window (window)) {
+ return;
+ }
+
+ file = get_original_file (window);
+
+ /* This gets called when the window is closed, which might be
+ * caused by the file having been deleted.
+ */
+ if (file == NULL || caja_file_is_gone (file)) {
+ return;
+ }
+
+ new_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1);
+
+ /* Special case: silently revert text if new text is empty. */
+ if (strlen (new_name) == 0) {
+ name_field_restore_original_name (CAJA_ENTRY (name_field));
+ } else {
+ original_name = (const char *) g_object_get_data (G_OBJECT (window->details->name_field),
+ "original_name");
+ /* Don't rename if not changed since we read the display name.
+ This is needed so that we don't save the display name to the
+ file when nothing is changed */
+ if (strcmp (new_name, original_name) != 0) {
+ set_pending_name (window, new_name);
+ g_object_ref (window);
+ caja_file_rename (file, new_name,
+ rename_callback, window);
+ }
+ }
+
+ g_free (new_name);
+}
+
+static gboolean
+name_field_focus_out (CajaEntry *name_field,
+ GdkEventFocus *event,
+ gpointer callback_data)
+{
+ g_assert (FM_IS_PROPERTIES_WINDOW (callback_data));
+
+ if (gtk_widget_get_sensitive (GTK_WIDGET (name_field))) {
+ name_field_done_editing (name_field, FM_PROPERTIES_WINDOW (callback_data));
+ }
+
+ return FALSE;
+}
+
+static void
+name_field_activate (CajaEntry *name_field, gpointer callback_data)
+{
+ g_assert (CAJA_IS_ENTRY (name_field));
+ g_assert (FM_IS_PROPERTIES_WINDOW (callback_data));
+
+ /* Accept changes. */
+ name_field_done_editing (name_field, FM_PROPERTIES_WINDOW (callback_data));
+
+ caja_entry_select_all_at_idle (name_field);
+}
+
+static gboolean
+file_has_keyword (CajaFile *file, const char *keyword)
+{
+ GList *keywords, *word;
+
+ keywords = caja_file_get_keywords (file);
+ word = g_list_find_custom (keywords, keyword, (GCompareFunc) strcmp);
+ eel_g_list_free_deep (keywords);
+
+ return (word != NULL);
+}
+
+static void
+get_initial_emblem_state (FMPropertiesWindow *window,
+ const char *name,
+ GList **on,
+ GList **off)
+{
+ GList *l;
+
+ *on = NULL;
+ *off = NULL;
+
+ for (l = window->details->original_files; l != NULL; l = l->next) {
+ GList *initial_emblems;
+
+ initial_emblems = g_hash_table_lookup (window->details->initial_emblems,
+ l->data);
+
+ if (g_list_find_custom (initial_emblems, name, (GCompareFunc) strcmp)) {
+ *on = g_list_prepend (*on, l->data);
+ } else {
+ *off = g_list_prepend (*off, l->data);
+ }
+ }
+}
+
+static void
+emblem_button_toggled (GtkToggleButton *button,
+ FMPropertiesWindow *window)
+{
+ GList *l;
+ GList *keywords;
+ GList *word;
+ char *name;
+ GList *files_on;
+ GList *files_off;
+
+ name = g_object_get_data (G_OBJECT (button), "caja_emblem_name");
+
+ files_on = NULL;
+ files_off = NULL;
+ if (gtk_toggle_button_get_active (button)
+ && !gtk_toggle_button_get_inconsistent (button)) {
+ /* Go to the initial state unless the initial state was
+ consistent */
+ get_initial_emblem_state (window, name,
+ &files_on, &files_off);
+
+ if (!(files_on && files_off)) {
+ g_list_free (files_on);
+ g_list_free (files_off);
+ files_on = g_list_copy (window->details->original_files);
+ files_off = NULL;
+ }
+ } else if (gtk_toggle_button_get_inconsistent (button)
+ && !gtk_toggle_button_get_active (button)) {
+ files_on = g_list_copy (window->details->original_files);
+ files_off = NULL;
+ } else {
+ files_off = g_list_copy (window->details->original_files);
+ files_on = NULL;
+ }
+
+ g_signal_handlers_block_by_func (G_OBJECT (button),
+ G_CALLBACK (emblem_button_toggled),
+ window);
+
+ gtk_toggle_button_set_active (button, files_on != NULL);
+ gtk_toggle_button_set_inconsistent (button, files_on && files_off);
+
+ g_signal_handlers_unblock_by_func (G_OBJECT (button),
+ G_CALLBACK (emblem_button_toggled),
+ window);
+
+ for (l = files_on; l != NULL; l = l->next) {
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+
+ keywords = caja_file_get_keywords (file);
+
+ word = g_list_find_custom (keywords, name, (GCompareFunc)strcmp);
+ if (!word) {
+ keywords = g_list_prepend (keywords, g_strdup (name));
+ }
+ caja_file_set_keywords (file, keywords);
+ eel_g_list_free_deep (keywords);
+ }
+
+ for (l = files_off; l != NULL; l = l->next) {
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+
+ keywords = caja_file_get_keywords (file);
+
+ word = g_list_find_custom (keywords, name, (GCompareFunc)strcmp);
+ if (word) {
+ keywords = g_list_remove_link (keywords, word);
+ eel_g_list_free_deep (word);
+ }
+ caja_file_set_keywords (file, keywords);
+ eel_g_list_free_deep (keywords);
+ }
+
+ g_list_free (files_on);
+ g_list_free (files_off);
+}
+
+static void
+emblem_button_update (FMPropertiesWindow *window,
+ GtkToggleButton *button)
+{
+ GList *l;
+ char *name;
+ gboolean all_set;
+ gboolean all_unset;
+
+ name = g_object_get_data (G_OBJECT (button), "caja_emblem_name");
+
+ all_set = TRUE;
+ all_unset = TRUE;
+ for (l = window->details->original_files; l != NULL; l = l->next) {
+ gboolean has_keyword;
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+
+ has_keyword = file_has_keyword (file, name);
+
+ if (has_keyword) {
+ all_unset = FALSE;
+ } else {
+ all_set = FALSE;
+ }
+ }
+
+ g_signal_handlers_block_by_func (G_OBJECT (button),
+ G_CALLBACK (emblem_button_toggled),
+ window);
+
+ gtk_toggle_button_set_active (button, !all_unset);
+ gtk_toggle_button_set_inconsistent (button, !all_unset && !all_set);
+
+ g_signal_handlers_unblock_by_func (G_OBJECT (button),
+ G_CALLBACK (emblem_button_toggled),
+ window);
+
+}
+
+static void
+update_properties_window_title (FMPropertiesWindow *window)
+{
+ char *name, *title;
+ CajaFile *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 = caja_file_get_display_name (file);
+ title = g_strdup_printf (_("%s Properties"), name);
+ g_free (name);
+ }
+ }
+
+ gtk_window_set_title (GTK_WINDOW (window), title);
+
+ g_free (title);
+}
+
+static void
+clear_extension_pages (FMPropertiesWindow *window)
+{
+ int i;
+ int num_pages;
+ GtkWidget *page;
+
+ num_pages = gtk_notebook_get_n_pages
+ (GTK_NOTEBOOK (window->details->notebook));
+
+ for (i = 0; i < num_pages; i++) {
+ page = gtk_notebook_get_nth_page
+ (GTK_NOTEBOOK (window->details->notebook), i);
+
+ if (g_object_get_data (G_OBJECT (page), "is-extension-page")) {
+ gtk_notebook_remove_page
+ (GTK_NOTEBOOK (window->details->notebook), i);
+ num_pages--;
+ i--;
+ }
+ }
+}
+
+static void
+refresh_extension_pages (FMPropertiesWindow *window)
+{
+ clear_extension_pages (window);
+ append_extension_pages (window);
+}
+
+static void
+remove_from_dialog (FMPropertiesWindow *window,
+ CajaFile *file)
+{
+ int index;
+ GList *original_link;
+ GList *target_link;
+ CajaFile *original_file;
+ CajaFile *target_file;
+
+ index = g_list_index (window->details->target_files, file);
+ if (index == -1) {
+ index = g_list_index (window->details->original_files, file);
+ g_return_if_fail (index != -1);
+ }
+
+ original_link = g_list_nth (window->details->original_files, index);
+ target_link = g_list_nth (window->details->target_files, index);
+
+ g_return_if_fail (original_link && target_link);
+
+ original_file = CAJA_FILE (original_link->data);
+ target_file = CAJA_FILE (target_link->data);
+
+ window->details->original_files = g_list_remove_link (window->details->original_files, original_link);
+ g_list_free (original_link);
+
+ window->details->target_files = g_list_remove_link (window->details->target_files, target_link);
+ g_list_free (target_link);
+
+ g_hash_table_remove (window->details->initial_emblems, original_file);
+ g_hash_table_remove (window->details->initial_permissions, target_file);
+
+ g_signal_handlers_disconnect_by_func (original_file,
+ G_CALLBACK (file_changed_callback),
+ window);
+ g_signal_handlers_disconnect_by_func (target_file,
+ G_CALLBACK (file_changed_callback),
+ window);
+
+ caja_file_monitor_remove (original_file, &window->details->original_files);
+ caja_file_monitor_remove (target_file, &window->details->target_files);
+
+ caja_file_unref (original_file);
+ caja_file_unref (target_file);
+
+}
+
+static gboolean
+mime_list_equal (GList *a, GList *b)
+{
+ while (a && b) {
+ if (strcmp (a->data, b->data)) {
+ return FALSE;
+ }
+ a = a->next;
+ b = b->next;
+ }
+
+ return (a == b);
+}
+
+static GList *
+get_mime_list (FMPropertiesWindow *window)
+{
+ GList *ret;
+ GList *l;
+
+ ret = NULL;
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ ret = g_list_append (ret, caja_file_get_mime_type (CAJA_FILE (l->data)));
+ }
+ ret = g_list_reverse (ret);
+ return ret;
+}
+
+static void
+properties_window_update (FMPropertiesWindow *window,
+ GList *files)
+{
+ GList *l;
+ GList *mime_list;
+ GList *tmp;
+ CajaFile *changed_file;
+ gboolean dirty_original = FALSE;
+ gboolean dirty_target = FALSE;
+
+ if (files == NULL) {
+ dirty_original = TRUE;
+ dirty_target = TRUE;
+ }
+
+ for (tmp = files; tmp != NULL; tmp = tmp->next) {
+ changed_file = CAJA_FILE (tmp->data);
+
+ if (changed_file && caja_file_is_gone (changed_file)) {
+ /* Remove the file from the property dialog */
+ remove_from_dialog (window, changed_file);
+ changed_file = NULL;
+
+ if (window->details->original_files == NULL) {
+ return;
+ }
+ }
+ if (changed_file == NULL ||
+ g_list_find (window->details->original_files, changed_file)) {
+ dirty_original = TRUE;
+ }
+ if (changed_file == NULL ||
+ g_list_find (window->details->target_files, changed_file)) {
+ dirty_target = TRUE;
+ }
+
+ }
+
+ if (dirty_original) {
+ update_properties_window_title (window);
+ update_properties_window_icon (GTK_IMAGE (window->details->icon_image));
+
+ update_name_field (window);
+
+ for (l = window->details->emblem_buttons; l != NULL; l = l->next) {
+ emblem_button_update (window, GTK_TOGGLE_BUTTON (l->data));
+ }
+
+ /* If any of the value fields start to depend on the original
+ * value, value_field_updates should be added here */
+ }
+
+ if (dirty_target) {
+ for (l = window->details->permission_buttons; l != NULL; l = l->next) {
+ permission_button_update (window, GTK_TOGGLE_BUTTON (l->data));
+ }
+
+ for (l = window->details->permission_combos; l != NULL; l = l->next) {
+ permission_combo_update (window, GTK_COMBO_BOX (l->data));
+ }
+
+ for (l = window->details->value_fields; l != NULL; l = l->next) {
+ value_field_update (window, GTK_LABEL (l->data));
+ }
+ }
+
+ mime_list = get_mime_list (window);
+
+ if (!window->details->mime_list) {
+ window->details->mime_list = mime_list;
+ } else {
+ if (!mime_list_equal (window->details->mime_list, mime_list)) {
+ refresh_extension_pages (window);
+ }
+
+ eel_g_list_free_deep (window->details->mime_list);
+ window->details->mime_list = mime_list;
+ }
+}
+
+static gboolean
+update_files_callback (gpointer data)
+{
+ FMPropertiesWindow *window;
+
+ window = FM_PROPERTIES_WINDOW (data);
+
+ window->details->update_files_timeout_id = 0;
+
+ properties_window_update (window, window->details->changed_files);
+
+ if (window->details->original_files == NULL) {
+ /* Close the window if no files are left */
+ gtk_widget_destroy (GTK_WIDGET (window));
+ } else {
+ caja_file_list_free (window->details->changed_files);
+ window->details->changed_files = NULL;
+ }
+
+ return FALSE;
+ }
+
+static void
+schedule_files_update (FMPropertiesWindow *window)
+ {
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+
+ if (window->details->update_files_timeout_id == 0) {
+ window->details->update_files_timeout_id
+ = g_timeout_add (FILES_UPDATE_INTERVAL,
+ update_files_callback,
+ window);
+ }
+ }
+
+static gboolean
+file_list_attributes_identical (GList *file_list, const char *attribute_name)
+{
+ gboolean identical;
+ char *first_attr;
+ GList *l;
+
+ first_attr = NULL;
+ identical = TRUE;
+
+ for (l = file_list; l != NULL; l = l->next) {
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+
+ if (caja_file_is_gone (file)) {
+ continue;
+ }
+
+ if (first_attr == NULL) {
+ first_attr = caja_file_get_string_attribute_with_default (file, attribute_name);
+ } else {
+ char *attr;
+ attr = caja_file_get_string_attribute_with_default (file, attribute_name);
+ if (strcmp (attr, first_attr)) {
+ identical = FALSE;
+ g_free (attr);
+ break;
+ }
+ g_free (attr);
+ }
+ }
+
+ g_free (first_attr);
+ return identical;
+}
+
+static char *
+file_list_get_string_attribute (GList *file_list,
+ const char *attribute_name,
+ const char *inconsistent_value)
+{
+ if (file_list_attributes_identical (file_list, attribute_name)) {
+ GList *l;
+
+ for (l = file_list; l != NULL; l = l->next) {
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+ if (!caja_file_is_gone (file)) {
+ return caja_file_get_string_attribute_with_default
+ (file,
+ attribute_name);
+ }
+ }
+ return g_strdup (_("unknown"));
+ } else {
+ return g_strdup (inconsistent_value);
+ }
+}
+
+
+static gboolean
+file_list_all_directories (GList *file_list)
+{
+ GList *l;
+ for (l = file_list; l != NULL; l = l->next) {
+ if (!caja_file_is_directory (CAJA_FILE (l->data))) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void
+value_field_update_internal (GtkLabel *label,
+ GList *file_list)
+{
+ const char *attribute_name;
+ char *attribute_value;
+ char *inconsistent_string;
+ 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);
+ g_free (attribute_value);
+}
+
+static void
+value_field_update (FMPropertiesWindow *window, GtkLabel *label)
+{
+ gboolean use_original;
+
+ use_original = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (label), "show_original"));
+
+ value_field_update_internal (label,
+ (use_original ?
+ window->details->original_files :
+ window->details->target_files));
+}
+
+static GtkLabel *
+attach_label (GtkTable *table,
+ int row,
+ int column,
+ const char *initial_text,
+ gboolean right_aligned,
+ gboolean bold,
+ gboolean ellipsize_text,
+ gboolean selectable,
+ gboolean mnemonic)
+{
+ GtkWidget *label_field;
+
+ if (ellipsize_text) {
+ label_field = gtk_label_new (initial_text);
+ gtk_label_set_ellipsize (GTK_LABEL (label_field),
+ right_aligned ? PANGO_ELLIPSIZE_START :
+ PANGO_ELLIPSIZE_END);
+ } else if (mnemonic) {
+ label_field = gtk_label_new_with_mnemonic (initial_text);
+ } else {
+ label_field = gtk_label_new (initial_text);
+ }
+
+ if (selectable) {
+ gtk_label_set_selectable (GTK_LABEL (label_field), TRUE);
+ }
+
+ if (bold) {
+ eel_gtk_label_make_bold (GTK_LABEL (label_field));
+ }
+ gtk_misc_set_alignment (GTK_MISC (label_field), right_aligned ? 1 : 0, 0.5);
+ gtk_widget_show (label_field);
+ gtk_table_attach (table, label_field,
+ column, column + 1,
+ row, row + 1,
+ ellipsize_text
+ ? GTK_FILL | GTK_EXPAND
+ : GTK_FILL,
+ 0,
+ 0, 0);
+
+ return GTK_LABEL (label_field);
+}
+
+static GtkLabel *
+attach_value_label (GtkTable *table,
+ int row,
+ int column,
+ const char *initial_text)
+{
+ return attach_label (table, row, column, initial_text, FALSE, FALSE, FALSE, TRUE, FALSE);
+}
+
+static GtkLabel *
+attach_ellipsizing_value_label (GtkTable *table,
+ int row,
+ int column,
+ const char *initial_text)
+{
+ return attach_label (table, row, column, initial_text, FALSE, FALSE, TRUE, TRUE, FALSE);
+}
+
+static GtkWidget*
+attach_value_field_internal (FMPropertiesWindow *window,
+ GtkTable *table,
+ int row,
+ int column,
+ const char *file_attribute_name,
+ const char *inconsistent_string,
+ gboolean show_original,
+ gboolean ellipsize_text)
+{
+ GtkLabel *value_field;
+
+ if (ellipsize_text) {
+ value_field = attach_ellipsizing_value_label (table, row, column, "");
+ } else {
+ value_field = attach_value_label (table, row, column, "");
+ }
+
+ /* Stash a copy of the file attribute name in this field for the callback's sake. */
+ g_object_set_data_full (G_OBJECT (value_field), "file_attribute",
+ g_strdup (file_attribute_name), g_free);
+
+ g_object_set_data_full (G_OBJECT (value_field), "inconsistent_string",
+ g_strdup (inconsistent_string), g_free);
+
+ g_object_set_data (G_OBJECT (value_field), "show_original", GINT_TO_POINTER (show_original));
+
+ window->details->value_fields = g_list_prepend (window->details->value_fields,
+ value_field);
+ return GTK_WIDGET(value_field);
+}
+
+static GtkWidget*
+attach_value_field (FMPropertiesWindow *window,
+ GtkTable *table,
+ int row,
+ int column,
+ const char *file_attribute_name,
+ const char *inconsistent_string,
+ gboolean show_original)
+{
+ return attach_value_field_internal (window,
+ table, row, column,
+ file_attribute_name,
+ inconsistent_string,
+ show_original,
+ FALSE);
+}
+
+static GtkWidget*
+attach_ellipsizing_value_field (FMPropertiesWindow *window,
+ GtkTable *table,
+ int row,
+ int column,
+ const char *file_attribute_name,
+ const char *inconsistent_string,
+ gboolean show_original)
+{
+ return attach_value_field_internal (window,
+ table, row, column,
+ file_attribute_name,
+ inconsistent_string,
+ show_original,
+ TRUE);
+}
+
+static void
+group_change_callback (CajaFile *file,
+ GFile *res_loc,
+ GError *error,
+ FMPropertiesWindow *window)
+{
+ char *group;
+
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+ g_assert (window->details->group_change_file == file);
+
+ group = window->details->group_change_group;
+ g_assert (group != NULL);
+
+ /* Report the error if it's an error. */
+ eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, window);
+ fm_report_error_setting_group (file, error, GTK_WINDOW (window));
+
+ caja_file_unref (file);
+ g_free (group);
+
+ window->details->group_change_file = NULL;
+ window->details->group_change_group = NULL;
+ g_object_unref (G_OBJECT (window));
+}
+
+static void
+cancel_group_change_callback (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+ char *group;
+
+ file = window->details->group_change_file;
+ g_assert (CAJA_IS_FILE (file));
+
+ group = window->details->group_change_group;
+ g_assert (group != NULL);
+
+ caja_file_cancel (file, (CajaFileOperationCallback) group_change_callback, window);
+
+ g_free (group);
+ caja_file_unref (file);
+
+ window->details->group_change_file = NULL;
+ window->details->group_change_group = NULL;
+ g_object_unref (window);
+}
+
+static gboolean
+schedule_group_change_timeout (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+ char *group;
+
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+
+ file = window->details->group_change_file;
+ g_assert (CAJA_IS_FILE (file));
+
+ group = window->details->group_change_group;
+ g_assert (group != NULL);
+
+ eel_timed_wait_start
+ ((EelCancelCallback) cancel_group_change_callback,
+ window,
+ _("Cancel Group Change?"),
+ GTK_WINDOW (window));
+
+ caja_file_set_group
+ (file, group,
+ (CajaFileOperationCallback) group_change_callback, window);
+
+ window->details->group_change_timeout = 0;
+ return FALSE;
+}
+
+static void
+schedule_group_change (FMPropertiesWindow *window,
+ CajaFile *file,
+ const char *group)
+{
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+ g_assert (window->details->group_change_group == NULL);
+ g_assert (window->details->group_change_file == NULL);
+ g_assert (CAJA_IS_FILE (file));
+
+ window->details->group_change_file = caja_file_ref (file);
+ window->details->group_change_group = g_strdup (group);
+ g_object_ref (G_OBJECT (window));
+ window->details->group_change_timeout =
+ g_timeout_add (CHOWN_CHGRP_TIMEOUT,
+ (GSourceFunc) schedule_group_change_timeout,
+ window);
+}
+
+static void
+unschedule_or_cancel_group_change (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+ char *group;
+
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+
+ file = window->details->group_change_file;
+ group = window->details->group_change_group;
+
+ g_assert ((file == NULL && group == NULL) ||
+ (file != NULL && group != NULL));
+
+ if (file != NULL) {
+ g_assert (CAJA_IS_FILE (file));
+
+ if (window->details->group_change_timeout == 0) {
+ caja_file_cancel (file,
+ (CajaFileOperationCallback) group_change_callback, window);
+ eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, window);
+ }
+
+ caja_file_unref (file);
+ g_free (group);
+
+ window->details->group_change_file = NULL;
+ window->details->group_change_group = NULL;
+ g_object_unref (G_OBJECT (window));
+ }
+
+ if (window->details->group_change_timeout > 0) {
+ g_assert (file != NULL);
+ g_source_remove (window->details->group_change_timeout);
+ window->details->group_change_timeout = 0;
+ }
+}
+
+static void
+changed_group_callback (GtkComboBox *combo_box, CajaFile *file)
+{
+ FMPropertiesWindow *window;
+ char *group;
+ char *cur_group;
+
+ g_assert (GTK_IS_COMBO_BOX (combo_box));
+ g_assert (CAJA_IS_FILE (file));
+
+ group = gtk_combo_box_get_active_text (combo_box);
+ cur_group = caja_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 = FM_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW));
+
+ unschedule_or_cancel_group_change (window);
+ schedule_group_change (window, file, group);
+ }
+ g_free (group);
+ g_free (cur_group);
+}
+
+/* checks whether the given column at the first level
+ * of model has the specified entries in the given order. */
+static gboolean
+tree_model_entries_equal (GtkTreeModel *model,
+ unsigned int column,
+ GList *entries)
+{
+ GtkTreeIter iter;
+ gboolean empty_model;
+
+ g_assert (GTK_IS_TREE_MODEL (model));
+ g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING);
+
+ empty_model = !gtk_tree_model_get_iter_first (model, &iter);
+
+ if (!empty_model && entries != NULL) {
+ GList *l;
+
+ l = entries;
+
+ do {
+ char *val;
+
+ gtk_tree_model_get (model, &iter,
+ column, &val,
+ -1);
+ if ((val == NULL && l->data != NULL) ||
+ (val != NULL && l->data == NULL) ||
+ (val != NULL && strcmp (val, l->data))) {
+ g_free (val);
+ return FALSE;
+ }
+
+ g_free (val);
+ l = l->next;
+ } while (gtk_tree_model_iter_next (model, &iter));
+
+ return l == NULL;
+ } else {
+ return (empty_model && entries == NULL) ||
+ (!empty_model && entries != NULL);
+ }
+}
+
+static char *
+combo_box_get_active_entry (GtkComboBox *combo_box,
+ unsigned int column)
+{
+ 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, CajaFile *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 (CAJA_IS_FILE (file));
+
+ if (caja_file_is_gone (file)) {
+ return;
+ }
+
+ groups = caja_file_get_settable_group_names (file);
+
+ model = gtk_combo_box_get_model (combo_box);
+ store = GTK_LIST_STORE (model);
+ g_assert (GTK_IS_LIST_STORE (model));
+
+ if (!tree_model_entries_equal (model, 0, groups)) {
+ /* 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_append_text (combo_box, group_name);
+ }
+ }
+
+ current_group_name = caja_file_get_group_name (file);
+ current_group_index = tree_model_get_entry_index (model, 0, current_group_name);
+
+ /* If current group wasn't in list, we prepend it (with a separator).
+ * This can happen if the current group is an id with no matching
+ * group in the groups file.
+ */
+ if (current_group_index < 0 && current_group_name != NULL) {
+ if (groups != NULL) {
+ /* add separator */
+ gtk_combo_box_prepend_text (combo_box, "-");
+ }
+
+ gtk_combo_box_prepend_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);
+ eel_g_list_free_deep (groups);
+}
+
+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 (GtkTable *table,
+ int row,
+ gboolean two_columns)
+{
+ GtkWidget *combo_box;
+ GtkWidget *aligner;
+
+ if (!two_columns) {
+ combo_box = gtk_combo_box_new_text ();
+ } 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_table_attach (table, aligner,
+ VALUE_COLUMN, VALUE_COLUMN + 1,
+ row, row + 1,
+ GTK_FILL, 0,
+ 0, 0);
+
+ return GTK_COMBO_BOX (combo_box);
+}
+
+static GtkComboBox*
+attach_group_combo_box (GtkTable *table,
+ int row,
+ CajaFile *file)
+{
+ GtkComboBox *combo_box;
+
+ combo_box = attach_combo_box (table, row, FALSE);
+
+ synch_groups_combo_box (combo_box, file);
+
+ /* Connect to signal to update menu when file changes. */
+ g_signal_connect_object (file, "changed",
+ G_CALLBACK (synch_groups_combo_box),
+ combo_box, G_CONNECT_SWAPPED);
+ g_signal_connect_data (combo_box, "changed",
+ G_CALLBACK (changed_group_callback),
+ caja_file_ref (file),
+ (GClosureNotify)caja_file_unref, 0);
+
+ return combo_box;
+}
+
+static void
+owner_change_callback (CajaFile *file,
+ GFile *result_location,
+ GError *error,
+ FMPropertiesWindow *window)
+{
+ char *owner;
+
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+ g_assert (window->details->owner_change_file == file);
+
+ owner = window->details->owner_change_owner;
+ g_assert (owner != NULL);
+
+ /* Report the error if it's an error. */
+ eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, window);
+ fm_report_error_setting_owner (file, error, GTK_WINDOW (window));
+
+ caja_file_unref (file);
+ g_free (owner);
+
+ window->details->owner_change_file = NULL;
+ window->details->owner_change_owner = NULL;
+ g_object_unref (G_OBJECT (window));
+}
+
+static void
+cancel_owner_change_callback (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+ char *owner;
+
+ file = window->details->owner_change_file;
+ g_assert (CAJA_IS_FILE (file));
+
+ owner = window->details->owner_change_owner;
+ g_assert (owner != NULL);
+
+ caja_file_cancel (file, (CajaFileOperationCallback) owner_change_callback, window);
+
+ caja_file_unref (file);
+ g_free (owner);
+
+ window->details->owner_change_file = NULL;
+ window->details->owner_change_owner = NULL;
+ g_object_unref (window);
+}
+
+static gboolean
+schedule_owner_change_timeout (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+ char *owner;
+
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+
+ file = window->details->owner_change_file;
+ g_assert (CAJA_IS_FILE (file));
+
+ owner = window->details->owner_change_owner;
+ g_assert (owner != NULL);
+
+ eel_timed_wait_start
+ ((EelCancelCallback) cancel_owner_change_callback,
+ window,
+ _("Cancel Owner Change?"),
+ GTK_WINDOW (window));
+
+ caja_file_set_owner
+ (file, owner,
+ (CajaFileOperationCallback) owner_change_callback, window);
+
+ window->details->owner_change_timeout = 0;
+ return FALSE;
+}
+
+static void
+schedule_owner_change (FMPropertiesWindow *window,
+ CajaFile *file,
+ const char *owner)
+{
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+ g_assert (window->details->owner_change_owner == NULL);
+ g_assert (window->details->owner_change_file == NULL);
+ g_assert (CAJA_IS_FILE (file));
+
+ window->details->owner_change_file = caja_file_ref (file);
+ window->details->owner_change_owner = g_strdup (owner);
+ g_object_ref (G_OBJECT (window));
+ window->details->owner_change_timeout =
+ g_timeout_add (CHOWN_CHGRP_TIMEOUT,
+ (GSourceFunc) schedule_owner_change_timeout,
+ window);
+}
+
+static void
+unschedule_or_cancel_owner_change (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+ char *owner;
+
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+
+ file = window->details->owner_change_file;
+ owner = window->details->owner_change_owner;
+
+ g_assert ((file == NULL && owner == NULL) ||
+ (file != NULL && owner != NULL));
+
+ if (file != NULL) {
+ g_assert (CAJA_IS_FILE (file));
+
+ if (window->details->owner_change_timeout == 0) {
+ caja_file_cancel (file,
+ (CajaFileOperationCallback) owner_change_callback, window);
+ eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, window);
+ }
+
+ caja_file_unref (file);
+ g_free (owner);
+
+ window->details->owner_change_file = NULL;
+ window->details->owner_change_owner = NULL;
+ g_object_unref (G_OBJECT (window));
+ }
+
+ if (window->details->owner_change_timeout > 0) {
+ g_assert (file != NULL);
+ g_source_remove (window->details->owner_change_timeout);
+ window->details->owner_change_timeout = 0;
+ }
+}
+
+static void
+changed_owner_callback (GtkComboBox *combo_box, CajaFile* file)
+{
+ FMPropertiesWindow *window;
+ char *owner_text;
+ char **name_array;
+ char *new_owner;
+ char *cur_owner;
+
+ g_assert (GTK_IS_COMBO_BOX (combo_box));
+ g_assert (CAJA_IS_FILE (file));
+
+ owner_text = combo_box_get_active_entry (combo_box, 0);
+ if (! owner_text)
+ return;
+ name_array = g_strsplit (owner_text, " - ", 2);
+ new_owner = name_array[0];
+ g_free (owner_text);
+ cur_owner = caja_file_get_owner_name (file);
+
+ if (strcmp (new_owner, cur_owner) != 0) {
+ /* Try to change file owner. If this fails, complain to user. */
+ window = FM_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW));
+
+ unschedule_or_cancel_owner_change (window);
+ schedule_owner_change (window, file, new_owner);
+ }
+ g_strfreev (name_array);
+ g_free (cur_owner);
+}
+
+static void
+synch_user_menu (GtkComboBox *combo_box, CajaFile *file)
+{
+ GList *users;
+ GList *node;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ char *user_name;
+ char *owner_name;
+ int user_index;
+ int owner_index;
+ char **name_array;
+ char *combo_text;
+
+ g_assert (GTK_IS_COMBO_BOX (combo_box));
+ g_assert (CAJA_IS_FILE (file));
+
+ if (caja_file_is_gone (file)) {
+ return;
+ }
+
+ users = caja_get_user_names ();
+
+ model = gtk_combo_box_get_model (combo_box);
+ store = GTK_LIST_STORE (model);
+ g_assert (GTK_IS_LIST_STORE (model));
+
+ if (!tree_model_entries_equal (model, 1, users)) {
+ /* 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 = caja_file_get_string_attribute (file, "owner");
+ owner_index = tree_model_get_entry_index (model, 0, owner_name);
+
+ /* If owner wasn't in list, we prepend it (with a separator).
+ * This can happen if the owner is an id with no matching
+ * identifier in the passwords file.
+ */
+ if (owner_index < 0 && owner_name != NULL) {
+ if (users != NULL) {
+ /* add separator */
+ gtk_list_store_prepend (store, &iter);
+ gtk_list_store_set (store, &iter,
+ 0, "-",
+ 1, NULL,
+ -1);
+ }
+
+ name_array = g_strsplit (owner_name, " - ", 2);
+ if (name_array[1] != NULL) {
+ user_name = g_strdup_printf ("%s\n%s", name_array[0], name_array[1]);
+ } else {
+ user_name = g_strdup (name_array[0]);
+ }
+ owner_index = 0;
+
+ gtk_list_store_prepend (store, &iter);
+ gtk_list_store_set (store, &iter,
+ 0, owner_name,
+ 1, user_name,
+ -1);
+
+ g_free (user_name);
+ g_strfreev (name_array);
+ }
+
+ gtk_combo_box_set_active (combo_box, owner_index);
+
+ g_free (owner_name);
+ eel_g_list_free_deep (users);
+}
+
+static GtkComboBox*
+attach_owner_combo_box (GtkTable *table,
+ int row,
+ CajaFile *file)
+{
+ GtkComboBox *combo_box;
+
+ combo_box = attach_combo_box (table, row, TRUE);
+
+ synch_user_menu (combo_box, file);
+
+ /* Connect to signal to update menu when file changes. */
+ g_signal_connect_object (file, "changed",
+ G_CALLBACK (synch_user_menu),
+ combo_box, G_CONNECT_SWAPPED);
+ g_signal_connect_data (combo_box, "changed",
+ G_CALLBACK (changed_owner_callback),
+ caja_file_ref (file),
+ (GClosureNotify)caja_file_unref, 0);
+
+ return combo_box;
+}
+
+static guint
+append_row (GtkTable *table)
+{
+ guint new_row_count;
+ gint nrows, ncols;
+
+ g_object_get (table, "n-rows", &nrows, "n-columns", &ncols, NULL);
+
+ new_row_count = nrows + 1;
+
+ gtk_table_resize (table, new_row_count, ncols);
+ gtk_table_set_row_spacing (table, new_row_count - 1, ROW_PAD);
+
+ return new_row_count - 1;
+}
+
+static gboolean
+file_has_prefix (CajaFile *file,
+ GList *prefix_candidates)
+{
+ GList *p;
+ GFile *location, *candidate_location;
+
+ location = caja_file_get_location (file);
+
+ for (p = prefix_candidates; p != NULL; p = p->next) {
+ if (file == p->data) {
+ continue;
+ }
+
+ candidate_location = caja_file_get_location (CAJA_FILE (p->data));
+ if (g_file_has_prefix (location, candidate_location)) {
+ g_object_unref (location);
+ g_object_unref (candidate_location);
+ return TRUE;
+ }
+ g_object_unref (candidate_location);
+ }
+
+ g_object_unref (location);
+
+ return FALSE;
+}
+
+static void
+directory_contents_value_field_update (FMPropertiesWindow *window)
+{
+ CajaRequestStatus file_status, status;
+ char *text, *temp;
+ guint directory_count;
+ guint file_count;
+ guint total_count;
+ guint unreadable_directory_count;
+ goffset total_size;
+ gboolean used_two_lines;
+ CajaFile *file;
+ GList *l;
+ guint file_unreadable;
+ goffset file_size;
+
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+
+ status = CAJA_REQUEST_DONE;
+ file_status = CAJA_REQUEST_NOT_STARTED;
+ total_count = window->details->total_count;
+ total_size = window->details->total_size;
+ unreadable_directory_count = FALSE;
+
+ for (l = window->details->target_files; l; l = l->next) {
+ file = CAJA_FILE (l->data);
+
+ if (file_has_prefix (file, window->details->target_files)) {
+ /* don't count nested files twice */
+ continue;
+ }
+
+ if (caja_file_is_directory (file)) {
+ file_status = caja_file_get_deep_counts (file,
+ &directory_count,
+ &file_count,
+ &file_unreadable,
+ &file_size,
+ TRUE);
+ total_count += (file_count + directory_count);
+ total_size += file_size;
+
+ if (file_unreadable) {
+ unreadable_directory_count = TRUE;
+ }
+
+ if (file_status != CAJA_REQUEST_DONE) {
+ status = file_status;
+ }
+ } else {
+ ++total_count;
+ total_size += caja_file_get_size (file);
+ }
+ }
+
+ /* If we've already displayed the total once, don't do another visible
+ * count-up if the deep_count happens to get invalidated.
+ * But still display the new total, since it might have changed.
+ */
+ if (window->details->deep_count_finished &&
+ status != CAJA_REQUEST_DONE) {
+ return;
+ }
+
+ text = NULL;
+ used_two_lines = FALSE;
+
+ if (total_count == 0) {
+ switch (status) {
+ case CAJA_REQUEST_DONE:
+ if (unreadable_directory_count == 0) {
+ text = g_strdup (_("nothing"));
+ } else {
+ text = g_strdup (_("unreadable"));
+ }
+
+ break;
+ default:
+ text = g_strdup ("...");
+ }
+ } else {
+ char *size_str;
+
+ #if GLIB_CHECK_VERSION(2, 30, 0)
+ size_str = g_format_size(total_size);
+ #else
+ size_str = g_format_size_for_display(total_size);
+ #endif
+
+ text = g_strdup_printf (ngettext("%'d item, with size %s",
+ "%'d items, totalling %s",
+ total_count),
+ total_count, size_str);
+ g_free (size_str);
+
+ if (unreadable_directory_count != 0) {
+ temp = text;
+ text = g_strconcat (temp, "\n",
+ _("(some contents unreadable)"),
+ NULL);
+ g_free (temp);
+ used_two_lines = TRUE;
+ }
+ }
+
+ gtk_label_set_text (window->details->directory_contents_value_field,
+ text);
+ g_free (text);
+
+ /* Also set the title field here, with a trailing carriage return &
+ * space if the value field has two lines. This is a hack to get the
+ * "Contents:" title to line up with the first line of the
+ * 2-line value. Maybe there's a better way to do this, but I
+ * couldn't think of one.
+ */
+ text = g_strdup (_("Contents:"));
+ if (used_two_lines) {
+ temp = text;
+ text = g_strconcat (temp, "\n ", NULL);
+ g_free (temp);
+ }
+ gtk_label_set_text (window->details->directory_contents_title_field,
+ text);
+ g_free (text);
+
+ if (status == CAJA_REQUEST_DONE) {
+ window->details->deep_count_finished = TRUE;
+ }
+}
+
+static gboolean
+update_directory_contents_callback (gpointer data)
+{
+ FMPropertiesWindow *window;
+
+ window = FM_PROPERTIES_WINDOW (data);
+
+ window->details->update_directory_contents_timeout_id = 0;
+ directory_contents_value_field_update (window);
+
+ return FALSE;
+}
+
+static void
+schedule_directory_contents_update (FMPropertiesWindow *window)
+{
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+
+ if (window->details->update_directory_contents_timeout_id == 0) {
+ window->details->update_directory_contents_timeout_id
+ = g_timeout_add (DIRECTORY_CONTENTS_UPDATE_INTERVAL,
+ update_directory_contents_callback,
+ window);
+ }
+}
+
+static GtkLabel *
+attach_directory_contents_value_field (FMPropertiesWindow *window,
+ GtkTable *table,
+ int row)
+{
+ GtkLabel *value_field;
+ GList *l;
+ CajaFile *file;
+
+ value_field = attach_value_label (table, row, VALUE_COLUMN, "");
+
+ g_assert (window->details->directory_contents_value_field == NULL);
+ window->details->directory_contents_value_field = value_field;
+
+ gtk_label_set_line_wrap (value_field, TRUE);
+
+ /* Fill in the initial value. */
+ directory_contents_value_field_update (window);
+
+ for (l = window->details->target_files; l; l = l->next) {
+ file = CAJA_FILE (l->data);
+ caja_file_recompute_deep_counts (file);
+
+ g_signal_connect_object (file,
+ "updated_deep_count_in_progress",
+ G_CALLBACK (schedule_directory_contents_update),
+ window, G_CONNECT_SWAPPED);
+ }
+
+ return value_field;
+}
+
+static GtkLabel *
+attach_title_field (GtkTable *table,
+ int row,
+ const char *title)
+{
+ return attach_label (table, row, TITLE_COLUMN, title, FALSE, FALSE, FALSE, FALSE, TRUE);
+}
+
+static guint
+append_title_field (GtkTable *table, const char *title, GtkLabel **label)
+{
+ guint last_row;
+ GtkLabel *title_label;
+
+ last_row = append_row (table);
+ title_label = attach_title_field (table, last_row, title);
+
+ if (label) {
+ *label = title_label;
+ }
+
+ return last_row;
+}
+
+#define INCONSISTENT_STATE_STRING \
+ "\xE2\x80\x92"
+
+static guint
+append_title_value_pair (FMPropertiesWindow *window,
+ GtkTable *table,
+ const char *title,
+ const char *file_attribute_name,
+ const char *inconsistent_state,
+ gboolean show_original)
+{
+ guint last_row;
+ GtkLabel *title_label;
+ GtkWidget *value;
+
+ last_row = append_title_field (table, title, &title_label);
+ value = attach_value_field (window, table, last_row, VALUE_COLUMN,
+ file_attribute_name,
+ inconsistent_state,
+ show_original);
+ gtk_label_set_mnemonic_widget (title_label, value);
+ return last_row;
+}
+
+static guint
+append_title_and_ellipsizing_value (FMPropertiesWindow *window,
+ GtkTable *table,
+ const char *title,
+ const char *file_attribute_name,
+ const char *inconsistent_state,
+ gboolean show_original)
+{
+ GtkLabel *title_label;
+ GtkWidget *value;
+ guint last_row;
+
+ last_row = append_title_field (table, title, &title_label);
+ value = attach_ellipsizing_value_field (window, table, last_row, VALUE_COLUMN,
+ file_attribute_name,
+ inconsistent_state,
+ show_original);
+ gtk_label_set_mnemonic_widget (title_label, value);
+
+ return last_row;
+}
+
+static guint
+append_directory_contents_fields (FMPropertiesWindow *window,
+ GtkTable *table)
+{
+ GtkLabel *title_field, *value_field;
+ guint last_row;
+
+ last_row = append_row (table);
+
+ title_field = attach_title_field (table, last_row, "");
+ window->details->directory_contents_title_field = title_field;
+ gtk_label_set_line_wrap (title_field, TRUE);
+
+ value_field = attach_directory_contents_value_field
+ (window, table, last_row);
+
+ gtk_label_set_mnemonic_widget(title_field, GTK_WIDGET(value_field));
+ return last_row;
+}
+
+static GtkWidget *
+create_page_with_hbox (GtkNotebook *notebook,
+ const char *title)
+{
+ GtkWidget *hbox;
+
+ g_assert (GTK_IS_NOTEBOOK (notebook));
+ g_assert (title != NULL);
+
+ hbox = gtk_hbox_new (FALSE, 0);
+ gtk_widget_show (hbox);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 12);
+ gtk_box_set_spacing (GTK_BOX (hbox), 12);
+ gtk_notebook_append_page (notebook, hbox, gtk_label_new (title));
+
+ return hbox;
+}
+
+static GtkWidget *
+create_page_with_vbox (GtkNotebook *notebook,
+ const char *title)
+{
+ GtkWidget *vbox;
+
+ g_assert (GTK_IS_NOTEBOOK (notebook));
+ g_assert (title != NULL);
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (vbox);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
+ gtk_notebook_append_page (notebook, vbox, gtk_label_new (title));
+
+ return vbox;
+}
+
+static GtkWidget *
+append_blank_row (GtkTable *table)
+{
+ GtkWidget *separator;
+
+ append_title_field (table, "", (GtkLabel **) &separator);
+
+ return separator;
+}
+
+static void
+apply_standard_table_padding (GtkTable *table)
+{
+ gtk_table_set_row_spacings (table, ROW_PAD);
+ gtk_table_set_col_spacings (table, 12);
+}
+
+static GtkWidget *
+create_attribute_value_table (GtkVBox *vbox, int row_count)
+{
+ GtkWidget *table;
+
+ table = gtk_table_new (row_count, COLUMN_COUNT, FALSE);
+ apply_standard_table_padding (GTK_TABLE (table));
+ gtk_widget_show (table);
+ gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
+
+ return table;
+}
+
+static gboolean
+is_merged_trash_directory (CajaFile *file)
+{
+ char *file_uri;
+ gboolean result;
+
+ file_uri = caja_file_get_uri (file);
+ result = strcmp (file_uri, "trash:///") == 0;
+ g_free (file_uri);
+
+ return result;
+}
+
+static gboolean
+is_computer_directory (CajaFile *file)
+{
+ char *file_uri;
+ gboolean result;
+
+ file_uri = caja_file_get_uri (file);
+ result = strcmp (file_uri, "computer:///") == 0;
+ g_free (file_uri);
+
+ return result;
+}
+
+static gboolean
+is_network_directory (CajaFile *file)
+{
+ char *file_uri;
+ gboolean result;
+
+ file_uri = caja_file_get_uri (file);
+ result = strcmp (file_uri, "network:///") == 0;
+ g_free (file_uri);
+
+ return result;
+}
+
+static gboolean
+is_burn_directory (CajaFile *file)
+{
+ char *file_uri;
+ gboolean result;
+
+ file_uri = caja_file_get_uri (file);
+ result = strcmp (file_uri, "burn:///") == 0;
+ g_free (file_uri);
+
+ return result;
+}
+
+static gboolean
+should_show_custom_icon_buttons (FMPropertiesWindow *window)
+{
+ if (is_multi_file_window (window)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_file_type (FMPropertiesWindow *window)
+{
+ if (!is_multi_file_window (window)
+ && (is_merged_trash_directory (get_target_file (window)) ||
+ is_computer_directory (get_target_file (window)) ||
+ is_network_directory (get_target_file (window)) ||
+ is_burn_directory (get_target_file (window)))) {
+ return FALSE;
+ }
+
+
+ return TRUE;
+}
+
+static gboolean
+should_show_location_info (FMPropertiesWindow *window)
+{
+ if (!is_multi_file_window (window)
+ && (is_merged_trash_directory (get_target_file (window)) ||
+ is_computer_directory (get_target_file (window)) ||
+ is_network_directory (get_target_file (window)) ||
+ is_burn_directory (get_target_file (window)))) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_accessed_date (FMPropertiesWindow *window)
+{
+ /* Accessed date for directory seems useless. If we some
+ * day decide that it is useful, we should separately
+ * consider whether it's useful for "trash:".
+ */
+ if (file_list_all_directories (window->details->target_files)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_link_target (FMPropertiesWindow *window)
+{
+ if (!is_multi_file_window (window)
+ && caja_file_is_symbolic_link (get_target_file (window))) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+should_show_free_space (FMPropertiesWindow *window)
+{
+
+ if (!is_multi_file_window (window)
+ && (is_merged_trash_directory (get_target_file (window)) ||
+ is_computer_directory (get_target_file (window)) ||
+ is_network_directory (get_target_file (window)) ||
+ is_burn_directory (get_target_file (window)))) {
+ return FALSE;
+ }
+
+ if (file_list_all_directories (window->details->target_files)) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+should_show_volume_usage (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+ gboolean success = FALSE;
+
+ if (is_multi_file_window (window)) {
+ return FALSE;
+ }
+
+ file = get_original_file (window);
+
+ if (file == NULL) {
+ return FALSE;
+ }
+
+ if (caja_file_can_unmount (file)) {
+ return TRUE;
+ }
+
+#ifdef TODO_GIO
+ /* Look at is_mountpoint for activation uri */
+#endif
+ return success;
+}
+
+static void
+paint_used_legend (GtkWidget *widget, GdkEventExpose *eev, gpointer data)
+{
+ FMPropertiesWindow *window;
+ cairo_t *cr;
+ gint width, height;
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ width = allocation.width;
+ height = allocation.height;
+
+ window = FM_PROPERTIES_WINDOW (data);
+
+ cr = gdk_cairo_create (gtk_widget_get_window (widget));
+
+ cairo_rectangle (cr,
+ 2,
+ 2,
+ width - 4,
+ height - 4);
+
+ cairo_set_source_rgb (cr, (double) window->details->used_color.red / 65535, (double) window->details->used_color.green / 65535, (double) window->details->used_color.blue / 65535);
+ cairo_fill_preserve (cr);
+
+ cairo_set_source_rgb (cr, (double) window->details->used_stroke_color.red / 65535, (double) window->details->used_stroke_color.green / 65535, (double) window->details->used_stroke_color.blue / 65535);
+ cairo_stroke (cr);
+
+ cairo_destroy (cr);
+}
+
+static void
+paint_free_legend (GtkWidget *widget, GdkEventExpose *eev, gpointer data)
+{
+ FMPropertiesWindow *window;
+ cairo_t *cr;
+ gint width, height;
+ GtkAllocation allocation;
+
+ window = FM_PROPERTIES_WINDOW (data);
+ gtk_widget_get_allocation (widget, &allocation);
+
+ width = allocation.width;
+ height = allocation.height;
+ cr = gdk_cairo_create (gtk_widget_get_window (widget));
+
+ cairo_rectangle (cr,
+ 2,
+ 2,
+ width - 4,
+ height - 4);
+
+ cairo_set_source_rgb (cr, (double) window->details->free_color.red / 65535, (double) window->details->free_color.green / 65535, (double) window->details->free_color.blue / 65535);
+ cairo_fill_preserve(cr);
+
+ cairo_set_source_rgb (cr, (double) window->details->free_stroke_color.red / 65535, (double) window->details->free_stroke_color.green / 65535, (double) window->details->free_stroke_color.blue / 65535);
+ cairo_stroke (cr);
+
+ cairo_destroy (cr);
+}
+
+static void
+paint_pie_chart (GtkWidget *widget, GdkEventExpose *eev, gpointer data)
+{
+
+ FMPropertiesWindow *window;
+ cairo_t *cr;
+ gint width, height;
+ double free, used;
+ double angle1, angle2, split, xc, yc, radius;
+ GtkAllocation allocation;
+
+ window = FM_PROPERTIES_WINDOW (data);
+ gtk_widget_get_allocation (widget, &allocation);
+
+ width = allocation.width;
+ height = allocation.height;
+
+
+ free = (double)window->details->volume_free / (double)window->details->volume_capacity;
+ used = 1.0 - free;
+
+ angle1 = free * 2 * G_PI;
+ angle2 = used * 2 * G_PI;
+ split = (2 * G_PI - angle1) * .5;
+ xc = width / 2;
+ yc = height / 2;
+
+ cr = gdk_cairo_create (gtk_widget_get_window (widget));
+
+ 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);
+ }
+
+ cairo_set_source_rgb (cr, (double) window->details->used_color.red / 65535, (double) window->details->used_color.green / 65535, (double) window->details->used_color.blue / 65535);
+ cairo_fill_preserve (cr);
+
+ cairo_set_source_rgb (cr, (double) window->details->used_stroke_color.red / 65535, (double) window->details->used_stroke_color.green / 65535, (double) window->details->used_stroke_color.blue / 65535);
+ 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);
+ }
+
+ cairo_set_source_rgb (cr, (double) window->details->free_color.red / 65535, (double) window->details->free_color.green / 65535,(double) window->details->free_color.blue / 65535);
+ cairo_fill_preserve(cr);
+
+ cairo_set_source_rgb (cr, (double) window->details->free_stroke_color.red / 65535, (double) window->details->free_stroke_color.green / 65535, (double) window->details->free_stroke_color.blue / 65535);
+ cairo_stroke (cr);
+ }
+
+ cairo_destroy (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 (GdkColor *a,
+ GdkColor *b,
+ gdouble k)
+{
+ gdouble red;
+ gdouble green;
+ gdouble blue;
+
+ red = (gdouble) a->red / 65535.0;
+ green = (gdouble) a->green / 65535.0;
+ blue = (gdouble) a->blue / 65535.0;
+
+ 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 * 65535.0;
+ b->green = green * 65535.0;
+ b->blue = blue * 65535.0;
+}
+
+
+static GtkWidget*
+create_pie_widget (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+ GtkTable *table;
+ GtkStyle *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;
+
+ #if GLIB_CHECK_VERSION(2, 30, 0)
+ capacity = g_format_size(window->details->volume_capacity);
+ free = g_format_size(window->details->volume_free);
+ used = g_format_size(window->details->volume_capacity - window->details->volume_free);
+ #else
+ capacity = g_format_size_for_display(window->details->volume_capacity);
+ free = g_format_size_for_display(window->details->volume_free);
+ used = g_format_size_for_display(window->details->volume_capacity - window->details->volume_free);
+ #endif
+
+ file = get_original_file (window);
+
+ uri = caja_file_get_activation_uri (file);
+
+ table = GTK_TABLE (gtk_table_new (4, 3, FALSE));
+
+ style = gtk_rc_get_style (GTK_WIDGET(table));
+
+ if (!gtk_style_lookup_color (style, "chart_color_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;
+ }
+
+ if (!gtk_style_lookup_color (style, "chart_color_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;
+ }
+
+ _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_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_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) {
+ gtk_label_set_text (GTK_LABEL (fstype_label), g_strconcat (_("Filesystem type:"), " ", fs_type, NULL));
+ }
+
+ g_object_unref (info);
+ }
+ g_object_unref (location);
+
+ g_free (uri);
+ g_free (capacity);
+ g_free (used);
+ g_free (free);
+
+ gtk_table_attach (table, pie_canvas , 0, 1, 0, 4, GTK_FILL, GTK_SHRINK, 5, 5);
+
+ gtk_table_attach (table, used_canvas, 1, 2, 0, 1, 0, 0, 5, 5);
+ gtk_table_attach (table, used_label , 2, 3, 0, 1, GTK_FILL, 0, 5, 5);
+
+ gtk_table_attach (table, free_canvas, 1, 2, 1, 2, 0, 0, 5, 5);
+ gtk_table_attach (table, free_label , 2, 3, 1, 2, GTK_FILL, 0, 5, 5);
+
+ gtk_table_attach (table, capacity_label , 1, 3, 2, 3, GTK_FILL, 0, 5, 5);
+ gtk_table_attach (table, fstype_label , 1, 3, 3, 4, GTK_FILL, 0, 5, 5);
+
+ g_signal_connect (G_OBJECT (pie_canvas), "expose-event", G_CALLBACK (paint_pie_chart), window);
+ g_signal_connect (G_OBJECT (used_canvas), "expose-event", G_CALLBACK (paint_used_legend), window);
+ g_signal_connect (G_OBJECT (free_canvas), "expose-event", G_CALLBACK (paint_free_legend), window);
+
+ return GTK_WIDGET (table);
+}
+
+static GtkWidget*
+create_volume_usage_widget (FMPropertiesWindow *window)
+{
+ GtkWidget *piewidget;
+ gchar *uri;
+ CajaFile *file;
+ GFile *location;
+ GFileInfo *info;
+
+ file = get_original_file (window);
+
+ uri = caja_file_get_activation_uri (file);
+
+ location = g_file_new_for_uri (uri);
+ info = g_file_query_filesystem_info (location, "filesystem::*", NULL, NULL);
+
+ if (info) {
+ window->details->volume_capacity = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
+ window->details->volume_free = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
+
+ g_object_unref (info);
+ } else {
+ window->details->volume_capacity = 0;
+ window->details->volume_free = 0;
+ }
+
+ g_object_unref (location);
+
+ piewidget = create_pie_widget (window);
+
+ gtk_widget_show_all (piewidget);
+
+ return piewidget;
+}
+
+static void
+create_basic_page (FMPropertiesWindow *window)
+{
+ GtkTable *table;
+ GtkWidget *icon_aligner;
+ GtkWidget *icon_pixmap_widget;
+ GtkWidget *volume_usage;
+ GtkWidget *hbox, *vbox;
+
+ guint last_row, row;
+
+ hbox = create_page_with_hbox (window->details->notebook, _("Basic"));
+
+ /* Icon pixmap */
+
+ icon_pixmap_widget = create_image_widget (
+ window, should_show_custom_icon_buttons (window));
+ gtk_widget_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;
+
+ /* Table */
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (vbox);
+ gtk_container_add (GTK_CONTAINER (hbox), vbox);
+
+ table = GTK_TABLE (create_attribute_value_table (GTK_VBOX (vbox), 0));
+ window->details->basic_table = table;
+
+ /* Name label. The text will be determined in update_name_field */
+ row = append_title_field (table, NULL, &window->details->name_label);
+ window->details->name_row = row;
+
+ /* Name field */
+ window->details->name_field = NULL;
+ update_name_field (window);
+
+ /* Start with name field selected, if it's an entry. */
+ if (CAJA_IS_ENTRY (window->details->name_field)) {
+ caja_entry_select_all (CAJA_ENTRY (window->details->name_field));
+ gtk_widget_grab_focus (GTK_WIDGET (window->details->name_field));
+ }
+
+ if (fm_ditem_page_should_show (window->details->target_files)) {
+ GtkSizeGroup *label_size_group;
+ GtkWidget *box;
+
+ row = append_row (table);
+
+ label_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+ gtk_size_group_add_widget (label_size_group,
+ GTK_WIDGET (window->details->name_label));
+ box = fm_ditem_page_make_box (label_size_group,
+ window->details->target_files);
+
+ gtk_table_attach (window->details->basic_table, box,
+ TITLE_COLUMN, VALUE_COLUMN + 1,
+ row, row + 1,
+ GTK_FILL, 0,
+ 0, 0);
+ }
+
+ if (should_show_file_type (window)) {
+ append_title_value_pair (window,
+ table, _("Type:"),
+ "type",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ }
+
+ if (should_show_link_target (window)) {
+ append_title_and_ellipsizing_value (window, table,
+ _("Link target:"),
+ "link_target",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ }
+
+ if (is_multi_file_window (window) ||
+ caja_file_is_directory (get_target_file (window))) {
+ append_directory_contents_fields (window, table);
+ } else {
+ append_title_value_pair (window, table, _("Size:"),
+ "size_detail",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ }
+
+ append_blank_row (table);
+
+ if (should_show_location_info (window)) {
+ append_title_and_ellipsizing_value (window, table, _("Location:"),
+ "where",
+ INCONSISTENT_STATE_STRING,
+ TRUE);
+
+ append_title_and_ellipsizing_value (window, table,
+ _("Volume:"),
+ "volume",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ }
+
+ if (should_show_accessed_date (window)) {
+ append_blank_row (table);
+
+ append_title_value_pair (window, table, _("Accessed:"),
+ "date_accessed",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ append_title_value_pair (window, table, _("Modified:"),
+ "date_modified",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ }
+
+ if (should_show_free_space (window)) {
+ append_blank_row (table);
+
+ append_title_value_pair (window, table, _("Free space:"),
+ "free_space",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ }
+
+ if (should_show_volume_usage (window)) {
+ last_row = append_row (table);
+ volume_usage = create_volume_usage_widget (window);
+ gtk_table_attach_defaults (GTK_TABLE(table), volume_usage, 0, 2, last_row, last_row+1);
+ }
+}
+
+static GHashTable *
+get_initial_emblems (GList *files)
+{
+ GHashTable *ret;
+ GList *l;
+
+ ret = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ NULL,
+ (GDestroyNotify)eel_g_list_free_deep);
+
+ for (l = files; l != NULL; l = l->next) {
+ CajaFile *file;
+ GList *keywords;
+
+ file = CAJA_FILE (l->data);
+
+ keywords = caja_file_get_keywords (file);
+ g_hash_table_insert (ret, file, keywords);
+ }
+
+ return ret;
+}
+
+static gboolean
+files_has_directory (FMPropertiesWindow *window)
+{
+ GList *l;
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ file = CAJA_FILE (l->data);
+ if (caja_file_is_directory (file)) {
+ return TRUE;
+ }
+
+ }
+
+ return FALSE;
+}
+
+static gboolean
+files_has_changable_permissions_directory (FMPropertiesWindow *window)
+{
+ GList *l;
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ file = CAJA_FILE (l->data);
+ if (caja_file_is_directory (file) &&
+ caja_file_can_get_permissions (file) &&
+ caja_file_can_set_permissions (file)) {
+ return TRUE;
+ }
+
+ }
+
+ return FALSE;
+}
+
+
+static gboolean
+files_has_file (FMPropertiesWindow *window)
+{
+ GList *l;
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ file = CAJA_FILE (l->data);
+ if (!caja_file_is_directory (file)) {
+ return TRUE;
+ }
+
+ }
+
+ return FALSE;
+}
+
+
+static void
+create_emblems_page (FMPropertiesWindow *window)
+{
+ GtkWidget *emblems_table, *button, *scroller;
+ char *emblem_name;
+ GdkPixbuf *pixbuf;
+ char *label;
+ GList *icons, *l;
+ CajaIconInfo *info;
+
+ /* The emblems wrapped table */
+ scroller = eel_scrolled_wrap_table_new (TRUE, GTK_SHADOW_NONE, &emblems_table);
+
+ gtk_container_set_border_width (GTK_CONTAINER (emblems_table), 12);
+
+ gtk_widget_show (scroller);
+
+ gtk_notebook_append_page (window->details->notebook,
+ scroller, gtk_label_new (_("Emblems")));
+
+ icons = caja_emblem_list_available ();
+
+ window->details->initial_emblems = get_initial_emblems (window->details->original_files);
+
+ l = icons;
+ while (l != NULL) {
+ emblem_name = l->data;
+ l = l->next;
+
+ if (!caja_emblem_should_show_in_list (emblem_name)) {
+ continue;
+ }
+
+ info = caja_icon_info_lookup_from_name (emblem_name, CAJA_ICON_SIZE_SMALL);
+ pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, CAJA_ICON_SIZE_SMALL);
+
+ if (pixbuf == NULL) {
+ continue;
+ }
+
+ label = g_strdup (caja_icon_info_get_display_name (info));
+ g_object_unref (info);
+
+ if (label == NULL) {
+ label = caja_emblem_get_keyword_from_icon_name (emblem_name);
+ }
+
+ button = eel_labeled_image_check_button_new (label, pixbuf);
+ eel_labeled_image_set_fixed_image_height (EEL_LABELED_IMAGE (gtk_bin_get_child (GTK_BIN (button))), STANDARD_EMBLEM_HEIGHT);
+ eel_labeled_image_set_spacing (EEL_LABELED_IMAGE (gtk_bin_get_child (GTK_BIN (button))), EMBLEM_LABEL_SPACING);
+
+ g_free (label);
+ g_object_unref (pixbuf);
+
+ /* Attach parameters and signal handler. */
+ g_object_set_data_full (G_OBJECT (button), "caja_emblem_name",
+ caja_emblem_get_keyword_from_icon_name (emblem_name), g_free);
+
+ window->details->emblem_buttons =
+ g_list_append (window->details->emblem_buttons,
+ button);
+
+ g_signal_connect_object (button, "toggled",
+ G_CALLBACK (emblem_button_toggled),
+ G_OBJECT (window),
+ 0);
+
+ gtk_container_add (GTK_CONTAINER (emblems_table), button);
+ }
+ eel_g_list_free_deep (icons);
+ gtk_widget_show_all (emblems_table);
+}
+
+static void
+start_long_operation (FMPropertiesWindow *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);
+ gdk_cursor_unref (cursor);
+ }
+ window->details->long_operation_underway ++;
+}
+
+static void
+end_long_operation (FMPropertiesWindow *window)
+{
+ if (gtk_widget_get_window (GTK_WIDGET (window)) != NULL &&
+ window->details->long_operation_underway == 1) {
+ /* finished !! */
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), NULL);
+ }
+ window->details->long_operation_underway--;
+}
+
+static void
+permission_change_callback (CajaFile *file,
+ GFile *res_loc,
+ GError *error,
+ gpointer callback_data)
+{
+ FMPropertiesWindow *window;
+ g_assert (callback_data != NULL);
+
+ window = FM_PROPERTIES_WINDOW (callback_data);
+ end_long_operation (window);
+
+ /* Report the error if it's an error. */
+ fm_report_error_setting_permissions (file, error, NULL);
+
+ g_object_unref (window);
+}
+
+static void
+update_permissions (FMPropertiesWindow *window,
+ guint32 vfs_new_perm,
+ guint32 vfs_mask,
+ gboolean is_folder,
+ gboolean apply_to_both_folder_and_dir,
+ gboolean use_original)
+{
+ GList *l;
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ guint32 permissions;
+
+ file = CAJA_FILE (l->data);
+
+ if (!caja_file_can_get_permissions (file)) {
+ continue;
+ }
+
+ if (!apply_to_both_folder_and_dir &&
+ ((caja_file_is_directory (file) && !is_folder) ||
+ (!caja_file_is_directory (file) && is_folder))) {
+ continue;
+ }
+
+ permissions = caja_file_get_permissions (file);
+ if (use_original) {
+ gpointer ptr;
+ if (g_hash_table_lookup_extended (window->details->initial_permissions,
+ file, NULL, &ptr)) {
+ permissions = (permissions & ~vfs_mask) | (GPOINTER_TO_INT (ptr) & vfs_mask);
+ }
+ } else {
+ permissions = (permissions & ~vfs_mask) | vfs_new_perm;
+ }
+
+ start_long_operation (window);
+ g_object_ref (window);
+ caja_file_set_permissions
+ (file, permissions,
+ permission_change_callback,
+ window);
+ }
+}
+
+static gboolean
+initial_permission_state_consistent (FMPropertiesWindow *window,
+ guint32 mask,
+ gboolean is_folder,
+ gboolean both_folder_and_dir)
+{
+ GList *l;
+ gboolean first;
+ guint32 first_permissions;
+
+ first = TRUE;
+ first_permissions = 0;
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ guint32 permissions;
+
+ file = l->data;
+
+ if (!both_folder_and_dir &&
+ ((caja_file_is_directory (file) && !is_folder) ||
+ (!caja_file_is_directory (file) && is_folder))) {
+ continue;
+ }
+
+ permissions = GPOINTER_TO_INT (g_hash_table_lookup (window->details->initial_permissions,
+ file));
+
+ if (first) {
+ if ((permissions & mask) != mask &&
+ (permissions & mask) != 0) {
+ /* Not fully on or off -> inconsistent */
+ return FALSE;
+ }
+
+ first_permissions = permissions;
+ first = FALSE;
+
+ } else if ((permissions & mask) != first_permissions) {
+ /* Not same permissions as first -> inconsistent */
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void
+permission_button_toggled (GtkToggleButton *button,
+ FMPropertiesWindow *window)
+{
+ gboolean is_folder, is_special;
+ guint32 permission_mask;
+ gboolean inconsistent;
+ gboolean on;
+
+ permission_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "permission"));
+ is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "is-folder"));
+ is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "is-special"));
+
+ if (gtk_toggle_button_get_active (button)
+ && !gtk_toggle_button_get_inconsistent (button)) {
+ /* Go to the initial state unless the initial state was
+ consistent, or we support recursive apply */
+ inconsistent = TRUE;
+ on = TRUE;
+
+ if (!window->details->has_recursive_apply &&
+ initial_permission_state_consistent (window, permission_mask, is_folder, is_special)) {
+ inconsistent = FALSE;
+ on = TRUE;
+ }
+ } else if (gtk_toggle_button_get_inconsistent (button)
+ && !gtk_toggle_button_get_active (button)) {
+ inconsistent = FALSE;
+ on = TRUE;
+ } else {
+ inconsistent = FALSE;
+ on = FALSE;
+ }
+
+ g_signal_handlers_block_by_func (G_OBJECT (button),
+ G_CALLBACK (permission_button_toggled),
+ window);
+
+ gtk_toggle_button_set_active (button, on);
+ gtk_toggle_button_set_inconsistent (button, inconsistent);
+
+ g_signal_handlers_unblock_by_func (G_OBJECT (button),
+ G_CALLBACK (permission_button_toggled),
+ window);
+
+ update_permissions (window,
+ on?permission_mask:0,
+ permission_mask,
+ is_folder,
+ is_special,
+ inconsistent);
+}
+
+static void
+permission_button_update (FMPropertiesWindow *window,
+ GtkToggleButton *button)
+{
+ GList *l;
+ gboolean all_set;
+ gboolean all_unset;
+ gboolean all_cannot_set;
+ gboolean is_folder, is_special;
+ gboolean no_match;
+ gboolean sensitive;
+ guint32 button_permission;
+
+ if (gtk_toggle_button_get_inconsistent (button) &&
+ window->details->has_recursive_apply) {
+ /* Never change from an inconsistent state if we have dirs, even
+ * if the current state is now consistent, because its a useful
+ * state for recursive apply.
+ */
+ return;
+ }
+
+ button_permission = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "permission"));
+ is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "is-folder"));
+ is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "is-special"));
+
+ all_set = TRUE;
+ all_unset = TRUE;
+ all_cannot_set = TRUE;
+ no_match = TRUE;
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ guint32 file_permissions;
+
+ file = CAJA_FILE (l->data);
+
+ if (!caja_file_can_get_permissions (file)) {
+ continue;
+ }
+
+ if (!is_special &&
+ ((caja_file_is_directory (file) && !is_folder) ||
+ (!caja_file_is_directory (file) && is_folder))) {
+ continue;
+ }
+
+ no_match = FALSE;
+
+ file_permissions = caja_file_get_permissions (file);
+
+ if ((file_permissions & button_permission) == button_permission) {
+ all_unset = FALSE;
+ } else if ((file_permissions & button_permission) == 0) {
+ all_set = FALSE;
+ } else {
+ all_unset = FALSE;
+ all_set = FALSE;
+ }
+
+ if (caja_file_can_set_permissions (file)) {
+ all_cannot_set = FALSE;
+ }
+ }
+
+ sensitive = !all_cannot_set;
+ if (!is_folder) {
+ /* Don't insitive files when we have recursive apply */
+ sensitive |= window->details->has_recursive_apply;
+ }
+
+
+ g_signal_handlers_block_by_func (G_OBJECT (button),
+ G_CALLBACK (permission_button_toggled),
+ window);
+
+ gtk_toggle_button_set_active (button, !all_unset);
+ /* if actually inconsistent, or default value for file buttons
+ if no files are selected. (useful for recursive apply) */
+ gtk_toggle_button_set_inconsistent (button,
+ (!all_unset && !all_set) ||
+ (!is_folder && no_match));
+ gtk_widget_set_sensitive (GTK_WIDGET (button), sensitive);
+
+ g_signal_handlers_unblock_by_func (G_OBJECT (button),
+ G_CALLBACK (permission_button_toggled),
+ window);
+}
+
+static void
+set_up_permissions_checkbox (FMPropertiesWindow *window,
+ GtkWidget *check_button,
+ guint32 permission,
+ gboolean is_folder)
+{
+ /* Load up the check_button with data we'll need when updating its state. */
+ g_object_set_data (G_OBJECT (check_button), "permission",
+ GINT_TO_POINTER (permission));
+ g_object_set_data (G_OBJECT (check_button), "properties_window",
+ window);
+ g_object_set_data (G_OBJECT (check_button), "is-folder",
+ GINT_TO_POINTER (is_folder));
+
+ window->details->permission_buttons =
+ g_list_prepend (window->details->permission_buttons,
+ check_button);
+
+ g_signal_connect_object (check_button, "toggled",
+ G_CALLBACK (permission_button_toggled),
+ window,
+ 0);
+}
+
+static void
+add_permissions_checkbox_with_label (FMPropertiesWindow *window,
+ GtkTable *table,
+ int row, int column,
+ 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);
+ gtk_table_attach (table, check_button,
+ column, column + 1,
+ row, row + 1,
+ GTK_FILL, 0,
+ 0, 0);
+
+ 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);
+ }
+}
+
+static void
+add_permissions_checkbox (FMPropertiesWindow *window,
+ GtkTable *table,
+ int row, int column,
+ guint32 permission_to_check,
+ GtkLabel *label_for,
+ gboolean is_folder)
+{
+ gchar *label;
+
+ if (column == PERMISSIONS_CHECKBOXES_READ_COLUMN) {
+ label = _("_Read");
+ } else if (column == PERMISSIONS_CHECKBOXES_WRITE_COLUMN) {
+ label = _("_Write");
+ } else {
+ label = _("E_xecute");
+ }
+
+ add_permissions_checkbox_with_label (window, table,
+ row, column,
+ label,
+ permission_to_check,
+ label_for,
+ is_folder);
+}
+
+enum {
+ UNIX_PERM_SUID = S_ISUID,
+ UNIX_PERM_SGID = S_ISGID,
+ UNIX_PERM_STICKY = 01000, /* S_ISVTX not defined on all systems */
+ UNIX_PERM_USER_READ = S_IRUSR,
+ UNIX_PERM_USER_WRITE = S_IWUSR,
+ UNIX_PERM_USER_EXEC = S_IXUSR,
+ UNIX_PERM_USER_ALL = S_IRUSR | S_IWUSR | S_IXUSR,
+ UNIX_PERM_GROUP_READ = S_IRGRP,
+ UNIX_PERM_GROUP_WRITE = S_IWGRP,
+ UNIX_PERM_GROUP_EXEC = S_IXGRP,
+ UNIX_PERM_GROUP_ALL = S_IRGRP | S_IWGRP | S_IXGRP,
+ UNIX_PERM_OTHER_READ = S_IROTH,
+ UNIX_PERM_OTHER_WRITE = S_IWOTH,
+ UNIX_PERM_OTHER_EXEC = S_IXOTH,
+ UNIX_PERM_OTHER_ALL = S_IROTH | S_IWOTH | S_IXOTH
+};
+
+typedef enum {
+ PERMISSION_READ = (1<<0),
+ PERMISSION_WRITE = (1<<1),
+ PERMISSION_EXEC = (1<<2)
+} PermissionValue;
+
+typedef enum {
+ PERMISSION_USER,
+ PERMISSION_GROUP,
+ PERMISSION_OTHER
+} PermissionType;
+
+static guint32 vfs_perms[3][3] = {
+ {UNIX_PERM_USER_READ, UNIX_PERM_USER_WRITE, UNIX_PERM_USER_EXEC},
+ {UNIX_PERM_GROUP_READ, UNIX_PERM_GROUP_WRITE, UNIX_PERM_GROUP_EXEC},
+ {UNIX_PERM_OTHER_READ, UNIX_PERM_OTHER_WRITE, UNIX_PERM_OTHER_EXEC},
+};
+
+static guint32
+permission_to_vfs (PermissionType type, PermissionValue perm)
+{
+ guint32 vfs_perm;
+ g_assert (type >= 0 && type < 3);
+
+ vfs_perm = 0;
+ if (perm & PERMISSION_READ) {
+ vfs_perm |= vfs_perms[type][0];
+ }
+ if (perm & PERMISSION_WRITE) {
+ vfs_perm |= vfs_perms[type][1];
+ }
+ if (perm & PERMISSION_EXEC) {
+ vfs_perm |= vfs_perms[type][2];
+ }
+
+ return vfs_perm;
+}
+
+
+static PermissionValue
+permission_from_vfs (PermissionType type, guint32 vfs_perm)
+{
+ PermissionValue perm;
+ g_assert (type >= 0 && type < 3);
+
+ perm = 0;
+ if (vfs_perm & vfs_perms[type][0]) {
+ perm |= PERMISSION_READ;
+ }
+ if (vfs_perm & vfs_perms[type][1]) {
+ perm |= PERMISSION_WRITE;
+ }
+ if (vfs_perm & vfs_perms[type][2]) {
+ perm |= PERMISSION_EXEC;
+ }
+
+ return perm;
+}
+
+static void
+permission_combo_changed (GtkWidget *combo, FMPropertiesWindow *window)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ gboolean is_folder, use_original;
+ PermissionType type;
+ int new_perm, mask;
+ guint32 vfs_new_perm, vfs_mask;
+
+ is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder"));
+ type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type"));
+
+ if (is_folder) {
+ mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC;
+ } else {
+ mask = PERMISSION_READ|PERMISSION_WRITE;
+ }
+
+ vfs_mask = permission_to_vfs (type, mask);
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+
+ if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) {
+ return;
+ }
+ gtk_tree_model_get (model, &iter, 1, &new_perm, 2, &use_original, -1);
+ vfs_new_perm = permission_to_vfs (type, new_perm);
+
+ update_permissions (window, vfs_new_perm, vfs_mask,
+ is_folder, FALSE, use_original);
+}
+
+static void
+permission_combo_add_multiple_choice (GtkComboBox *combo, GtkTreeIter *iter)
+{
+ GtkTreeModel *model;
+ GtkListStore *store;
+ gboolean found;
+
+ model = gtk_combo_box_get_model (combo);
+ store = GTK_LIST_STORE (model);
+
+ found = FALSE;
+ gtk_tree_model_get_iter_first (model, iter);
+ do {
+ gboolean multi;
+ gtk_tree_model_get (model, iter, 2, &multi, -1);
+
+ if (multi) {
+ found = TRUE;
+ break;
+ }
+ } while (gtk_tree_model_iter_next (model, iter));
+
+ if (!found) {
+ gtk_list_store_append (store, iter);
+ gtk_list_store_set (store, iter, 0, "---", 1, 0, 2, TRUE, -1);
+ }
+}
+
+static void
+permission_combo_update (FMPropertiesWindow *window,
+ GtkComboBox *combo)
+{
+ PermissionType type;
+ PermissionValue perm, all_dir_perm, all_file_perm, all_perm;
+ gboolean is_folder, no_files, no_dirs, all_file_same, all_dir_same, all_same;
+ gboolean all_dir_cannot_set, all_file_cannot_set, sensitive;
+ GtkTreeIter iter;
+ int mask;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GList *l;
+ gboolean is_multi;
+
+ model = gtk_combo_box_get_model (combo);
+
+ is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder"));
+ type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type"));
+
+ is_multi = FALSE;
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) {
+ gtk_tree_model_get (model, &iter, 2, &is_multi, -1);
+ }
+
+ if (is_multi && window->details->has_recursive_apply) {
+ /* Never change from an inconsistent state if we have dirs, even
+ * if the current state is now consistent, because its a useful
+ * state for recursive apply.
+ */
+ return;
+ }
+
+ no_files = TRUE;
+ no_dirs = TRUE;
+ all_dir_same = TRUE;
+ all_file_same = TRUE;
+ all_dir_perm = 0;
+ all_file_perm = 0;
+ all_dir_cannot_set = TRUE;
+ all_file_cannot_set = TRUE;
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ guint32 file_permissions;
+
+ file = CAJA_FILE (l->data);
+
+ if (!caja_file_can_get_permissions (file)) {
+ continue;
+ }
+
+ if (caja_file_is_directory (file)) {
+ mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC;
+ } else {
+ mask = PERMISSION_READ|PERMISSION_WRITE;
+ }
+
+ file_permissions = caja_file_get_permissions (file);
+
+ perm = permission_from_vfs (type, file_permissions) & mask;
+
+ if (caja_file_is_directory (file)) {
+ if (no_dirs) {
+ all_dir_perm = perm;
+ no_dirs = FALSE;
+ } else if (perm != all_dir_perm) {
+ all_dir_same = FALSE;
+ }
+
+ if (caja_file_can_set_permissions (file)) {
+ all_dir_cannot_set = FALSE;
+ }
+ } else {
+ if (no_files) {
+ all_file_perm = perm;
+ no_files = FALSE;
+ } else if (perm != all_file_perm) {
+ all_file_same = FALSE;
+ }
+
+ if (caja_file_can_set_permissions (file)) {
+ all_file_cannot_set = FALSE;
+ }
+ }
+ }
+
+ if (is_folder) {
+ all_same = all_dir_same;
+ all_perm = all_dir_perm;
+ } else {
+ all_same = all_file_same && !no_files;
+ all_perm = all_file_perm;
+ }
+
+ store = GTK_LIST_STORE (model);
+ if (all_same) {
+ gboolean found;
+
+ found = FALSE;
+ gtk_tree_model_get_iter_first (model, &iter);
+ do {
+ int current_perm;
+ gtk_tree_model_get (model, &iter, 1, &current_perm, -1);
+
+ if (current_perm == all_perm) {
+ found = TRUE;
+ break;
+ }
+ } while (gtk_tree_model_iter_next (model, &iter));
+
+ if (!found) {
+ GString *str;
+ str = g_string_new ("");
+
+ if (!(all_perm & PERMISSION_READ)) {
+ /* translators: this gets concatenated to "no read",
+ * "no access", etc. (see following strings)
+ */
+ g_string_append (str, _("no "));
+ }
+ if (is_folder) {
+ g_string_append (str, _("list"));
+ } else {
+ g_string_append (str, _("read"));
+ }
+
+ g_string_append (str, ", ");
+
+ if (!(all_perm & PERMISSION_WRITE)) {
+ g_string_append (str, _("no "));
+ }
+ if (is_folder) {
+ g_string_append (str, _("create/delete"));
+ } else {
+ g_string_append (str, _("write"));
+ }
+
+ if (is_folder) {
+ g_string_append (str, ", ");
+
+ if (!(all_perm & PERMISSION_EXEC)) {
+ g_string_append (str, _("no "));
+ }
+ g_string_append (str, _("access"));
+ }
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ 0, str->str,
+ 1, all_perm, -1);
+
+ g_string_free (str, TRUE);
+ }
+ } else {
+ permission_combo_add_multiple_choice (combo, &iter);
+ }
+
+ g_signal_handlers_block_by_func (G_OBJECT (combo),
+ G_CALLBACK (permission_combo_changed),
+ window);
+
+ gtk_combo_box_set_active_iter (combo, &iter);
+
+ /* Also enable if no files found (for recursive
+ file changes when only selecting folders) */
+ if (is_folder) {
+ sensitive = !all_dir_cannot_set;
+ } else {
+ sensitive = !all_file_cannot_set ||
+ window->details->has_recursive_apply;
+ }
+ gtk_widget_set_sensitive (GTK_WIDGET (combo), sensitive);
+
+ g_signal_handlers_unblock_by_func (G_OBJECT (combo),
+ G_CALLBACK (permission_combo_changed),
+ window);
+
+}
+
+static void
+add_permissions_combo_box (FMPropertiesWindow *window, GtkTable *table,
+ PermissionType type, gboolean is_folder,
+ gboolean short_label)
+{
+ GtkWidget *combo;
+ GtkLabel *label;
+ GtkListStore *store;
+ GtkCellRenderer *cell;
+ GtkTreeIter iter;
+ int row;
+
+ if (short_label) {
+ row = append_title_field (table, _("Access:"), &label);
+ } else if (is_folder) {
+ row = append_title_field (table, _("Folder access:"), &label);
+ } else {
+ row = append_title_field (table, _("File access:"), &label);
+ }
+
+ 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_table_attach (table, combo,
+ VALUE_COLUMN, VALUE_COLUMN + 1,
+ row, row + 1,
+ GTK_FILL, 0,
+ 0, 0);
+}
+
+
+static GtkWidget *
+append_special_execution_checkbox (FMPropertiesWindow *window,
+ GtkTable *table,
+ const char *label_text,
+ guint32 permission_to_check)
+{
+ GtkWidget *check_button;
+ guint last_row;
+
+ last_row = append_row (table);
+
+ check_button = gtk_check_button_new_with_mnemonic (label_text);
+ gtk_widget_show (check_button);
+
+ gtk_table_attach (table, check_button,
+ VALUE_COLUMN, VALUE_COLUMN + 1,
+ last_row, last_row + 1,
+ GTK_FILL, 0,
+ 0, 0);
+
+ set_up_permissions_checkbox (window,
+ check_button,
+ permission_to_check,
+ FALSE);
+ g_object_set_data (G_OBJECT (check_button), "is-special",
+ GINT_TO_POINTER (TRUE));
+
+ return check_button;
+}
+
+static void
+append_special_execution_flags (FMPropertiesWindow *window, GtkTable *table)
+{
+ gint nrows;
+
+ append_special_execution_checkbox
+ (window, table, _("Set _user ID"), UNIX_PERM_SUID);
+
+ g_object_get (table, "n-rows", &nrows, NULL);
+ attach_title_field (table, nrows - 1, _("Special flags:"));
+
+ append_special_execution_checkbox (window, table, _("Set gro_up ID"), UNIX_PERM_SGID);
+ append_special_execution_checkbox (window, table, _("_Sticky"), UNIX_PERM_STICKY);
+
+ g_object_get (table, "n-rows", &nrows, NULL);
+ gtk_table_set_row_spacing (table, nrows - 1, 18);
+}
+
+static gboolean
+all_can_get_permissions (GList *file_list)
+{
+ GList *l;
+ for (l = file_list; l != NULL; l = l->next) {
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+
+ if (!caja_file_can_get_permissions (file)) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+all_can_set_permissions (GList *file_list)
+{
+ GList *l;
+ for (l = file_list; l != NULL; l = l->next) {
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+
+ if (!caja_file_can_set_permissions (file)) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static GHashTable *
+get_initial_permissions (GList *file_list)
+{
+ GHashTable *ret;
+ GList *l;
+
+ ret = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
+
+ for (l = file_list; l != NULL; l = l->next) {
+ guint32 permissions;
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+
+ permissions = caja_file_get_permissions (file);
+ g_hash_table_insert (ret, file,
+ GINT_TO_POINTER (permissions));
+ }
+
+ return ret;
+}
+
+static void
+create_simple_permissions (FMPropertiesWindow *window, GtkTable *page_table)
+{
+ gboolean has_file, has_directory;
+ GtkLabel *group_label;
+ GtkLabel *owner_label;
+ GtkLabel *execute_label;
+ GtkWidget *value;
+ GtkComboBox *group_combo_box;
+ GtkComboBox *owner_combo_box;
+ guint last_row;
+ gint nrows;
+
+ last_row = 0;
+
+ has_file = files_has_file (window);
+ has_directory = files_has_directory (window);
+
+ if (!is_multi_file_window (window) && caja_file_can_set_owner (get_target_file (window))) {
+ owner_label = attach_title_field (page_table, last_row, _("_Owner:"));
+ /* Combo box in this case. */
+ owner_combo_box = attach_owner_combo_box (page_table, last_row, get_target_file (window));
+ gtk_label_set_mnemonic_widget (owner_label,
+ GTK_WIDGET (owner_combo_box));
+ } else {
+ owner_label = attach_title_field (page_table, last_row, _("Owner:"));
+ /* Static text in this case. */
+ value = attach_value_field (window,
+ page_table, last_row, VALUE_COLUMN,
+ "owner",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ gtk_label_set_mnemonic_widget (owner_label, value);
+ }
+
+ if (has_directory) {
+ add_permissions_combo_box (window, page_table,
+ PERMISSION_USER, TRUE, FALSE);
+ }
+ if (has_file || window->details->has_recursive_apply) {
+ add_permissions_combo_box (window, page_table,
+ PERMISSION_USER, FALSE, !has_directory);
+ }
+
+ g_object_get (page_table, "n-rows", &nrows, NULL);
+ gtk_table_set_row_spacing (page_table, nrows - 1, 18);
+
+ if (!is_multi_file_window (window) && caja_file_can_set_group (get_target_file (window))) {
+ last_row = append_title_field (page_table,
+ _("_Group:"),
+ &group_label);
+ /* Combo box in this case. */
+ group_combo_box = attach_group_combo_box (page_table, last_row,
+ get_target_file (window));
+ gtk_label_set_mnemonic_widget (group_label,
+ GTK_WIDGET (group_combo_box));
+ } else {
+ last_row = append_title_field (page_table,
+ _("Group:"),
+ &group_label);
+ /* Static text in this case. */
+ value = attach_value_field (window, page_table, last_row,
+ VALUE_COLUMN,
+ "group",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ gtk_label_set_mnemonic_widget (group_label, value);
+ }
+
+ if (has_directory) {
+ add_permissions_combo_box (window, page_table,
+ PERMISSION_GROUP, TRUE,
+ FALSE);
+ }
+ if (has_file || window->details->has_recursive_apply) {
+ add_permissions_combo_box (window, page_table,
+ PERMISSION_GROUP, FALSE,
+ !has_directory);
+ }
+
+ g_object_get (page_table, "n-rows", &nrows, NULL);
+ gtk_table_set_row_spacing (page_table, nrows - 1, 18);
+
+ append_title_field (page_table,
+ _("Others"),
+ &group_label);
+
+ if (has_directory) {
+ add_permissions_combo_box (window, page_table,
+ PERMISSION_OTHER, TRUE,
+ FALSE);
+ }
+ if (has_file || window->details->has_recursive_apply) {
+ add_permissions_combo_box (window, page_table,
+ PERMISSION_OTHER, FALSE,
+ !has_directory);
+ }
+
+ g_object_get (page_table, "n-rows", &nrows, NULL);
+ gtk_table_set_row_spacing (page_table, nrows - 1, 18);
+
+ last_row = append_title_field (page_table,
+ _("Execute:"),
+ &execute_label);
+ add_permissions_checkbox_with_label (window, page_table,
+ last_row, 1,
+ _("Allow _executing file as program"),
+ UNIX_PERM_USER_EXEC|UNIX_PERM_GROUP_EXEC|UNIX_PERM_OTHER_EXEC,
+ execute_label, FALSE);
+
+}
+
+static void
+create_permission_checkboxes (FMPropertiesWindow *window,
+ GtkTable *page_table,
+ gboolean is_folder)
+{
+ guint checkbox_titles_row;
+ GtkLabel *owner_perm_label;
+ GtkLabel *group_perm_label;
+ GtkLabel *other_perm_label;
+ GtkTable *check_button_table;
+
+ checkbox_titles_row = append_title_field (page_table, _("Owner:"), &owner_perm_label);
+ append_title_field (page_table, _("Group:"), &group_perm_label);
+ append_title_field (page_table, _("Others:"), &other_perm_label);
+
+ check_button_table = GTK_TABLE (gtk_table_new
+ (PERMISSIONS_CHECKBOXES_ROW_COUNT,
+ PERMISSIONS_CHECKBOXES_COLUMN_COUNT,
+ FALSE));
+ apply_standard_table_padding (check_button_table);
+ gtk_widget_show (GTK_WIDGET (check_button_table));
+ gtk_table_attach (page_table, GTK_WIDGET (check_button_table),
+ VALUE_COLUMN, VALUE_COLUMN + 1,
+ checkbox_titles_row, checkbox_titles_row + PERMISSIONS_CHECKBOXES_ROW_COUNT,
+ 0, 0,
+ 0, 0);
+
+ add_permissions_checkbox (window,
+ check_button_table,
+ PERMISSIONS_CHECKBOXES_OWNER_ROW,
+ PERMISSIONS_CHECKBOXES_READ_COLUMN,
+ UNIX_PERM_USER_READ,
+ owner_perm_label,
+ is_folder);
+
+ add_permissions_checkbox (window,
+ check_button_table,
+ PERMISSIONS_CHECKBOXES_OWNER_ROW,
+ PERMISSIONS_CHECKBOXES_WRITE_COLUMN,
+ UNIX_PERM_USER_WRITE,
+ owner_perm_label,
+ is_folder);
+
+ add_permissions_checkbox (window,
+ check_button_table,
+ PERMISSIONS_CHECKBOXES_OWNER_ROW,
+ PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN,
+ UNIX_PERM_USER_EXEC,
+ owner_perm_label,
+ is_folder);
+
+ add_permissions_checkbox (window,
+ check_button_table,
+ PERMISSIONS_CHECKBOXES_GROUP_ROW,
+ PERMISSIONS_CHECKBOXES_READ_COLUMN,
+ UNIX_PERM_GROUP_READ,
+ group_perm_label,
+ is_folder);
+
+ add_permissions_checkbox (window,
+ check_button_table,
+ PERMISSIONS_CHECKBOXES_GROUP_ROW,
+ PERMISSIONS_CHECKBOXES_WRITE_COLUMN,
+ UNIX_PERM_GROUP_WRITE,
+ group_perm_label,
+ is_folder);
+
+ add_permissions_checkbox (window,
+ check_button_table,
+ PERMISSIONS_CHECKBOXES_GROUP_ROW,
+ PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN,
+ UNIX_PERM_GROUP_EXEC,
+ group_perm_label,
+ is_folder);
+
+ add_permissions_checkbox (window,
+ check_button_table,
+ PERMISSIONS_CHECKBOXES_OTHERS_ROW,
+ PERMISSIONS_CHECKBOXES_READ_COLUMN,
+ UNIX_PERM_OTHER_READ,
+ other_perm_label,
+ is_folder);
+
+ add_permissions_checkbox (window,
+ check_button_table,
+ PERMISSIONS_CHECKBOXES_OTHERS_ROW,
+ PERMISSIONS_CHECKBOXES_WRITE_COLUMN,
+ UNIX_PERM_OTHER_WRITE,
+ other_perm_label,
+ is_folder);
+
+ add_permissions_checkbox (window,
+ check_button_table,
+ PERMISSIONS_CHECKBOXES_OTHERS_ROW,
+ PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN,
+ UNIX_PERM_OTHER_EXEC,
+ other_perm_label,
+ is_folder);
+}
+
+static void
+create_advanced_permissions (FMPropertiesWindow *window, GtkTable *page_table)
+{
+ guint last_row;
+ GtkLabel *group_label;
+ GtkLabel *owner_label;
+ GtkComboBox *group_combo_box;
+ GtkComboBox *owner_combo_box;
+ gboolean has_directory, has_file;
+ gint nrows;
+
+ last_row = 0;
+
+ if (!is_multi_file_window (window) && caja_file_can_set_owner (get_target_file (window))) {
+
+ owner_label = attach_title_field (page_table, last_row, _("_Owner:"));
+ /* Combo box in this case. */
+ owner_combo_box = attach_owner_combo_box (page_table, last_row, 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_table, last_row, _("Owner:"));
+ /* Static text in this case. */
+ value = attach_value_field (window,
+ page_table, last_row, VALUE_COLUMN,
+ "owner",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ gtk_label_set_mnemonic_widget (owner_label, value);
+ }
+
+ if (!is_multi_file_window (window) && caja_file_can_set_group (get_target_file (window))) {
+ last_row = append_title_field (page_table,
+ _("_Group:"),
+ &group_label);
+ /* Combo box in this case. */
+ group_combo_box = attach_group_combo_box (page_table, last_row,
+ get_target_file (window));
+ gtk_label_set_mnemonic_widget (group_label,
+ GTK_WIDGET (group_combo_box));
+ } else {
+ last_row = append_title_field (page_table,
+ _("Group:"),
+ NULL);
+ /* Static text in this case. */
+ attach_value_field (window, page_table, last_row,
+ VALUE_COLUMN,
+ "group",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ }
+
+ g_object_get (page_table, "n-rows", &nrows, NULL);
+ gtk_table_set_row_spacing (page_table, nrows - 1, 18);
+
+ has_directory = files_has_directory (window);
+ has_file = files_has_file (window);
+
+ if (has_directory) {
+ if (has_file || window->details->has_recursive_apply) {
+ append_title_field (page_table,
+ _("Folder Permissions:"),
+ NULL);
+ }
+ create_permission_checkboxes (window, page_table, TRUE);
+ g_object_get (page_table, "n-rows", &nrows, NULL);
+ gtk_table_set_row_spacing (page_table, nrows - 1, 18);
+
+ }
+
+
+ if (has_file || window->details->has_recursive_apply) {
+ if (has_directory) {
+ append_title_field (page_table,
+ _("File Permissions:"),
+ NULL);
+ }
+ create_permission_checkboxes (window, page_table, FALSE);
+ g_object_get (page_table, "n-rows", &nrows, NULL);
+ gtk_table_set_row_spacing (page_table, nrows - 1, 18);
+ }
+
+ append_special_execution_flags (window, page_table);
+
+ append_title_value_pair
+ (window, page_table, _("Text view:"),
+ "permissions", INCONSISTENT_STATE_STRING,
+ FALSE);
+}
+
+static void
+set_recursive_permissions_done (gpointer callback_data)
+{
+ FMPropertiesWindow *window;
+
+ window = FM_PROPERTIES_WINDOW (callback_data);
+ end_long_operation (window);
+
+ g_object_unref (window);
+}
+
+
+static void
+apply_recursive_clicked (GtkWidget *recursive_button,
+ FMPropertiesWindow *window)
+{
+ guint32 file_permission, file_permission_mask;
+ guint32 dir_permission, dir_permission_mask;
+ guint32 vfs_mask, vfs_new_perm, p;
+ 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) {
+ CajaFile *file;
+ char *uri;
+
+ file = CAJA_FILE (l->data);
+
+ if (caja_file_is_directory (file) &&
+ caja_file_can_set_permissions (file)) {
+ uri = caja_file_get_uri (file);
+ start_long_operation (window);
+ g_object_ref (window);
+ caja_file_set_permissions_recursive (uri,
+ file_permission,
+ file_permission_mask,
+ dir_permission,
+ dir_permission_mask,
+ set_recursive_permissions_done,
+ window);
+ g_free (uri);
+ }
+ }
+}
+
+static void
+create_permissions_page (FMPropertiesWindow *window)
+{
+ GtkWidget *vbox, *button, *hbox;
+ GtkTable *page_table;
+ char *file_name, *prompt_text;
+ GList *file_list;
+ guint last_row;
+ gint nrows;
+
+ vbox = create_page_with_vbox (window->details->notebook,
+ _("Permissions"));
+
+ file_list = window->details->original_files;
+
+ window->details->initial_permissions = NULL;
+
+ if (all_can_get_permissions (file_list) && all_can_get_permissions (window->details->target_files)) {
+ 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 (
+ GTK_VBOX (vbox),
+ _("You are not the owner, so you cannot change these permissions."));
+ }
+
+ page_table = GTK_TABLE (gtk_table_new (1, COLUMN_COUNT, FALSE));
+ window->details->permissions_table = page_table;
+
+ apply_standard_table_padding (page_table);
+ gtk_widget_show (GTK_WIDGET (page_table));
+ gtk_box_pack_start (GTK_BOX (vbox),
+ GTK_WIDGET (page_table),
+ TRUE, TRUE, 0);
+
+ if (eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_ADVANCED_PERMISSIONS)) {
+ window->details->advanced_permissions = TRUE;
+ create_advanced_permissions (window, page_table);
+ } else {
+ window->details->advanced_permissions = FALSE;
+ create_simple_permissions (window, page_table);
+ }
+
+ g_object_get (page_table, "n-rows", &nrows, NULL);
+ gtk_table_set_row_spacing (page_table, nrows - 1, 18);
+
+#ifdef HAVE_SELINUX
+ append_title_value_pair
+ (window, page_table, _("SELinux context:"),
+ "selinux_context", INCONSISTENT_STATE_STRING,
+ FALSE);
+#endif
+ append_title_value_pair
+ (window, page_table, _("Last changed:"),
+ "date_permissions", INCONSISTENT_STATE_STRING,
+ FALSE);
+
+ if (window->details->has_recursive_apply) {
+ last_row = append_row (page_table);
+ hbox = gtk_hbox_new (FALSE, 0);
+ gtk_widget_show (hbox);
+ gtk_table_attach (page_table, hbox,
+ 0, 2,
+ last_row, last_row+1,
+ GTK_FILL, 0,
+ 0, 0);
+
+ 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 = caja_file_get_display_name (get_target_file (window));
+ prompt_text = g_strdup_printf (_("The permissions of \"%s\" could not be determined."), file_name);
+ g_free (file_name);
+ } else {
+ prompt_text = g_strdup (_("The permissions of the selected file could not be determined."));
+ }
+
+ add_prompt (GTK_VBOX (vbox), prompt_text, TRUE);
+ g_free (prompt_text);
+ }
+}
+
+static void
+append_extension_pages (FMPropertiesWindow *window)
+{
+ GList *providers;
+ GList *p;
+
+ providers = caja_module_get_extensions_for_type (CAJA_TYPE_PROPERTY_PAGE_PROVIDER);
+
+ for (p = providers; p != NULL; p = p->next) {
+ CajaPropertyPageProvider *provider;
+ GList *pages;
+ GList *l;
+
+ provider = CAJA_PROPERTY_PAGE_PROVIDER (p->data);
+
+ pages = caja_property_page_provider_get_pages
+ (provider, window->details->original_files);
+
+ for (l = pages; l != NULL; l = l->next) {
+ CajaPropertyPage *page;
+ GtkWidget *page_widget;
+ GtkWidget *label;
+
+ page = CAJA_PROPERTY_PAGE (l->data);
+
+ g_object_get (G_OBJECT (page),
+ "page", &page_widget, "label", &label,
+ NULL);
+
+ gtk_notebook_append_page (window->details->notebook,
+ page_widget, label);
+
+ g_object_set_data (G_OBJECT (page_widget),
+ "is-extension-page",
+ page);
+
+ g_object_unref (page_widget);
+ g_object_unref (label);
+
+ g_object_unref (page);
+ }
+
+ g_list_free (pages);
+ }
+
+ caja_module_extension_list_free (providers);
+}
+
+static gboolean
+should_show_emblems (FMPropertiesWindow *window)
+{
+ /* FIXME bugzilla.gnome.org 45643:
+ * Emblems aren't displayed on the the desktop Trash icon, so
+ * we shouldn't pretend that they work by showing them here.
+ * When bug 5643 is fixed we can remove this case.
+ */
+ if (!is_multi_file_window (window)
+ && is_merged_trash_directory (get_target_file (window))) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_permissions (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+
+ file = get_target_file (window);
+
+ /* Don't show permissions for Trash and Computer since they're not
+ * really file system objects.
+ */
+ if (!is_multi_file_window (window)
+ && (is_merged_trash_directory (file) ||
+ is_computer_directory (file))) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static char *
+get_pending_key (GList *file_list)
+{
+ GList *l;
+ GList *uris;
+ GString *key;
+ char *ret;
+
+ uris = NULL;
+ for (l = file_list; l != NULL; l = l->next) {
+ uris = g_list_prepend (uris, caja_file_get_uri (CAJA_FILE (l->data)));
+ }
+ uris = g_list_sort (uris, (GCompareFunc)strcmp);
+
+ key = g_string_new ("");
+ for (l = uris; l != NULL; l = l->next) {
+ g_string_append (key, l->data);
+ g_string_append (key, ";");
+ }
+
+ eel_g_list_free_deep (uris);
+
+ ret = key->str;
+ g_string_free (key, FALSE);
+
+ return ret;
+}
+
+static StartupData *
+startup_data_new (GList *original_files,
+ GList *target_files,
+ const char *pending_key,
+ GtkWidget *parent_widget)
+{
+ StartupData *data;
+ GList *l;
+
+ data = g_new0 (StartupData, 1);
+ data->original_files = caja_file_list_copy (original_files);
+ data->target_files = caja_file_list_copy (target_files);
+ data->parent_widget = parent_widget;
+ data->pending_key = g_strdup (pending_key);
+ data->pending_files = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
+
+ for (l = data->target_files; l != NULL; l = l->next) {
+ g_hash_table_insert (data->pending_files, l->data, l->data);
+ }
+
+ return data;
+}
+
+static void
+startup_data_free (StartupData *data)
+{
+ caja_file_list_free (data->original_files);
+ caja_file_list_free (data->target_files);
+ g_hash_table_destroy (data->pending_files);
+ g_free (data->pending_key);
+ g_free (data);
+}
+
+static void
+file_changed_callback (CajaFile *file, gpointer user_data)
+{
+ FMPropertiesWindow *window = FM_PROPERTIES_WINDOW (user_data);
+
+ if (!g_list_find (window->details->changed_files, file)) {
+ caja_file_ref (file);
+ window->details->changed_files = g_list_prepend (window->details->changed_files, file);
+
+ schedule_files_update (window);
+ }
+}
+
+static gboolean
+is_a_special_file (CajaFile *file)
+{
+ if (file == NULL ||
+ CAJA_IS_DESKTOP_ICON_FILE (file) ||
+ caja_file_is_caja_link (file) ||
+ is_merged_trash_directory (file) ||
+ is_computer_directory (file)) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+should_show_open_with (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+
+ /* Don't show open with tab for desktop special icons (trash, etc)
+ * 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 = CAJA_FILE (l->data);
+ if (caja_file_is_directory (file) ||
+ is_a_special_file (file)) {
+ return FALSE;
+ }
+ }
+ }
+ } else {
+ file = get_original_file (window);
+ if (caja_file_is_directory (file) ||
+ is_a_special_file (file)) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void
+create_open_with_page (FMPropertiesWindow *window)
+{
+ GtkWidget *vbox;
+ char *mime_type;
+ char *uri;
+
+ mime_type = caja_file_get_mime_type (get_target_file (window));
+
+ if (!is_multi_file_window (window)) {
+ uri = caja_file_get_uri (get_target_file (window));
+ if (uri == NULL) {
+ return;
+ }
+ vbox = caja_mime_application_chooser_new (uri, mime_type);
+
+ g_free (uri);
+ } else {
+ GList *uris;
+
+ uris = window->details->original_files;
+ if (uris == NULL) {
+ return;
+ }
+ vbox = caja_mime_application_chooser_new_for_multiple_files (uris, mime_type);
+ }
+
+ gtk_widget_show (vbox);
+ g_free (mime_type);
+
+ gtk_notebook_append_page (window->details->notebook,
+ vbox, gtk_label_new (_("Open With")));
+}
+
+
+static FMPropertiesWindow *
+create_properties_window (StartupData *startup_data)
+{
+ FMPropertiesWindow *window;
+ GList *l;
+
+ window = FM_PROPERTIES_WINDOW (gtk_widget_new (fm_properties_window_get_type (), NULL));
+
+ window->details->original_files = caja_file_list_copy (startup_data->original_files);
+
+ window->details->target_files = caja_file_list_copy (startup_data->target_files);
+
+ gtk_window_set_wmclass (GTK_WINDOW (window), "file_properties", "Caja");
+ gtk_window_set_screen (GTK_WINDOW (window),
+ gtk_widget_get_screen (startup_data->parent_widget));
+
+ gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_DIALOG);
+
+ /* Set initial window title */
+ update_properties_window_title (window);
+
+ /* Start monitoring the file attributes we display. Note that some
+ * of the attributes are for the original file, and some for the
+ * target files.
+ */
+
+ for (l = window->details->original_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ CajaFileAttributes attributes;
+
+ file = CAJA_FILE (l->data);
+
+ attributes =
+ CAJA_FILE_ATTRIBUTES_FOR_ICON |
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_LINK_INFO;
+
+ caja_file_monitor_add (CAJA_FILE (l->data),
+ &window->details->original_files,
+ attributes);
+ }
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ CajaFileAttributes attributes;
+
+ file = CAJA_FILE (l->data);
+
+ attributes = 0;
+ if (caja_file_is_directory (file)) {
+ attributes |= CAJA_FILE_ATTRIBUTE_DEEP_COUNTS;
+ }
+
+ attributes |= CAJA_FILE_ATTRIBUTE_INFO;
+ caja_file_monitor_add (file, &window->details->target_files, attributes);
+ }
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ g_signal_connect_object (CAJA_FILE (l->data),
+ "changed",
+ G_CALLBACK (file_changed_callback),
+ G_OBJECT (window),
+ 0);
+ }
+
+ for (l = window->details->original_files; l != NULL; l = l->next) {
+ g_signal_connect_object (CAJA_FILE (l->data),
+ "changed",
+ G_CALLBACK (file_changed_callback),
+ G_OBJECT (window),
+ 0);
+ }
+
+ /* Create the notebook tabs. */
+ window->details->notebook = GTK_NOTEBOOK (gtk_notebook_new ());
+ gtk_widget_show (GTK_WIDGET (window->details->notebook));
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))),
+ GTK_WIDGET (window->details->notebook),
+ TRUE, TRUE, 0);
+
+ /* Create the pages. */
+ create_basic_page (window);
+
+ if (should_show_emblems (window)) {
+ create_emblems_page (window);
+ }
+
+ if (should_show_permissions (window)) {
+ create_permissions_page (window);
+ }
+
+ if (should_show_open_with (window)) {
+ create_open_with_page (window);
+ }
+
+ /* append pages from available views */
+ append_extension_pages (window);
+
+ gtk_dialog_add_buttons (GTK_DIALOG (window),
+ GTK_STOCK_HELP, GTK_RESPONSE_HELP,
+ GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+ NULL);
+
+ /* FIXME - HIGificiation, should be done inside GTK+ */
+ gtk_widget_ensure_style (GTK_WIDGET (window));
+ gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (window))), 12);
+ gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (window))), 0);
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))), 12);
+ gtk_dialog_set_has_separator (GTK_DIALOG (window), FALSE);
+
+ /* Update from initial state */
+ properties_window_update (window, NULL);
+
+ return window;
+}
+
+static GList *
+get_target_file_list (GList *original_files)
+{
+ GList *ret;
+ GList *l;
+
+ ret = NULL;
+
+ for (l = original_files; l != NULL; l = l->next) {
+ CajaFile *target;
+
+ target = get_target_file_for_original_file (CAJA_FILE (l->data));
+
+ ret = g_list_prepend (ret, target);
+ }
+
+ ret = g_list_reverse (ret);
+
+ return ret;
+}
+
+static void
+add_window (FMPropertiesWindow *window)
+{
+ if (!is_multi_file_window (window)) {
+ g_hash_table_insert (windows,
+ get_original_file (window),
+ window);
+ g_object_set_data (G_OBJECT (window), "window_key",
+ get_original_file (window));
+ }
+}
+
+static void
+remove_window (FMPropertiesWindow *window)
+{
+ gpointer key;
+
+ key = g_object_get_data (G_OBJECT (window), "window_key");
+ if (key) {
+ g_hash_table_remove (windows, key);
+ }
+}
+
+static GtkWindow *
+get_existing_window (GList *file_list)
+{
+ if (!file_list->next) {
+ return g_hash_table_lookup (windows, file_list->data);
+ }
+
+ return NULL;
+}
+
+static void
+cancel_create_properties_window_callback (gpointer callback_data)
+{
+ remove_pending ((StartupData *)callback_data, TRUE, FALSE, TRUE);
+}
+
+static void
+parent_widget_destroyed_callback (GtkWidget *widget, gpointer callback_data)
+{
+ g_assert (widget == ((StartupData *)callback_data)->parent_widget);
+
+ remove_pending ((StartupData *)callback_data, TRUE, TRUE, FALSE);
+}
+
+static void
+cancel_call_when_ready_callback (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ caja_file_cancel_call_when_ready
+ (CAJA_FILE (key),
+ is_directory_ready_callback,
+ user_data);
+}
+
+static void
+remove_pending (StartupData *startup_data,
+ gboolean cancel_call_when_ready,
+ gboolean cancel_timed_wait,
+ gboolean cancel_destroy_handler)
+{
+ if (cancel_call_when_ready) {
+ g_hash_table_foreach (startup_data->pending_files,
+ cancel_call_when_ready_callback,
+ startup_data);
+
+ }
+ if (cancel_timed_wait) {
+ eel_timed_wait_stop
+ (cancel_create_properties_window_callback, startup_data);
+ }
+ if (cancel_destroy_handler) {
+ g_signal_handlers_disconnect_by_func (startup_data->parent_widget,
+ G_CALLBACK (parent_widget_destroyed_callback),
+ startup_data);
+ }
+
+ g_hash_table_remove (pending_lists, startup_data->pending_key);
+
+ startup_data_free (startup_data);
+}
+
+static void
+is_directory_ready_callback (CajaFile *file,
+ gpointer data)
+{
+ StartupData *startup_data;
+
+ startup_data = data;
+
+ g_hash_table_remove (startup_data->pending_files, file);
+
+ if (g_hash_table_size (startup_data->pending_files) == 0) {
+ FMPropertiesWindow *new_window;
+
+ new_window = create_properties_window (startup_data);
+
+ add_window (new_window);
+
+ remove_pending (startup_data, FALSE, TRUE, TRUE);
+
+/* FIXME bugzilla.gnome.org 42151:
+ * See comment elsewhere in this file about bug 2151.
+ */
+#ifdef UNDO_ENABLED
+ caja_undo_share_undo_manager (GTK_OBJECT (new_window),
+ GTK_OBJECT (callback_data));
+#endif
+ gtk_window_present (GTK_WINDOW (new_window));
+ }
+}
+
+
+void
+fm_properties_window_present (GList *original_files,
+ GtkWidget *parent_widget)
+{
+ GList *l, *next;
+ GtkWidget *parent_window;
+ StartupData *startup_data;
+ GList *target_files;
+ GtkWindow *existing_window;
+ char *pending_key;
+
+ g_return_if_fail (original_files != NULL);
+ g_return_if_fail (GTK_IS_WIDGET (parent_widget));
+
+ /* Create the hash tables first time through. */
+ if (windows == NULL) {
+ windows = eel_g_hash_table_new_free_at_exit
+ (NULL, NULL, "property windows");
+ }
+
+ if (pending_lists == NULL) {
+ pending_lists = eel_g_hash_table_new_free_at_exit
+ (g_str_hash, g_str_equal, "pending property window files");
+ }
+
+ /* Look to see if there's already a window for this file. */
+ existing_window = get_existing_window (original_files);
+ if (existing_window != NULL) {
+ gtk_window_set_screen (existing_window,
+ gtk_widget_get_screen (parent_widget));
+ gtk_window_present (existing_window);
+ return;
+ }
+
+
+ pending_key = get_pending_key (original_files);
+
+ /* Look to see if we're already waiting for a window for this file. */
+ if (g_hash_table_lookup (pending_lists, pending_key) != NULL) {
+ return;
+ }
+
+ target_files = get_target_file_list (original_files);
+
+ startup_data = startup_data_new (original_files,
+ target_files,
+ pending_key,
+ parent_widget);
+
+ caja_file_list_free (target_files);
+ g_free(pending_key);
+
+ /* Wait until we can tell whether it's a directory before showing, since
+ * some one-time layout decisions depend on that info.
+ */
+
+ g_hash_table_insert (pending_lists, startup_data->pending_key, startup_data->pending_key);
+ g_signal_connect (parent_widget, "destroy",
+ G_CALLBACK (parent_widget_destroyed_callback), startup_data);
+
+ parent_window = gtk_widget_get_ancestor (parent_widget, GTK_TYPE_WINDOW);
+
+ eel_timed_wait_start
+ (cancel_create_properties_window_callback,
+ startup_data,
+ _("Creating Properties window."),
+ parent_window == NULL ? NULL : GTK_WINDOW (parent_window));
+
+
+ for (l = startup_data->target_files; l != NULL; l = next) {
+ next = l->next;
+ caja_file_call_when_ready
+ (CAJA_FILE (l->data),
+ CAJA_FILE_ATTRIBUTE_INFO,
+ is_directory_ready_callback,
+ startup_data);
+ }
+}
+
+static void
+real_response (GtkDialog *dialog,
+ int response)
+{
+ GError *error = NULL;
+
+ switch (response) {
+ case GTK_RESPONSE_HELP:
+ gtk_show_uri (gtk_window_get_screen (GTK_WINDOW (dialog)),
+ "ghelp:user-guide#goscaja-51",
+ gtk_get_current_event_time (),
+ &error);
+ if (error != NULL) {
+ eel_show_error_dialog (_("There was an error displaying help."), error->message,
+ GTK_WINDOW (dialog));
+ g_error_free (error);
+ }
+ break;
+
+ case GTK_RESPONSE_NONE:
+ case GTK_RESPONSE_CLOSE:
+ case GTK_RESPONSE_DELETE_EVENT:
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+real_destroy (GtkObject *object)
+{
+ FMPropertiesWindow *window;
+ GList *l;
+
+ window = FM_PROPERTIES_WINDOW (object);
+
+ remove_window (window);
+
+ for (l = window->details->original_files; l != NULL; l = l->next) {
+ caja_file_monitor_remove (CAJA_FILE (l->data), &window->details->original_files);
+ }
+ caja_file_list_free (window->details->original_files);
+ window->details->original_files = NULL;
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ caja_file_monitor_remove (CAJA_FILE (l->data), &window->details->target_files);
+ }
+ caja_file_list_free (window->details->target_files);
+ window->details->target_files = NULL;
+
+ caja_file_list_free (window->details->changed_files);
+ window->details->changed_files = NULL;
+
+ window->details->name_field = NULL;
+
+ g_list_free (window->details->emblem_buttons);
+ window->details->emblem_buttons = NULL;
+
+ if (window->details->initial_emblems) {
+ g_hash_table_destroy (window->details->initial_emblems);
+ window->details->initial_emblems = NULL;
+ }
+
+ g_list_free (window->details->permission_buttons);
+ window->details->permission_buttons = NULL;
+
+ g_list_free (window->details->permission_combos);
+ window->details->permission_combos = NULL;
+
+ if (window->details->initial_permissions) {
+ g_hash_table_destroy (window->details->initial_permissions);
+ window->details->initial_permissions = NULL;
+ }
+
+ g_list_free (window->details->value_fields);
+ window->details->value_fields = NULL;
+
+ if (window->details->update_directory_contents_timeout_id != 0) {
+ g_source_remove (window->details->update_directory_contents_timeout_id);
+ window->details->update_directory_contents_timeout_id = 0;
+ }
+
+ if (window->details->update_files_timeout_id != 0) {
+ g_source_remove (window->details->update_files_timeout_id);
+ window->details->update_files_timeout_id = 0;
+ }
+
+ GTK_OBJECT_CLASS (parent_class)->destroy (object);
+}
+
+static void
+real_finalize (GObject *object)
+{
+ FMPropertiesWindow *window;
+
+ window = FM_PROPERTIES_WINDOW (object);
+
+ eel_g_list_free_deep (window->details->mime_list);
+
+ g_free (window->details->pending_name);
+ g_free (window->details);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+/* converts
+ * file://foo/foobar/foofoo/bar
+ * to
+ * foofoo/bar
+ * if
+ * file://foo/foobar
+ * is the parent
+ *
+ * It does not resolve any symlinks.
+ * */
+static char *
+make_relative_uri_from_full (const char *uri,
+ const char *base_uri)
+{
+ g_assert (uri != NULL);
+ g_assert (base_uri != NULL);
+
+ if (g_str_has_prefix (uri, base_uri)) {
+ uri += strlen (base_uri);
+ if (*uri != '/') {
+ return NULL;
+ }
+
+ while (*uri == '/') {
+ uri++;
+ }
+
+ if (*uri != '\0') {
+ return g_strdup (uri);
+ }
+ }
+
+ return NULL;
+}
+
+/* icon selection callback to set the image of the file object to the selected file */
+static void
+set_icon (const char* icon_uri, FMPropertiesWindow *properties_window)
+{
+ CajaFile *file;
+ char *file_uri;
+ char *icon_path;
+ char *real_icon_uri;
+
+ g_assert (icon_uri != NULL);
+ g_assert (FM_IS_PROPERTIES_WINDOW (properties_window));
+
+ icon_path = g_filename_from_uri (icon_uri, NULL, NULL);
+ /* we don't allow remote URIs */
+ if (icon_path != NULL) {
+ GList *l;
+
+ for (l = properties_window->details->original_files; l != NULL; l = l->next) {
+ file = CAJA_FILE (l->data);
+
+ file_uri = caja_file_get_uri (file);
+
+ if (caja_file_is_mime_type (file, "application/x-desktop")) {
+ if (caja_link_local_set_icon (file_uri, icon_path)) {
+ caja_file_invalidate_attributes (file,
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_LINK_INFO);
+ }
+ } else {
+ real_icon_uri = make_relative_uri_from_full (icon_uri, file_uri);
+ if (real_icon_uri == NULL) {
+ real_icon_uri = g_strdup (icon_uri);
+ }
+
+ caja_file_set_metadata (file, CAJA_METADATA_KEY_CUSTOM_ICON, NULL, real_icon_uri);
+ caja_file_set_metadata (file, CAJA_METADATA_KEY_ICON_SCALE, NULL, NULL);
+
+ g_free (real_icon_uri);
+ }
+
+ g_free (file_uri);
+ }
+
+ g_free (icon_path);
+ }
+}
+
+static void
+update_preview_callback (GtkFileChooser *icon_chooser,
+ FMPropertiesWindow *window)
+{
+ GtkWidget *preview_widget;
+ GdkPixbuf *pixbuf, *scaled_pixbuf;
+ char *filename;
+ double scale;
+
+ pixbuf = NULL;
+
+ filename = gtk_file_chooser_get_filename (icon_chooser);
+ if (filename != NULL) {
+ pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
+ }
+
+ if (pixbuf != NULL) {
+ preview_widget = gtk_file_chooser_get_preview_widget (icon_chooser);
+ gtk_file_chooser_set_preview_widget_active (icon_chooser, TRUE);
+
+ if (gdk_pixbuf_get_width (pixbuf) > PREVIEW_IMAGE_WIDTH) {
+ scale = (double)gdk_pixbuf_get_height (pixbuf) /
+ gdk_pixbuf_get_width (pixbuf);
+
+ scaled_pixbuf = mate_desktop_thumbnail_scale_down_pixbuf
+ (pixbuf,
+ PREVIEW_IMAGE_WIDTH,
+ scale * PREVIEW_IMAGE_WIDTH);
+ g_object_unref (pixbuf);
+ pixbuf = scaled_pixbuf;
+ }
+
+ gtk_image_set_from_pixbuf (GTK_IMAGE (preview_widget), pixbuf);
+ } else {
+ gtk_file_chooser_set_preview_widget_active (icon_chooser, FALSE);
+ }
+
+ g_free (filename);
+
+ if (pixbuf != NULL) {
+ g_object_unref (pixbuf);
+ }
+}
+
+static void
+custom_icon_file_chooser_response_cb (GtkDialog *dialog,
+ gint response,
+ FMPropertiesWindow *window)
+{
+ char *uri;
+
+ switch (response) {
+ case GTK_RESPONSE_NO:
+ reset_icon (window);
+ break;
+
+ case GTK_RESPONSE_OK:
+ uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
+ set_icon (uri, window);
+ g_free (uri);
+ break;
+
+ default:
+ break;
+ }
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+}
+
+static void
+select_image_button_callback (GtkWidget *widget,
+ FMPropertiesWindow *window)
+{
+ GtkWidget *dialog, *preview;
+ GtkFileFilter *filter;
+ GList *l;
+ CajaFile *file;
+ char *uri;
+ char *image_path;
+ gboolean revert_is_sensitive;
+
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+
+ dialog = window->details->icon_chooser;
+
+ if (dialog == NULL) {
+ dialog = gtk_file_chooser_dialog_new (_("Select Custom Icon"), GTK_WINDOW (window),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_REVERT_TO_SAVED, GTK_RESPONSE_NO,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN, GTK_RESPONSE_OK,
+ NULL);
+ gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), "/usr/share/pixmaps", NULL);
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_add_pixbuf_formats (filter);
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+ preview = gtk_image_new ();
+ gtk_widget_set_size_request (preview, PREVIEW_IMAGE_WIDTH, -1);
+ gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (dialog), preview);
+ gtk_file_chooser_set_use_preview_label (GTK_FILE_CHOOSER (dialog), FALSE);
+ gtk_file_chooser_set_preview_widget_active (GTK_FILE_CHOOSER (dialog), FALSE);
+
+ g_signal_connect (dialog, "update-preview",
+ G_CALLBACK (update_preview_callback), window);
+
+ window->details->icon_chooser = dialog;
+
+ g_object_add_weak_pointer (G_OBJECT (dialog),
+ (gpointer *) &window->details->icon_chooser);
+ }
+
+ /* it's likely that the user wants to pick an icon that is inside a local directory */
+ if (g_list_length (window->details->original_files) == 1) {
+ file = CAJA_FILE (window->details->original_files->data);
+
+ if (caja_file_is_directory (file)) {
+ uri = caja_file_get_uri (file);
+
+ image_path = g_filename_from_uri (uri, NULL, NULL);
+ if (image_path != NULL) {
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), image_path);
+ g_free (image_path);
+ }
+
+ g_free (uri);
+ }
+ }
+
+ revert_is_sensitive = FALSE;
+ for (l = window->details->original_files; l != NULL; l = l->next) {
+ file = CAJA_FILE (l->data);
+ image_path = caja_file_get_metadata (file, CAJA_METADATA_KEY_CUSTOM_ICON, NULL);
+ revert_is_sensitive = (image_path != NULL);
+ g_free (image_path);
+
+ if (revert_is_sensitive) {
+ break;
+ }
+ }
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_NO, revert_is_sensitive);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (custom_icon_file_chooser_response_cb), window);
+ gtk_widget_show (dialog);
+}
+
+static void
+fm_properties_window_class_init (FMPropertiesWindowClass *class)
+{
+ GtkBindingSet *binding_set;
+
+ G_OBJECT_CLASS (class)->finalize = real_finalize;
+ GTK_OBJECT_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_Escape, 0,
+ "close", 0);
+}
+
+static void
+fm_properties_window_init (FMPropertiesWindow *window)
+{
+ window->details = g_new0 (FMPropertiesWindowDetails, 1);
+}