summaryrefslogtreecommitdiffstats
path: root/src/nautilus-properties-window.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/nautilus-properties-window.c4457
1 files changed, 4457 insertions, 0 deletions
diff --git a/src/nautilus-properties-window.c b/src/nautilus-properties-window.c
new file mode 100644
index 0000000..32fd42e
--- /dev/null
+++ b/src/nautilus-properties-window.c
@@ -0,0 +1,4457 @@
+/* fm-properties-window.c - window that lets user modify file properties
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Darin Adler <darin@bentspoon.com>
+ */
+
+#include "nautilus-properties-window.h"
+
+#include <cairo.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-string.h>
+#include <eel/eel-vfs-extensions.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <gio/gunixmounts.h>
+#include <nautilus-extension.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-desktop-thumbnail.h>
+
+#include "nautilus-application.h"
+#include "nautilus-dbus-launcher.h"
+#include "nautilus-enums.h"
+#include "nautilus-error-reporting.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-icon-info.h"
+#include "nautilus-metadata.h"
+#include "nautilus-mime-actions.h"
+#include "nautilus-module.h"
+#include "nautilus-properties-model.h"
+#include "nautilus-properties-item.h"
+#include "nautilus-signaller.h"
+#include "nautilus-tag-manager.h"
+#include "nautilus-ui-utilities.h"
+
+static GHashTable *pending_lists;
+
+typedef struct
+{
+ NautilusFile *file;
+ char *owner;
+ GtkWindow *window;
+ unsigned int timeout;
+ gboolean cancelled;
+} OwnerChange;
+
+typedef struct
+{
+ NautilusFile *file;
+ char *group;
+ GtkWindow *window;
+ unsigned int timeout;
+ gboolean cancelled;
+} GroupChange;
+
+struct _NautilusPropertiesWindow
+{
+ AdwWindow parent_instance;
+
+ GList *original_files;
+ GList *target_files;
+
+ AdwWindowTitle *window_title;
+
+ GtkStack *page_stack;
+
+ /* Basic page */
+
+ GtkStack *icon_stack;
+ GtkWidget *icon_image;
+ GtkWidget *icon_button;
+ GtkWidget *icon_button_image;
+ GtkWidget *icon_chooser;
+
+ GtkWidget *star_button;
+
+ GtkLabel *name_value_label;
+ GtkWidget *type_value_label;
+ GtkLabel *type_file_system_label;
+ GtkWidget *size_value_label;
+ GtkWidget *contents_box;
+ GtkWidget *contents_value_label;
+ GtkWidget *free_space_value_label;
+
+ GtkWidget *disk_list_box;
+ GtkLevelBar *disk_space_level_bar;
+ GtkWidget *disk_space_used_value;
+ GtkWidget *disk_space_free_value;
+ GtkWidget *disk_space_capacity_value;
+
+ GtkWidget *locations_list_box;
+ GtkWidget *link_target_row;
+ GtkWidget *link_target_value_label;
+ GtkWidget *contents_spinner;
+ guint update_directory_contents_timeout_id;
+ guint update_files_timeout_id;
+ GtkWidget *parent_folder_row;
+ GtkWidget *parent_folder_value_label;
+
+ GtkWidget *trashed_list_box;
+ GtkWidget *trashed_on_value_label;
+ GtkWidget *original_folder_value_label;
+
+ GtkWidget *times_list_box;
+ GtkWidget *modified_row;
+ GtkWidget *modified_value_label;
+ GtkWidget *created_row;
+ GtkWidget *created_value_label;
+ GtkWidget *accessed_row;
+ GtkWidget *accessed_value_label;
+
+ GtkWidget *permissions_navigation_row;
+ GtkWidget *permissions_value_label;
+
+ GtkWidget *extension_models_list_box;
+
+ /* Permissions page */
+
+ GtkWidget *permissions_stack;
+
+ GtkWidget *unknown_permissions_page;
+
+ GtkWidget *bottom_prompt_seperator;
+ GtkWidget *not_the_owner_label;
+
+ AdwComboRow *owner_row;
+ AdwComboRow *owner_access_row;
+ AdwComboRow *owner_folder_access_row;
+ AdwComboRow *owner_file_access_row;
+
+ AdwComboRow *group_row;
+ AdwComboRow *group_access_row;
+ AdwComboRow *group_folder_access_row;
+ AdwComboRow *group_file_access_row;
+
+ AdwComboRow *others_access_row;
+ AdwComboRow *others_folder_access_row;
+ AdwComboRow *others_file_access_row;
+
+ AdwComboRow *execution_row;
+ GtkSwitch *execution_switch;
+
+ GtkWidget *security_context_list_box;
+ GtkWidget *security_context_value_label;
+
+ GtkWidget *change_permissions_button_box;
+ GtkWidget *change_permissions_button;
+
+ GroupChange *group_change;
+ OwnerChange *owner_change;
+
+ GList *permission_rows;
+ GList *change_permission_combos;
+ GHashTable *initial_permissions;
+ gboolean has_recursive_apply;
+
+ GList *value_fields;
+
+ GList *mime_list;
+
+ gboolean deep_count_finished;
+ GList *deep_count_files;
+ guint deep_count_spinner_timeout_id;
+
+ guint long_operation_underway;
+
+ GList *changed_files;
+
+ guint64 volume_capacity;
+ guint64 volume_free;
+ guint64 volume_used;
+};
+
+typedef enum
+{
+ NO_FILES_OR_FOLDERS = (0),
+ FILES_ONLY = (1 << 0),
+ FOLDERS_ONLY = (1 << 1),
+ FILES_AND_FOLDERS = FILES_ONLY | FOLDERS_ONLY,
+} FilterType;
+
+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_NONE = (0),
+ PERMISSION_READ = (1 << 0),
+ PERMISSION_WRITE = (1 << 1),
+ PERMISSION_EXEC = (1 << 2),
+ PERMISSION_INCONSISTENT = (1 << 3)
+} PermissionValue;
+
+typedef enum
+{
+ PERMISSION_USER,
+ PERMISSION_GROUP,
+ PERMISSION_OTHER,
+ NUM_PERMISSION_TYPE
+} PermissionType;
+
+/** Contains permissions for files and folders for each PermissionType */
+typedef struct
+{
+ NautilusPropertiesWindow *window;
+
+ PermissionValue folder_permissions[NUM_PERMISSION_TYPE];
+ PermissionValue file_permissions[NUM_PERMISSION_TYPE];
+ PermissionValue file_exec_permissions;
+ gboolean has_files;
+ gboolean has_folders;
+ gboolean can_set_all_folder_permission;
+ gboolean can_set_all_file_permission;
+ gboolean can_set_any_file_permission;
+ gboolean is_multi_file_window;
+} TargetPermissions;
+
+/* NautilusPermissionEntry - helper struct for permission AdwComboRow */
+
+#define NAUTILUS_TYPE_PERMISSION_ENTRY (nautilus_permission_entry_get_type ())
+G_DECLARE_FINAL_TYPE (NautilusPermissionEntry, nautilus_permission_entry,
+ NAUTILUS, PERMISSION_ENTRY, GObject)
+
+enum
+{
+ PROP_NAME = 1,
+ NUM_PROPERTIES
+};
+
+struct _NautilusPermissionEntry
+{
+ GObject parent;
+
+ char *name;
+ PermissionValue permission_value;
+};
+
+G_DEFINE_TYPE (NautilusPermissionEntry,
+ nautilus_permission_entry,
+ G_TYPE_OBJECT)
+
+static void
+nautilus_permission_entry_init (NautilusPermissionEntry *self)
+{
+ self->name = NULL;
+ self->permission_value = PERMISSION_NONE;
+}
+
+static void
+nautilus_permission_entry_finalize (GObject *object)
+{
+ NautilusPermissionEntry *self = NAUTILUS_PERMISSION_ENTRY (object);
+
+ g_free (self->name);
+
+ G_OBJECT_CLASS (nautilus_permission_entry_parent_class)->finalize (object);
+}
+
+static void
+nautilus_permission_entry_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusPermissionEntry *self = NAUTILUS_PERMISSION_ENTRY (object);
+
+ switch (prop_id)
+ {
+ case PROP_NAME:
+ {
+ g_value_set_string (value, self->name);
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ break;
+ }
+}
+
+static void
+nautilus_permission_entry_class_init (NautilusPermissionEntryClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = nautilus_permission_entry_finalize;
+ gobject_class->get_property = nautilus_permission_entry_get_property;
+
+ g_object_class_install_property (gobject_class,
+ PROP_NAME,
+ g_param_spec_string ("name", "", "",
+ NULL,
+ G_PARAM_READABLE));
+}
+
+static gchar *
+permission_value_to_string (PermissionValue permission_value,
+ gboolean describes_folder)
+{
+ if (permission_value & PERMISSION_INCONSISTENT)
+ {
+ return "---";
+ }
+ else if (permission_value & PERMISSION_READ)
+ {
+ if (permission_value & PERMISSION_WRITE)
+ {
+ if (!describes_folder)
+ {
+ return _("Read and write");
+ }
+ else if (permission_value & PERMISSION_EXEC)
+ {
+ return _("Create and delete files");
+ }
+ else
+ {
+ return _("Read/write, no access");
+ }
+ }
+ else
+ {
+ if (!describes_folder)
+ {
+ return _("Read-only");
+ }
+ else if (permission_value & PERMISSION_EXEC)
+ {
+ return _("Access files");
+ }
+ else
+ {
+ return _("List files only");
+ }
+ }
+ }
+ else
+ {
+ if (permission_value & PERMISSION_WRITE)
+ {
+ if (!describes_folder || permission_value & PERMISSION_EXEC)
+ {
+ return _("Write-only");
+ }
+ else
+ {
+ return _("Write-only, no access");
+ }
+ }
+ else
+ {
+ if (describes_folder && permission_value & PERMISSION_EXEC)
+ {
+ return _("Access-only");
+ }
+ else
+ {
+ /* Translators: this is referred to the permissions the user has in a directory. */
+ return _("None");
+ }
+ }
+ }
+}
+
+/* end NautilusPermissionEntry */
+
+enum
+{
+ COLUMN_NAME,
+ COLUMN_VALUE,
+ COLUMN_USE_ORIGINAL,
+ COLUMN_ID,
+ NUM_COLUMNS
+};
+
+typedef struct
+{
+ GList *original_files;
+ GList *target_files;
+ GtkWidget *parent_widget;
+ GtkWindow *parent_window;
+ char *startup_id;
+ char *pending_key;
+ GHashTable *pending_files;
+ NautilusPropertiesWindowCallback callback;
+ gpointer callback_data;
+ NautilusPropertiesWindow *window;
+ gboolean cancelled;
+} StartupData;
+
+#define DIRECTORY_CONTENTS_UPDATE_INTERVAL 200 /* milliseconds */
+#define FILES_UPDATE_INTERVAL 200 /* milliseconds */
+
+/*
+ * A timeout before changes through the user/group combo box will be applied.
+ * When quickly changing owner/groups (i.e. by keyboard or scroll wheel),
+ * this ensures that the GUI doesn't end up unresponsive.
+ *
+ * Both combos react on changes by scheduling a new change and unscheduling
+ * or cancelling old pending changes.
+ */
+#define CHOWN_CHGRP_TIMEOUT 300 /* milliseconds */
+
+static void schedule_directory_contents_update (NautilusPropertiesWindow *self);
+static void directory_contents_value_field_update (NautilusPropertiesWindow *self);
+static void file_changed_callback (NautilusFile *file,
+ gpointer user_data);
+static void update_execution_row (GtkWidget *row,
+ TargetPermissions *target_perm);
+static void update_permission_row (AdwComboRow *row,
+ TargetPermissions *target_perm);
+static void value_field_update (GtkLabel *field,
+ NautilusPropertiesWindow *self);
+static void properties_window_update (NautilusPropertiesWindow *self,
+ GList *files);
+static void is_directory_ready_callback (NautilusFile *file,
+ gpointer data);
+static void cancel_group_change_callback (GroupChange *change);
+static void cancel_owner_change_callback (OwnerChange *change);
+static void update_owner_row (AdwComboRow *row,
+ TargetPermissions *target_perm);
+static void update_group_row (AdwComboRow *row,
+ TargetPermissions *target_perm);
+static void select_image_button_callback (GtkWidget *widget,
+ NautilusPropertiesWindow *self);
+static void set_icon (const char *icon_path,
+ NautilusPropertiesWindow *self);
+static void remove_pending (StartupData *data,
+ gboolean cancel_call_when_ready,
+ gboolean cancel_timed_wait);
+static void refresh_extension_model_pages (NautilusPropertiesWindow *self);
+static gboolean is_root_directory (NautilusFile *file);
+
+G_DEFINE_TYPE (NautilusPropertiesWindow, nautilus_properties_window, ADW_TYPE_WINDOW);
+
+static gboolean
+is_multi_file_window (NautilusPropertiesWindow *self)
+{
+ GList *l;
+ int count;
+
+ count = 0;
+
+ for (l = self->original_files; l != NULL; l = l->next)
+ {
+ if (!nautilus_file_is_gone (NAUTILUS_FILE (l->data)))
+ {
+ count++;
+ if (count > 1)
+ {
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+static NautilusFile *
+get_original_file (NautilusPropertiesWindow *self)
+{
+ g_return_val_if_fail (!is_multi_file_window (self), NULL);
+
+ if (self->original_files == NULL)
+ {
+ return NULL;
+ }
+
+ return NAUTILUS_FILE (self->original_files->data);
+}
+
+static NautilusFile *
+get_target_file_for_original_file (NautilusFile *file)
+{
+ NautilusFile *target_file;
+ g_autoptr (GFile) location = NULL;
+ g_autofree char *uri_to_display = NULL;
+
+ uri_to_display = nautilus_file_get_uri (file);
+ location = g_file_new_for_uri (uri_to_display);
+ target_file = nautilus_file_get (location);
+
+ return target_file;
+}
+
+static NautilusFile *
+get_target_file (NautilusPropertiesWindow *self)
+{
+ return NAUTILUS_FILE (self->target_files->data);
+}
+
+static void
+navigate_main_page (NautilusPropertiesWindow *self,
+ GParamSpec *params,
+ GtkWidget *widget)
+{
+ gtk_stack_set_visible_child_name (self->page_stack, "main");
+}
+
+static void
+navigate_permissions_page (NautilusPropertiesWindow *self,
+ GParamSpec *params,
+ GtkWidget *widget)
+{
+ gtk_stack_set_visible_child_name (self->page_stack, "permissions");
+}
+
+static void
+navigate_extension_model_page (NautilusPropertiesWindow *self,
+ GParamSpec *params,
+ AdwPreferencesRow *row)
+{
+ gtk_stack_set_visible_child (self->page_stack,
+ g_object_get_data (G_OBJECT (row),
+ "nautilus-extension-properties-page"));
+}
+
+static void
+get_image_for_properties_window (NautilusPropertiesWindow *self,
+ char **icon_name,
+ GdkPaintable **icon_paintable)
+{
+ g_autoptr (NautilusIconInfo) icon = NULL;
+ GList *l;
+ gint icon_scale;
+
+ icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (self));
+
+ for (l = self->original_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+ g_autoptr (NautilusIconInfo) new_icon = NULL;
+
+ file = NAUTILUS_FILE (l->data);
+
+ if (!icon)
+ {
+ icon = nautilus_file_get_icon (file, NAUTILUS_GRID_ICON_SIZE_MEDIUM, icon_scale,
+ NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS |
+ NAUTILUS_FILE_ICON_FLAGS_USE_MOUNT_ICON);
+ }
+ else
+ {
+ new_icon = nautilus_file_get_icon (file, NAUTILUS_GRID_ICON_SIZE_MEDIUM, icon_scale,
+ NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS |
+ NAUTILUS_FILE_ICON_FLAGS_USE_MOUNT_ICON);
+ if (!new_icon || new_icon != icon)
+ {
+ g_object_unref (icon);
+ icon = NULL;
+ break;
+ }
+ }
+ }
+
+ if (!icon)
+ {
+ g_autoptr (GIcon) gicon = g_themed_icon_new ("text-x-generic");
+
+ icon = nautilus_icon_info_lookup (gicon, NAUTILUS_GRID_ICON_SIZE_MEDIUM, icon_scale);
+ }
+
+ if (icon_name != NULL)
+ {
+ *icon_name = g_strdup (nautilus_icon_info_get_used_name (icon));
+ }
+
+ if (icon_paintable != NULL)
+ {
+ *icon_paintable = nautilus_icon_info_get_paintable (icon);
+ }
+}
+
+
+static void
+update_properties_window_icon (NautilusPropertiesWindow *self)
+{
+ g_autoptr (GdkPaintable) paintable = NULL;
+ g_autofree char *name = NULL;
+ gint pixel_size;
+
+ get_image_for_properties_window (self, &name, &paintable);
+
+ if (name != NULL)
+ {
+ gtk_window_set_icon_name (GTK_WINDOW (self), name);
+ }
+
+ pixel_size = MAX (gdk_paintable_get_intrinsic_width (paintable),
+ gdk_paintable_get_intrinsic_width (paintable));
+
+ gtk_image_set_from_paintable (GTK_IMAGE (self->icon_image), paintable);
+ gtk_image_set_from_paintable (GTK_IMAGE (self->icon_button_image), paintable);
+ gtk_image_set_pixel_size (GTK_IMAGE (self->icon_image), pixel_size);
+ gtk_image_set_pixel_size (GTK_IMAGE (self->icon_button_image), pixel_size);
+}
+
+/* utility to test if a uri refers to a local image */
+static gboolean
+uri_is_local_image (const char *uri)
+{
+ g_autoptr (GdkPixbuf) pixbuf = NULL;
+ g_autofree char *image_path = NULL;
+
+ image_path = g_filename_from_uri (uri, NULL, NULL);
+ if (image_path == NULL)
+ {
+ return FALSE;
+ }
+
+ pixbuf = gdk_pixbuf_new_from_file (image_path, NULL);
+
+ if (pixbuf == NULL)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+reset_icon (NautilusPropertiesWindow *self)
+{
+ GList *l;
+
+ for (l = self->original_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (l->data);
+
+ nautilus_file_set_metadata (file,
+ NAUTILUS_METADATA_KEY_CUSTOM_ICON,
+ NULL, NULL);
+ }
+}
+
+static void
+nautilus_properties_window_drag_drop_cb (GtkDropTarget *target,
+ const GValue *value,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ GSList *file_list;
+ gboolean exactly_one;
+ GtkImage *image;
+ GtkWindow *window;
+
+ image = GTK_IMAGE (user_data);
+ window = GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (image)));
+
+ if (!G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
+ {
+ return;
+ }
+
+ file_list = g_value_get_boxed (value);
+ exactly_one = file_list != NULL && g_slist_next (file_list) == NULL;
+
+ if (!exactly_one)
+ {
+ show_dialog (_("You cannot assign more than one custom icon at a time!"),
+ _("Please drop just one image to set a custom icon."),
+ window,
+ GTK_MESSAGE_ERROR);
+ }
+ else
+ {
+ g_autofree gchar *uri = g_file_get_uri (file_list->data);
+
+ if (uri_is_local_image (uri))
+ {
+ set_icon (uri, NAUTILUS_PROPERTIES_WINDOW (window));
+ }
+ else
+ {
+ if (!g_file_is_native (file_list->data))
+ {
+ show_dialog (_("The file that you dropped is not local."),
+ _("You can only use local images as custom icons."),
+ window,
+ GTK_MESSAGE_ERROR);
+ }
+ else
+ {
+ show_dialog (_("The file that you dropped is not an image."),
+ _("You can only use local images as custom icons."),
+ window,
+ GTK_MESSAGE_ERROR);
+ }
+ }
+ }
+}
+
+static void
+star_clicked (NautilusPropertiesWindow *self)
+{
+ NautilusTagManager *tag_manager = nautilus_tag_manager_get ();
+ NautilusFile *file = get_original_file (self);
+ g_autofree gchar *uri = nautilus_file_get_uri (file);
+
+ if (nautilus_tag_manager_file_is_starred (tag_manager, uri))
+ {
+ nautilus_tag_manager_unstar_files (tag_manager, G_OBJECT (self),
+ &(GList){ file, NULL }, NULL, NULL);
+ }
+ else
+ {
+ nautilus_tag_manager_star_files (tag_manager, G_OBJECT (self),
+ &(GList){ file, NULL }, NULL, NULL);
+ }
+}
+
+static void
+update_star (NautilusPropertiesWindow *self,
+ NautilusTagManager *tag_manager)
+{
+ gboolean is_starred;
+ g_autofree gchar *file_uri = NULL;
+
+ file_uri = nautilus_file_get_uri (get_target_file (self));
+ is_starred = nautilus_tag_manager_file_is_starred (tag_manager, file_uri);
+
+ gtk_button_set_icon_name (GTK_BUTTON (self->star_button),
+ is_starred ? "starred-symbolic" : "non-starred-symbolic");
+ /* Translators: This is a verb for tagging or untagging a file with a star. */
+ gtk_widget_set_tooltip_text (self->star_button, is_starred ? _("Unstar") : _("Star"));
+}
+
+static void
+on_starred_changed (NautilusTagManager *tag_manager,
+ GList *changed_files,
+ gpointer user_data)
+{
+ NautilusPropertiesWindow *self = user_data;
+ NautilusFile *file = get_target_file (self);
+
+ if (g_list_find (changed_files, file))
+ {
+ update_star (self, tag_manager);
+ }
+}
+
+static void
+setup_star_button (NautilusPropertiesWindow *self)
+{
+ NautilusTagManager *tag_manager = nautilus_tag_manager_get ();
+ NautilusFile *file = get_target_file (self);
+ g_autoptr (GFile) parent_location = nautilus_file_get_parent_location (file);
+
+ if (parent_location == NULL)
+ {
+ return;
+ }
+
+ if (nautilus_tag_manager_can_star_contents (tag_manager, parent_location))
+ {
+ gtk_widget_show (self->star_button);
+ update_star (self, tag_manager);
+ g_signal_connect_object (tag_manager, "starred-changed",
+ G_CALLBACK (on_starred_changed), self, 0);
+ }
+}
+
+static void
+setup_image_widget (NautilusPropertiesWindow *self,
+ gboolean is_customizable)
+{
+ update_properties_window_icon (self);
+
+ if (is_customizable)
+ {
+ GtkDropTarget *target;
+
+ /* prepare the image to receive dropped objects to assign custom images */
+ target = gtk_drop_target_new (GDK_TYPE_FILE_LIST, GDK_ACTION_COPY);
+ gtk_widget_add_controller (self->icon_button, GTK_EVENT_CONTROLLER (target));
+ g_signal_connect (target, "drop",
+ G_CALLBACK (nautilus_properties_window_drag_drop_cb), self->icon_button_image);
+
+ g_signal_connect (self->icon_button, "clicked",
+ G_CALLBACK (select_image_button_callback), self);
+ gtk_stack_set_visible_child (self->icon_stack, self->icon_button);
+ }
+ else
+ {
+ gtk_stack_set_visible_child (self->icon_stack, self->icon_image);
+ }
+}
+
+static void
+update_name_field (NautilusPropertiesWindow *self)
+{
+ g_autoptr (GString) name_str = g_string_new ("");
+ g_autofree gchar *os_name = NULL;
+ gchar *name_value;
+ guint file_counter = 0;
+
+ for (GList *l = self->target_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file = NAUTILUS_FILE (l->data);
+
+ if (!nautilus_file_is_gone (file))
+ {
+ g_autofree gchar *file_name = NULL;
+
+ file_counter += 1;
+ if (file_counter > 1)
+ {
+ g_string_append (name_str, ", ");
+ }
+
+ file_name = nautilus_file_get_display_name (file);
+ g_string_append (name_str, file_name);
+ }
+ }
+
+ if (!is_multi_file_window (self) && is_root_directory (get_original_file (self)))
+ {
+ os_name = g_get_os_info (G_OS_INFO_KEY_NAME);
+ name_value = (os_name != NULL) ? os_name : _("Operating System");
+ }
+ else
+ {
+ name_value = name_str->str;
+ }
+
+ gtk_label_set_text (self->name_value_label, name_value);
+}
+
+/**
+ * Returns the attribute value if all files in file_list have identical
+ * attributes, "unknown" if no files exist and NULL otherwise.
+ */
+static char *
+file_list_get_string_attribute (GList *file_list,
+ const char *attribute_name)
+{
+ g_autofree char *first_attr = NULL;
+
+ for (GList *l = file_list; l != NULL; l = l->next)
+ {
+ NautilusFile *file = NAUTILUS_FILE (l->data);
+
+ if (nautilus_file_is_gone (file))
+ {
+ continue;
+ }
+
+ if (first_attr == NULL)
+ {
+ first_attr = nautilus_file_get_string_attribute_with_default (file, attribute_name);
+ }
+ else
+ {
+ g_autofree char *attr = NULL;
+ attr = nautilus_file_get_string_attribute_with_default (file, attribute_name);
+ if (!g_str_equal (attr, first_attr))
+ {
+ /* Not all files have the same value for attribute_name. */
+ return NULL;
+ }
+ }
+ }
+
+ if (first_attr != NULL)
+ {
+ return g_steal_pointer (&first_attr);
+ }
+ else
+ {
+ return g_strdup (_("unknown"));
+ }
+}
+
+static GtkWidget *
+create_extension_group_row (NautilusPropertiesItem *item,
+ NautilusPropertiesWindow *self)
+{
+ GtkWidget *row = adw_action_row_new ();
+ GtkWidget *box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3);
+ GtkWidget *name_label = gtk_label_new (NULL);
+ GtkWidget *value_label = gtk_label_new (NULL);
+
+ gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (row), FALSE);
+ gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE);
+ adw_action_row_add_prefix (ADW_ACTION_ROW (row), box);
+
+ gtk_widget_set_margin_top (box, 7);
+ gtk_widget_set_margin_bottom (box, 7);
+ gtk_box_append (GTK_BOX (box), name_label);
+ gtk_box_append (GTK_BOX (box), value_label);
+
+ g_object_bind_property (item, "name", name_label, "label", G_BINDING_SYNC_CREATE);
+ gtk_widget_add_css_class (name_label, "caption");
+ gtk_widget_add_css_class (name_label, "dim-label");
+ gtk_widget_set_halign (name_label, GTK_ALIGN_START);
+ gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
+
+ g_object_bind_property (item, "value", value_label, "label", G_BINDING_SYNC_CREATE);
+ gtk_widget_set_halign (value_label, GTK_ALIGN_START);
+ gtk_label_set_wrap (GTK_LABEL (value_label), TRUE);
+ gtk_label_set_wrap_mode (GTK_LABEL (value_label), PANGO_WRAP_WORD_CHAR);
+ gtk_label_set_selectable (GTK_LABEL (value_label), TRUE);
+
+ return row;
+}
+
+static GtkWidget *
+add_extension_model_page (NautilusPropertiesModel *model,
+ NautilusPropertiesWindow *self)
+{
+ GListModel *list_model = nautilus_properties_model_get_model (model);
+ GtkWidget *row;
+ GtkWidget *title;
+ GtkWidget *header_bar;
+ GtkWidget *list_box;
+ GtkWidget *clamp;
+ GtkWidget *scrolled_window;
+ GtkWidget *up_button;
+ GtkWidget *box;
+
+ row = adw_action_row_new ();
+ g_object_bind_property (model, "title", row, "title", G_BINDING_SYNC_CREATE);
+ gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), TRUE);
+ gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (row), FALSE);
+ adw_action_row_add_suffix (ADW_ACTION_ROW (row),
+ gtk_image_new_from_icon_name ("go-next-symbolic"));
+ g_signal_connect_swapped (row, "activated",
+ G_CALLBACK (navigate_extension_model_page), self);
+
+ title = adw_window_title_new (NULL, NULL);
+ g_object_bind_property (model, "title", title, "title", G_BINDING_SYNC_CREATE);
+
+ up_button = gtk_button_new_from_icon_name ("go-previous-symbolic");
+ g_signal_connect_swapped (up_button, "clicked", G_CALLBACK (navigate_main_page), self);
+
+ header_bar = gtk_header_bar_new ();
+ gtk_header_bar_set_title_widget (GTK_HEADER_BAR (header_bar), title);
+ gtk_header_bar_pack_start (GTK_HEADER_BAR (header_bar), up_button);
+
+ list_box = gtk_list_box_new ();
+ gtk_widget_add_css_class (list_box, "boxed-list");
+ gtk_widget_set_valign (list_box, GTK_ALIGN_START);
+ gtk_list_box_bind_model (GTK_LIST_BOX (list_box), list_model,
+ (GtkListBoxCreateWidgetFunc) create_extension_group_row,
+ self,
+ NULL);
+
+ clamp = adw_clamp_new ();
+ adw_clamp_set_child (ADW_CLAMP (clamp), list_box);
+ gtk_widget_set_margin_top (clamp, 18);
+ gtk_widget_set_margin_bottom (clamp, 18);
+ gtk_widget_set_margin_start (clamp, 18);
+ gtk_widget_set_margin_end (clamp, 18);
+
+ scrolled_window = gtk_scrolled_window_new ();
+ gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled_window), clamp);
+ gtk_widget_set_vexpand (scrolled_window, TRUE);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_box_append (GTK_BOX (box), header_bar);
+ gtk_box_append (GTK_BOX (box), scrolled_window);
+ gtk_widget_add_css_class (scrolled_window, "background");
+
+ gtk_stack_add_named (self->page_stack,
+ box,
+ NULL);
+
+ g_object_set_data (G_OBJECT (row), "nautilus-extension-properties-page", box);
+
+ return row;
+}
+
+static void
+remove_from_dialog (NautilusPropertiesWindow *self,
+ NautilusFile *file)
+{
+ int index;
+ GList *original_link;
+ GList *target_link;
+ g_autoptr (NautilusFile) original_file = NULL;
+ g_autoptr (NautilusFile) target_file = NULL;
+
+ index = g_list_index (self->target_files, file);
+ if (index == -1)
+ {
+ index = g_list_index (self->original_files, file);
+ g_return_if_fail (index != -1);
+ }
+
+ original_link = g_list_nth (self->original_files, index);
+ target_link = g_list_nth (self->target_files, index);
+
+ g_return_if_fail (original_link && target_link);
+
+ original_file = NAUTILUS_FILE (original_link->data);
+ target_file = NAUTILUS_FILE (target_link->data);
+
+ self->original_files = g_list_delete_link (self->original_files, original_link);
+ self->target_files = g_list_delete_link (self->target_files, target_link);
+
+ g_hash_table_remove (self->initial_permissions, target_file);
+
+ g_signal_handlers_disconnect_by_func (original_file,
+ G_CALLBACK (file_changed_callback),
+ self);
+ g_signal_handlers_disconnect_by_func (target_file,
+ G_CALLBACK (file_changed_callback),
+ self);
+
+ nautilus_file_monitor_remove (original_file, &self->original_files);
+ nautilus_file_monitor_remove (target_file, &self->target_files);
+}
+
+static gboolean
+mime_list_equal (GList *a,
+ GList *b)
+{
+ while (a && b)
+ {
+ if (strcmp (a->data, b->data))
+ {
+ return FALSE;
+ }
+ a = a->next;
+ b = b->next;
+ }
+
+ return (a == b);
+}
+
+static GList *
+get_mime_list (NautilusPropertiesWindow *self)
+{
+ return g_list_copy_deep (self->target_files,
+ (GCopyFunc) nautilus_file_get_mime_type,
+ NULL);
+}
+
+static gboolean
+start_spinner_callback (NautilusPropertiesWindow *self)
+{
+ gtk_widget_show (self->contents_spinner);
+ gtk_spinner_start (GTK_SPINNER (self->contents_spinner));
+ self->deep_count_spinner_timeout_id = 0;
+
+ return FALSE;
+}
+
+static void
+schedule_start_spinner (NautilusPropertiesWindow *self)
+{
+ if (self->deep_count_spinner_timeout_id == 0)
+ {
+ self->deep_count_spinner_timeout_id
+ = g_timeout_add_seconds (1,
+ (GSourceFunc) start_spinner_callback,
+ self);
+ }
+}
+
+static void
+stop_spinner (NautilusPropertiesWindow *self)
+{
+ gtk_spinner_stop (GTK_SPINNER (self->contents_spinner));
+ gtk_widget_hide (self->contents_spinner);
+ g_clear_handle_id (&self->deep_count_spinner_timeout_id, g_source_remove);
+}
+
+static void
+stop_deep_count_for_file (NautilusPropertiesWindow *self,
+ NautilusFile *file)
+{
+ if (g_list_find (self->deep_count_files, file))
+ {
+ g_signal_handlers_disconnect_by_func (file,
+ G_CALLBACK (schedule_directory_contents_update),
+ self);
+ nautilus_file_unref (file);
+ self->deep_count_files = g_list_remove (self->deep_count_files, file);
+ }
+}
+
+static void
+start_deep_count_for_file (NautilusFile *file,
+ NautilusPropertiesWindow *self)
+{
+ if (!nautilus_file_is_directory (file))
+ {
+ return;
+ }
+
+ if (!g_list_find (self->deep_count_files, file))
+ {
+ nautilus_file_ref (file);
+ self->deep_count_files = g_list_prepend (self->deep_count_files, file);
+
+ nautilus_file_recompute_deep_counts (file);
+ if (!self->deep_count_finished)
+ {
+ g_signal_connect_object (file,
+ "updated-deep-count-in-progress",
+ G_CALLBACK (schedule_directory_contents_update),
+ self, G_CONNECT_SWAPPED);
+ schedule_start_spinner (self);
+ }
+ }
+}
+
+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 = PERMISSION_NONE;
+
+ g_assert (type >= 0 && type < 3);
+
+ 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 PermissionValue
+exec_permission_from_vfs (guint32 vfs_perm)
+{
+ guint32 perm_user = vfs_perm & UNIX_PERM_USER_EXEC;
+ guint32 perm_group = vfs_perm & UNIX_PERM_GROUP_EXEC;
+ guint32 perm_other = vfs_perm & UNIX_PERM_OTHER_EXEC;
+
+ if (perm_user && perm_group && perm_other)
+ {
+ return PERMISSION_EXEC;
+ }
+ else if (perm_user || perm_group || perm_other)
+ {
+ return PERMISSION_INCONSISTENT;
+ }
+ else
+ {
+ return PERMISSION_NONE;
+ }
+}
+
+static TargetPermissions *
+get_target_permissions (NautilusPropertiesWindow *self)
+{
+ TargetPermissions *p = g_new0 (TargetPermissions, 1);
+ p->window = self;
+ p->can_set_all_folder_permission = TRUE;
+ p->can_set_all_file_permission = TRUE;
+
+ for (GList *entry = self->target_files; entry != NULL; entry = entry->next)
+ {
+ guint32 vfs_permissions;
+ gboolean can_set_permissions;
+ NautilusFile *file = NAUTILUS_FILE (entry->data);
+
+ if (nautilus_file_is_gone (file) || !nautilus_file_can_get_permissions (file))
+ {
+ continue;
+ }
+
+ vfs_permissions = nautilus_file_get_permissions (file);
+ can_set_permissions = nautilus_file_can_set_permissions (file);
+
+ if (nautilus_file_is_directory (file))
+ {
+ /* Gather permissions for each type (owner, group, other) */
+ for (PermissionType type = PERMISSION_USER; type < NUM_PERMISSION_TYPE; type += 1)
+ {
+ PermissionValue permissions = permission_from_vfs (type, vfs_permissions)
+ & (PERMISSION_READ | PERMISSION_WRITE | PERMISSION_EXEC);
+ if (!p->has_folders)
+ {
+ /* first found folder, initialize with its permissions */
+ p->folder_permissions[type] = permissions;
+ }
+ else if (permissions != p->folder_permissions[type])
+ {
+ p->folder_permissions[type] = PERMISSION_INCONSISTENT;
+ }
+ }
+
+ p->can_set_all_folder_permission &= can_set_permissions;
+ p->has_folders = TRUE;
+ }
+ else
+ {
+ PermissionValue exec_permissions = exec_permission_from_vfs (vfs_permissions);
+
+ if (!p->has_files)
+ {
+ p->file_exec_permissions = exec_permissions;
+ }
+ else if (exec_permissions != p->file_exec_permissions)
+ {
+ p->file_exec_permissions = PERMISSION_INCONSISTENT;
+ }
+ for (PermissionType type = PERMISSION_USER ; type < NUM_PERMISSION_TYPE; type += 1)
+ {
+ PermissionValue permissions = permission_from_vfs (type, vfs_permissions)
+ & (PERMISSION_READ | PERMISSION_WRITE);
+
+ if (!p->has_files)
+ {
+ /* first found file, initialize with its permissions */
+ p->file_permissions[type] = permissions;
+ }
+ else if (permissions != p->file_permissions[type])
+ {
+ p->file_permissions[type] = PERMISSION_INCONSISTENT;
+ }
+ }
+
+ p->can_set_all_file_permission &= can_set_permissions;
+ p->can_set_any_file_permission |= can_set_permissions;
+ p->has_files = TRUE;
+ }
+ }
+
+ p->is_multi_file_window = is_multi_file_window (self);
+
+ return p;
+}
+
+static void
+update_permissions_navigation_row (NautilusPropertiesWindow *self,
+ TargetPermissions *target_perm)
+{
+ if (!target_perm->is_multi_file_window)
+ {
+ uid_t user_id = geteuid ();
+ gid_t group_id = getegid ();
+ PermissionType permission_type = PERMISSION_OTHER;
+ const gchar *text;
+
+ if (user_id == nautilus_file_get_uid (get_original_file (self)))
+ {
+ permission_type = PERMISSION_USER;
+ }
+ else if (group_id == nautilus_file_get_gid (get_original_file (self)))
+ {
+ permission_type = PERMISSION_GROUP;
+ }
+
+ if (nautilus_file_is_directory (get_original_file (self)))
+ {
+ text = permission_value_to_string (target_perm->folder_permissions[permission_type], TRUE);
+ }
+ else
+ {
+ text = permission_value_to_string (target_perm->file_permissions[permission_type], FALSE);
+ }
+
+ gtk_label_set_text (GTK_LABEL (self->permissions_value_label), text);
+ }
+}
+
+static void
+properties_window_update (NautilusPropertiesWindow *self,
+ GList *files)
+{
+ GList *mime_list;
+ NautilusFile *changed_file;
+ gboolean dirty_original = FALSE;
+ gboolean dirty_target = FALSE;
+
+ if (files == NULL)
+ {
+ dirty_original = TRUE;
+ dirty_target = TRUE;
+ }
+
+ for (GList *tmp = files; tmp != NULL; tmp = tmp->next)
+ {
+ changed_file = NAUTILUS_FILE (tmp->data);
+
+ if (changed_file && nautilus_file_is_gone (changed_file))
+ {
+ /* Remove the file from the property dialog */
+ remove_from_dialog (self, changed_file);
+ changed_file = NULL;
+
+ if (self->original_files == NULL)
+ {
+ return;
+ }
+ }
+ if (changed_file == NULL ||
+ g_list_find (self->original_files, changed_file))
+ {
+ dirty_original = TRUE;
+ }
+ if (changed_file == NULL ||
+ g_list_find (self->target_files, changed_file))
+ {
+ dirty_target = TRUE;
+ }
+ }
+
+ if (dirty_original)
+ {
+ update_properties_window_icon (self);
+ update_name_field (self);
+
+ /* If any of the value fields start to depend on the original
+ * value, value_field_updates should be added here */
+ }
+
+ if (dirty_target)
+ {
+ g_autofree TargetPermissions *target_perm = get_target_permissions (self);
+
+ update_permissions_navigation_row (self, target_perm);
+ update_owner_row (self->owner_row, target_perm);
+ update_group_row (self->group_row, target_perm);
+ update_execution_row (GTK_WIDGET (self->execution_row), target_perm);
+ g_list_foreach (self->permission_rows,
+ (GFunc) update_permission_row,
+ target_perm);
+ g_list_foreach (self->value_fields,
+ (GFunc) value_field_update,
+ self);
+ }
+
+ mime_list = get_mime_list (self);
+
+ if (self->mime_list == NULL)
+ {
+ self->mime_list = mime_list;
+ }
+ else
+ {
+ if (!mime_list_equal (self->mime_list, mime_list))
+ {
+ refresh_extension_model_pages (self);
+ }
+
+ g_list_free_full (self->mime_list, g_free);
+ self->mime_list = mime_list;
+ }
+}
+
+static gboolean
+update_files_callback (gpointer data)
+{
+ NautilusPropertiesWindow *self;
+
+ self = NAUTILUS_PROPERTIES_WINDOW (data);
+
+ self->update_files_timeout_id = 0;
+
+ properties_window_update (self, self->changed_files);
+
+ if (self->original_files == NULL)
+ {
+ /* Close the window if no files are left */
+ gtk_window_destroy (GTK_WINDOW (self));
+ }
+ else
+ {
+ nautilus_file_list_free (self->changed_files);
+ self->changed_files = NULL;
+ }
+
+ return FALSE;
+}
+
+static void
+schedule_files_update (NautilusPropertiesWindow *self)
+{
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ if (self->update_files_timeout_id == 0)
+ {
+ self->update_files_timeout_id
+ = g_timeout_add (FILES_UPDATE_INTERVAL,
+ update_files_callback,
+ self);
+ }
+}
+
+static gboolean
+location_show_original (NautilusPropertiesWindow *self)
+{
+ NautilusFile *file;
+
+ /* there is no way a recent item will be mixed with
+ * other items so just pick the first file to check */
+ file = NAUTILUS_FILE (g_list_nth_data (self->original_files, 0));
+ return (file != NULL && !nautilus_file_is_in_recent (file));
+}
+
+static void
+value_field_update (GtkLabel *label,
+ NautilusPropertiesWindow *self)
+{
+ GList *file_list;
+ const char *attribute_name;
+ g_autofree char *attribute_value = NULL;
+ gboolean is_where;
+
+ g_assert (GTK_IS_LABEL (label));
+
+ attribute_name = g_object_get_data (G_OBJECT (label), "file_attribute");
+
+ is_where = (g_strcmp0 (attribute_name, "where") == 0);
+ if (is_where && location_show_original (self))
+ {
+ file_list = self->original_files;
+ }
+ else
+ {
+ file_list = self->target_files;
+ }
+
+ attribute_value = file_list_get_string_attribute (file_list,
+ attribute_name);
+ if (g_str_equal (attribute_name, "detailed_type"))
+ {
+ g_autofree char *mime_type = NULL;
+ gchar *cap_label;
+
+ mime_type = file_list_get_string_attribute (file_list, "mime_type");
+ gtk_widget_set_tooltip_text (GTK_WIDGET (label), mime_type);
+
+ cap_label = eel_str_capitalize (attribute_value);
+ if (cap_label != NULL)
+ {
+ g_free (attribute_value);
+
+ attribute_value = cap_label;
+ }
+ }
+ else if (g_str_equal (attribute_name, "size"))
+ {
+ g_autofree char *size_detail = NULL;
+
+ size_detail = file_list_get_string_attribute (file_list, "size_detail");
+
+ gtk_widget_set_tooltip_text (GTK_WIDGET (label), size_detail);
+ }
+
+ gtk_label_set_text (label, attribute_value);
+}
+
+static guint
+hash_string_list (GList *list)
+{
+ guint hash_value = 0;
+
+ for (GList *node = list; node != NULL; node = node->next)
+ {
+ hash_value ^= g_str_hash ((gconstpointer) node->data);
+ }
+
+ return hash_value;
+}
+
+static gsize
+get_first_word_length (const gchar *str)
+{
+ const gchar *space_pos = g_strstr_len (str, -1, " ");
+ return space_pos ? space_pos - str : strlen (str);
+}
+
+static void
+update_combo_row_dropdown (AdwComboRow *row,
+ GList *entries)
+{
+ /* check if dropdown already exist and is up to date by comparing with stored hash. */
+ guint current_hash = hash_string_list (entries);
+ guint stored_hash = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (row), "dropdown-hash"));
+
+ if (stored_hash != current_hash)
+ {
+ /* Recreate the drop down. */
+ g_autoptr (GtkStringList) new_model = gtk_string_list_new (NULL);
+
+ for (GList *node = entries; node != NULL; node = node->next)
+ {
+ const char *entry = (const char *) node->data;
+ gtk_string_list_append (new_model, entry);
+ }
+
+ adw_combo_row_set_model (row, G_LIST_MODEL (new_model));
+
+ g_object_set_data (G_OBJECT (row), "dropdown-hash", GUINT_TO_POINTER (current_hash));
+ }
+}
+
+typedef gboolean CompareOwnershipRowFunc (GListModel *list,
+ guint position,
+ const char *str);
+
+static void
+select_ownership_row_entry (AdwComboRow *row,
+ const char *entry,
+ CompareOwnershipRowFunc compare_func)
+{
+ GListModel *list = adw_combo_row_get_model (row);
+ guint index_to_select = GTK_INVALID_LIST_POSITION;
+ guint n_entries;
+
+ /* check if entry is already selected */
+ gint selected_pos = adw_combo_row_get_selected (row);
+
+ if (selected_pos >= 0
+ && compare_func (list, selected_pos, entry))
+ {
+ /* entry already selected */
+ return;
+ }
+
+ /* check if entry exists in model list */
+ n_entries = g_list_model_get_n_items (list);
+
+ for (guint position = 0; position < n_entries; position += 1)
+ {
+ if (compare_func (list, position, entry))
+ {
+ /* found entry in list, select it */
+ index_to_select = position;
+ break;
+ }
+ }
+
+ if (index_to_select == GTK_INVALID_LIST_POSITION)
+ {
+ /* entry not in list, add */
+ gtk_string_list_append (GTK_STRING_LIST (list), entry);
+ index_to_select = n_entries;
+ }
+
+ adw_combo_row_set_selected (row, index_to_select);
+}
+
+static void
+ownership_row_set_single_entry (AdwComboRow *row,
+ const char *entry,
+ CompareOwnershipRowFunc compare_func)
+{
+ GListModel *list = adw_combo_row_get_model (row);
+ gint selected_pos = adw_combo_row_get_selected (row);
+
+ /* check entry not already displayed */
+ if (selected_pos < 0
+ || g_list_model_get_n_items (list) > 1
+ || !compare_func (list, selected_pos, entry))
+ {
+ g_autoptr (GtkStringList) new_model = gtk_string_list_new (NULL);
+
+ /* set current entry as only entry */
+ gtk_string_list_append (new_model, entry);
+
+ adw_combo_row_set_model (row, G_LIST_MODEL (new_model));
+ adw_combo_row_set_selected (row, 0);
+ }
+}
+
+static void
+group_change_free (GroupChange *change)
+{
+ nautilus_file_unref (change->file);
+ g_free (change->group);
+ g_object_unref (change->window);
+
+ g_free (change);
+}
+
+static void
+group_change_callback (NautilusFile *file,
+ GFile *res_loc,
+ GError *error,
+ GroupChange *change)
+{
+ NautilusPropertiesWindow *self;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window));
+ g_assert (NAUTILUS_IS_FILE (change->file));
+ g_assert (change->group != NULL);
+
+ if (!change->cancelled)
+ {
+ /* Report the error if it's an error. */
+ eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, change);
+ nautilus_report_error_setting_group (change->file, error, change->window);
+ }
+
+ self = NAUTILUS_PROPERTIES_WINDOW (change->window);
+ if (self->group_change == change)
+ {
+ self->group_change = NULL;
+ }
+
+ group_change_free (change);
+}
+
+static void
+cancel_group_change_callback (GroupChange *change)
+{
+ g_assert (NAUTILUS_IS_FILE (change->file));
+ g_assert (change->group != NULL);
+
+ change->cancelled = TRUE;
+ nautilus_file_cancel (change->file, (NautilusFileOperationCallback) group_change_callback, change);
+}
+
+static gboolean
+schedule_group_change_timeout (GroupChange *change)
+{
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window));
+ g_assert (NAUTILUS_IS_FILE (change->file));
+ g_assert (change->group != NULL);
+
+ change->timeout = 0;
+
+ eel_timed_wait_start
+ ((EelCancelCallback) cancel_group_change_callback,
+ change,
+ _("Cancel Group Change?"),
+ change->window);
+
+ nautilus_file_set_group
+ (change->file, change->group,
+ (NautilusFileOperationCallback) group_change_callback, change);
+
+ return FALSE;
+}
+
+static void
+schedule_group_change (NautilusPropertiesWindow *self,
+ NautilusFile *file,
+ const char *group)
+{
+ GroupChange *change;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+ g_assert (self->group_change == NULL);
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ change = g_new0 (GroupChange, 1);
+
+ change->file = nautilus_file_ref (file);
+ change->group = g_strdup (group);
+ change->window = GTK_WINDOW (g_object_ref (self));
+ change->timeout =
+ g_timeout_add (CHOWN_CHGRP_TIMEOUT,
+ (GSourceFunc) schedule_group_change_timeout,
+ change);
+
+ self->group_change = change;
+}
+
+static void
+unschedule_or_cancel_group_change (NautilusPropertiesWindow *self)
+{
+ GroupChange *change;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ change = self->group_change;
+
+ if (change != NULL)
+ {
+ if (change->timeout == 0)
+ {
+ /* The operation was started, cancel it and let the operation callback free the change */
+ cancel_group_change_callback (change);
+ eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, change);
+ }
+ else
+ {
+ g_source_remove (change->timeout);
+ group_change_free (change);
+ }
+
+ self->group_change = NULL;
+ }
+}
+
+/** Apply group owner change on user selection. */
+static void
+changed_group_callback (AdwComboRow *row,
+ GParamSpec *pspec,
+ NautilusPropertiesWindow *self)
+{
+ guint selected_pos = adw_combo_row_get_selected (row);
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ if (selected_pos >= 0)
+ {
+ NautilusFile *file = get_target_file (self);
+ GListModel *list = adw_combo_row_get_model (row);
+ const gchar *new_group_name = gtk_string_list_get_string (GTK_STRING_LIST (list), selected_pos);
+ g_autofree char *current_group_name = nautilus_file_get_group_name (file);
+
+ g_assert (new_group_name);
+ g_assert (current_group_name);
+
+ if (strcmp (new_group_name, current_group_name) != 0)
+ {
+ /* Try to change file group. If this fails, complain to user. */
+ unschedule_or_cancel_group_change (self);
+ schedule_group_change (self, file, new_group_name);
+ }
+ }
+}
+
+static void
+owner_change_free (OwnerChange *change)
+{
+ nautilus_file_unref (change->file);
+ g_free (change->owner);
+ g_object_unref (change->window);
+
+ g_free (change);
+}
+
+static void
+owner_change_callback (NautilusFile *file,
+ GFile *result_location,
+ GError *error,
+ OwnerChange *change)
+{
+ NautilusPropertiesWindow *self;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window));
+ g_assert (NAUTILUS_IS_FILE (change->file));
+ g_assert (change->owner != NULL);
+
+ if (!change->cancelled)
+ {
+ /* Report the error if it's an error. */
+ eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, change);
+ nautilus_report_error_setting_owner (file, error, change->window);
+ }
+
+ self = NAUTILUS_PROPERTIES_WINDOW (change->window);
+ if (self->owner_change == change)
+ {
+ self->owner_change = NULL;
+ }
+
+ owner_change_free (change);
+}
+
+static void
+cancel_owner_change_callback (OwnerChange *change)
+{
+ g_assert (NAUTILUS_IS_FILE (change->file));
+ g_assert (change->owner != NULL);
+
+ change->cancelled = TRUE;
+ nautilus_file_cancel (change->file, (NautilusFileOperationCallback) owner_change_callback, change);
+}
+
+static gboolean
+schedule_owner_change_timeout (OwnerChange *change)
+{
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window));
+ g_assert (NAUTILUS_IS_FILE (change->file));
+ g_assert (change->owner != NULL);
+
+ change->timeout = 0;
+
+ eel_timed_wait_start
+ ((EelCancelCallback) cancel_owner_change_callback,
+ change,
+ _("Cancel Owner Change?"),
+ change->window);
+
+ nautilus_file_set_owner
+ (change->file, change->owner,
+ (NautilusFileOperationCallback) owner_change_callback, change);
+
+ return FALSE;
+}
+
+static void
+schedule_owner_change (NautilusPropertiesWindow *self,
+ NautilusFile *file,
+ const char *owner)
+{
+ OwnerChange *change;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+ g_assert (self->owner_change == NULL);
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ change = g_new0 (OwnerChange, 1);
+
+ change->file = nautilus_file_ref (file);
+ change->owner = g_strdup (owner);
+ change->window = GTK_WINDOW (g_object_ref (self));
+ change->timeout =
+ g_timeout_add (CHOWN_CHGRP_TIMEOUT,
+ (GSourceFunc) schedule_owner_change_timeout,
+ change);
+
+ self->owner_change = change;
+}
+
+static void
+unschedule_or_cancel_owner_change (NautilusPropertiesWindow *self)
+{
+ OwnerChange *change;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ change = self->owner_change;
+
+ if (change != NULL)
+ {
+ g_assert (NAUTILUS_IS_FILE (change->file));
+
+ if (change->timeout == 0)
+ {
+ /* The operation was started, cancel it and let the operation callback free the change */
+ cancel_owner_change_callback (change);
+ eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, change);
+ }
+ else
+ {
+ g_source_remove (change->timeout);
+ owner_change_free (change);
+ }
+
+ self->owner_change = NULL;
+ }
+}
+
+static void
+changed_owner_callback (AdwComboRow *row,
+ GParamSpec *pspec,
+ NautilusPropertiesWindow *self)
+{
+ guint selected_pos = adw_combo_row_get_selected (row);
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ if (selected_pos >= 0)
+ {
+ NautilusFile *file = get_target_file (self);
+
+ GListModel *list = adw_combo_row_get_model (row);
+ const gchar *selected_owner_str = gtk_string_list_get_string (GTK_STRING_LIST (list), selected_pos);
+ gsize owner_name_length = get_first_word_length (selected_owner_str);
+ g_autofree gchar *new_owner_name = g_strndup (selected_owner_str, owner_name_length);
+ g_autofree char *current_owner_name = nautilus_file_get_owner_name (file);
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ if (strcmp (new_owner_name, current_owner_name) != 0)
+ {
+ /* Try to change file owner. If this fails, complain to user. */
+ unschedule_or_cancel_owner_change (self);
+ schedule_owner_change (self, file, new_owner_name);
+ }
+ }
+}
+
+static gboolean
+string_list_item_starts_with_word (GListModel *list,
+ guint position,
+ const char *word)
+{
+ const gchar *entry_str = gtk_string_list_get_string (GTK_STRING_LIST (list), position);
+
+ return g_str_has_prefix (entry_str, word)
+ && strlen (word) == get_first_word_length (entry_str);
+}
+
+static gboolean
+string_list_item_equals_string (GListModel *list,
+ guint position,
+ const char *string)
+{
+ const gchar *entry_str = gtk_string_list_get_string (GTK_STRING_LIST (list), position);
+
+ return strcmp (entry_str, string) == 0;
+}
+
+/* Select correct owner if file permissions have changed. */
+static void
+update_owner_row (AdwComboRow *row,
+ TargetPermissions *target_perm)
+{
+ NautilusPropertiesWindow *self = target_perm->window;
+ gboolean provide_dropdown = (!target_perm->is_multi_file_window
+ && nautilus_file_can_set_owner (get_target_file (self)));
+ gboolean had_dropdown = gtk_widget_is_sensitive (GTK_WIDGET (row));
+
+ gtk_widget_set_sensitive (GTK_WIDGET (row), provide_dropdown);
+
+ /* check if should provide dropdown */
+ if (provide_dropdown)
+ {
+ NautilusFile *file = get_target_file (self);
+ g_autofree char *owner_name = nautilus_file_get_owner_name (file);
+ GList *users = nautilus_get_user_names ();
+
+ update_combo_row_dropdown (row, users);
+
+ /* display current owner */
+ select_ownership_row_entry (row, owner_name, string_list_item_starts_with_word);
+
+ if (!had_dropdown)
+ {
+ /* Update file when selection changes. */
+ g_signal_connect (row, "notify::selected",
+ G_CALLBACK (changed_owner_callback),
+ self);
+ }
+ }
+ else
+ {
+ g_autofree char *owner_name = file_list_get_string_attribute (self->target_files,
+ "owner");
+ if (owner_name == NULL)
+ {
+ owner_name = g_strdup (_("Multiple"));
+ }
+
+ g_signal_handlers_disconnect_by_func (row, G_CALLBACK (changed_owner_callback), self);
+
+ ownership_row_set_single_entry (row, owner_name, string_list_item_starts_with_word);
+ }
+}
+
+/* Select correct group if file permissions have changed. */
+static void
+update_group_row (AdwComboRow *row,
+ TargetPermissions *target_perm)
+{
+ NautilusPropertiesWindow *self = target_perm->window;
+ gboolean provide_dropdown = (!target_perm->is_multi_file_window
+ && nautilus_file_can_set_group (get_target_file (self)));
+ gboolean had_dropdown = gtk_widget_is_sensitive (GTK_WIDGET (row));
+
+ gtk_widget_set_sensitive (GTK_WIDGET (row), provide_dropdown);
+
+ if (provide_dropdown)
+ {
+ NautilusFile *file = get_target_file (self);
+ g_autofree char *group_name = nautilus_file_get_group_name (file);
+ GList *groups = nautilus_file_get_settable_group_names (file);
+ update_combo_row_dropdown (row, groups);
+
+ /* display current group */
+ select_ownership_row_entry (row, group_name, string_list_item_equals_string);
+
+ if (!had_dropdown)
+ {
+ /* Update file when selection changes. */
+ g_signal_connect (row, "notify::selected",
+ G_CALLBACK (changed_group_callback),
+ self);
+ }
+ }
+ else
+ {
+ g_autofree char *group_name = file_list_get_string_attribute (self->target_files,
+ "group");
+ if (group_name == NULL)
+ {
+ group_name = g_strdup (_("Multiple"));
+ }
+
+ g_signal_handlers_disconnect_by_func (row, G_CALLBACK (changed_group_callback), self);
+
+ ownership_row_set_single_entry (row, group_name, string_list_item_equals_string);
+ }
+}
+
+static void
+setup_ownership_row (NautilusPropertiesWindow *self,
+ AdwComboRow *row)
+{
+ adw_combo_row_set_model (row, G_LIST_MODEL (gtk_string_list_new (NULL)));
+
+ /* Intial setup of list model is handled via update function, called via properties_window_update. */
+}
+
+static gboolean
+file_has_prefix (NautilusFile *file,
+ GList *prefix_candidates)
+{
+ GList *p;
+ g_autoptr (GFile) location = NULL;
+
+ location = nautilus_file_get_location (file);
+
+ for (p = prefix_candidates; p != NULL; p = p->next)
+ {
+ g_autoptr (GFile) candidate_location = NULL;
+
+ if (file == p->data)
+ {
+ continue;
+ }
+
+ candidate_location = nautilus_file_get_location (NAUTILUS_FILE (p->data));
+ if (g_file_has_prefix (location, candidate_location))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+directory_contents_value_field_update (NautilusPropertiesWindow *self)
+{
+ NautilusRequestStatus file_status;
+ g_autofree char *text = NULL;
+ guint directory_count;
+ guint file_count;
+ guint total_count;
+ guint unreadable_directory_count;
+ goffset total_size;
+ NautilusFile *file;
+ GList *l;
+ guint file_unreadable;
+ goffset file_size;
+ gboolean deep_count_active;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ total_count = 0;
+ total_size = 0;
+ unreadable_directory_count = FALSE;
+
+ for (l = self->target_files; l; l = l->next)
+ {
+ file = NAUTILUS_FILE (l->data);
+
+ if (file_has_prefix (file, self->target_files))
+ {
+ /* don't count nested files twice */
+ continue;
+ }
+
+ if (nautilus_file_is_directory (file))
+ {
+ file_status = nautilus_file_get_deep_counts (file,
+ &directory_count,
+ &file_count,
+ &file_unreadable,
+ &file_size,
+ TRUE);
+ total_count += (file_count + directory_count);
+ total_size += file_size;
+
+ if (file_unreadable)
+ {
+ unreadable_directory_count = TRUE;
+ }
+
+ if (file_status == NAUTILUS_REQUEST_DONE)
+ {
+ stop_deep_count_for_file (self, file);
+ }
+ }
+ else
+ {
+ ++total_count;
+ total_size += nautilus_file_get_size (file);
+ }
+ }
+
+ deep_count_active = (self->deep_count_files != NULL);
+ /* If we've already displayed the total once, don't do another visible
+ * count-up if the deep_count happens to get invalidated.
+ * But still display the new total, since it might have changed.
+ */
+ if (self->deep_count_finished && deep_count_active)
+ {
+ return;
+ }
+
+ text = NULL;
+
+ if (total_count == 0)
+ {
+ if (!deep_count_active)
+ {
+ if (unreadable_directory_count == 0)
+ {
+ text = g_strdup (_("Empty folder"));
+ }
+ else
+ {
+ text = g_strdup (_("Contents unreadable"));
+ }
+ }
+ else
+ {
+ text = g_strdup ("…");
+ }
+ }
+ else
+ {
+ g_autofree char *size_str = NULL;
+ size_str = g_format_size (total_size);
+ text = g_strdup_printf (ngettext ("%'d item, with size %s",
+ "%'d items, totalling %s",
+ total_count),
+ total_count, size_str);
+
+ if (unreadable_directory_count != 0)
+ {
+ g_autofree char *temp = g_steal_pointer (&text);
+
+ text = g_strconcat (temp, "\n",
+ _("(some contents unreadable)"),
+ NULL);
+ }
+ }
+
+ gtk_label_set_text (GTK_LABEL (self->contents_value_label),
+ text);
+
+ if (!deep_count_active)
+ {
+ self->deep_count_finished = TRUE;
+ stop_spinner (self);
+ }
+}
+
+static gboolean
+update_directory_contents_callback (gpointer data)
+{
+ NautilusPropertiesWindow *self;
+
+ self = NAUTILUS_PROPERTIES_WINDOW (data);
+
+ self->update_directory_contents_timeout_id = 0;
+ directory_contents_value_field_update (self);
+
+ return FALSE;
+}
+
+static void
+schedule_directory_contents_update (NautilusPropertiesWindow *self)
+{
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ if (self->update_directory_contents_timeout_id == 0)
+ {
+ self->update_directory_contents_timeout_id
+ = g_timeout_add (DIRECTORY_CONTENTS_UPDATE_INTERVAL,
+ update_directory_contents_callback,
+ self);
+ }
+}
+
+static void
+setup_contents_field (NautilusPropertiesWindow *self)
+{
+ g_list_foreach (self->target_files,
+ (GFunc) start_deep_count_for_file,
+ self);
+
+ /* Fill in the initial value. */
+ directory_contents_value_field_update (self);
+}
+
+static gboolean
+is_root_directory (NautilusFile *file)
+{
+ g_autoptr (GFile) location = NULL;
+ gboolean result;
+
+ location = nautilus_file_get_location (file);
+ result = nautilus_is_root_directory (location);
+
+ return result;
+}
+
+static gboolean
+is_network_directory (NautilusFile *file)
+{
+ g_autofree char *file_uri = NULL;
+
+ file_uri = nautilus_file_get_uri (file);
+
+ return strcmp (file_uri, "network:///") == 0;
+}
+
+static gboolean
+is_burn_directory (NautilusFile *file)
+{
+ g_autofree char *file_uri = NULL;
+
+ file_uri = nautilus_file_get_uri (file);
+
+ return strcmp (file_uri, "burn:///") == 0;
+}
+
+
+static gboolean
+is_volume_properties (NautilusPropertiesWindow *self)
+{
+ NautilusFile *file;
+ gboolean success = FALSE;
+
+ if (is_multi_file_window (self))
+ {
+ return FALSE;
+ }
+
+ file = get_original_file (self);
+
+ if (file == NULL)
+ {
+ return FALSE;
+ }
+
+ if (is_root_directory (file) && nautilus_application_is_sandboxed ())
+ {
+ return FALSE;
+ }
+
+ if (nautilus_file_can_unmount (file))
+ {
+ return TRUE;
+ }
+
+ success = is_root_directory (file);
+
+#ifdef TODO_GIO
+ /* Look at is_mountpoint for activation uri */
+#endif
+
+ return success;
+}
+
+static gboolean
+should_show_custom_icon_buttons (NautilusPropertiesWindow *self)
+{
+ if (is_multi_file_window (self) || is_volume_properties (self) ||
+ is_root_directory (get_original_file (self)))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+is_single_file_type (NautilusPropertiesWindow *self)
+{
+ if (is_multi_file_window (self))
+ {
+ g_autofree gchar *mime_type = NULL;
+ GList *l = self->original_files;
+
+ mime_type = nautilus_file_get_mime_type (NAUTILUS_FILE (l->data));
+ for (l = l->next; l != NULL; l = l->next)
+ {
+ g_autofree gchar *next_mime_type = NULL;
+
+ if (nautilus_file_is_gone (NAUTILUS_FILE (l->data)))
+ {
+ continue;
+ }
+
+ next_mime_type = nautilus_file_get_mime_type (NAUTILUS_FILE (l->data));
+ if (g_strcmp0 (next_mime_type, mime_type) != 0)
+ {
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_file_type (NautilusPropertiesWindow *self)
+{
+ if (!is_single_file_type (self))
+ {
+ return FALSE;
+ }
+
+ if (!is_multi_file_window (self)
+ && (nautilus_file_is_in_trash (get_target_file (self)) ||
+ nautilus_file_is_directory (get_original_file (self)) ||
+ is_network_directory (get_target_file (self)) ||
+ is_burn_directory (get_target_file (self)) ||
+ is_volume_properties (self)))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_location_info (NautilusPropertiesWindow *self)
+{
+ GList *l;
+
+ for (l = self->original_files; l != NULL; l = l->next)
+ {
+ if (nautilus_file_is_in_trash (NAUTILUS_FILE (l->data)) ||
+ is_root_directory (NAUTILUS_FILE (l->data)) ||
+ is_network_directory (NAUTILUS_FILE (l->data)) ||
+ is_burn_directory (NAUTILUS_FILE (l->data)))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_trashed_info (NautilusPropertiesWindow *self)
+{
+ GList *l;
+
+ for (l = self->original_files; l != NULL; l = l->next)
+ {
+ if (!nautilus_file_is_in_trash (NAUTILUS_FILE (l->data)))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_accessed_date (NautilusPropertiesWindow *self)
+{
+ /* 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 (nautilus_file_list_are_all_folders (self->target_files)
+ || is_multi_file_window (self))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_modified_date (NautilusPropertiesWindow *self)
+{
+ return !is_multi_file_window (self);
+}
+
+static gboolean
+should_show_created_date (NautilusPropertiesWindow *self)
+{
+ return !is_multi_file_window (self);
+}
+
+static gboolean
+should_show_link_target (NautilusPropertiesWindow *self)
+{
+ if (!is_multi_file_window (self)
+ && nautilus_file_is_symbolic_link (get_target_file (self)))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+should_show_free_space (NautilusPropertiesWindow *self)
+{
+ if (!is_multi_file_window (self)
+ && (nautilus_file_is_in_trash (get_target_file (self)) ||
+ is_network_directory (get_target_file (self)) ||
+ nautilus_file_is_in_recent (get_target_file (self)) ||
+ is_burn_directory (get_target_file (self)) ||
+ is_volume_properties (self)))
+ {
+ return FALSE;
+ }
+
+ if (nautilus_file_list_are_all_folders (self->target_files))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+should_show_volume_usage (NautilusPropertiesWindow *self)
+{
+ return is_volume_properties (self);
+}
+
+static void
+setup_volume_information (NautilusPropertiesWindow *self)
+{
+ NautilusFile *file;
+ g_autofree gchar *capacity = NULL;
+ g_autofree gchar *used = NULL;
+ g_autofree gchar *free = NULL;
+ const char *fs_type;
+ g_autofree gchar *uri = NULL;
+ g_autoptr (GFile) location = NULL;
+ g_autoptr (GFileInfo) info = NULL;
+
+ capacity = g_format_size (self->volume_capacity);
+ free = g_format_size (self->volume_free);
+ used = g_format_size (self->volume_used);
+
+ file = get_original_file (self);
+
+ uri = nautilus_file_get_activation_uri (file);
+
+ gtk_label_set_text (GTK_LABEL (self->disk_space_used_value), used);
+ gtk_label_set_text (GTK_LABEL (self->disk_space_free_value), free);
+ gtk_label_set_text (GTK_LABEL (self->disk_space_capacity_value), capacity);
+
+ 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);
+
+ /* We shouldn't be using filesystem::type, it's not meant for UI.
+ * https://gitlab.gnome.org/GNOME/nautilus/-/issues/98
+ *
+ * Until we fix that issue, workaround this common outrageous case. */
+ if (g_strcmp0 (fs_type, "msdos") == 0)
+ {
+ fs_type = "FAT";
+ }
+
+ if (fs_type != NULL)
+ {
+ /* Translators: %s will be filled with a filesystem type, such as 'ext4' or 'msdos'. */
+ g_autofree gchar *fs_label = g_strdup_printf (_("%s Filesystem"), fs_type);
+ gchar *cap_label = eel_str_capitalize (fs_label);
+ if (cap_label != NULL)
+ {
+ g_free (fs_label);
+ fs_label = cap_label;
+ }
+
+ gtk_label_set_text (self->type_file_system_label, fs_label);
+ gtk_widget_show (GTK_WIDGET (self->type_file_system_label));
+ }
+ }
+
+ gtk_level_bar_set_value (self->disk_space_level_bar, (double) self->volume_used / (double) self->volume_capacity);
+ /* display color changing based on filled level */
+ gtk_level_bar_add_offset_value (self->disk_space_level_bar, GTK_LEVEL_BAR_OFFSET_FULL, 0.0);
+}
+
+static void
+setup_volume_usage_widget (NautilusPropertiesWindow *self)
+{
+ NautilusFile *file;
+ g_autofree gchar *uri = NULL;
+ g_autoptr (GFile) location = NULL;
+ g_autoptr (GFileInfo) info = NULL;
+
+ file = get_original_file (self);
+
+ uri = nautilus_file_get_activation_uri (file);
+
+ location = g_file_new_for_uri (uri);
+ info = g_file_query_filesystem_info (location, "filesystem::*", NULL, NULL);
+
+ if (info)
+ {
+ self->volume_capacity = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
+ self->volume_free = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED))
+ {
+ self->volume_used = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED);
+ }
+ else
+ {
+ self->volume_used = self->volume_capacity - self->volume_free;
+ }
+ }
+ else
+ {
+ self->volume_capacity = 0;
+ self->volume_free = 0;
+ self->volume_used = 0;
+ }
+
+ if (self->volume_capacity > 0)
+ {
+ setup_volume_information (self);
+ }
+}
+
+static void
+open_parent_folder (NautilusPropertiesWindow *self)
+{
+ g_autoptr (GFile) parent_location = NULL;
+
+ parent_location = nautilus_file_get_parent_location (get_target_file (self));
+ g_return_if_fail (parent_location != NULL);
+
+ nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()),
+ parent_location,
+ NAUTILUS_OPEN_FLAG_NEW_WINDOW,
+ &(GList){get_original_file (self), NULL},
+ NULL, NULL);
+}
+
+static void
+open_link_target (NautilusPropertiesWindow *self)
+{
+ g_autofree gchar *link_target_uri = NULL;
+ g_autoptr (GFile) link_target_location = NULL;
+ g_autoptr (NautilusFile) link_target_file = NULL;
+ g_autoptr (GFile) parent_location = NULL;
+
+ link_target_uri = nautilus_file_get_symbolic_link_target_uri (get_target_file (self));
+ g_return_if_fail (link_target_uri != NULL);
+ link_target_location = g_file_new_for_uri (link_target_uri);
+ link_target_file = nautilus_file_get (link_target_location);
+ parent_location = nautilus_file_get_parent_location (link_target_file);
+ g_return_if_fail (parent_location != NULL);
+
+ nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()),
+ parent_location,
+ NAUTILUS_OPEN_FLAG_NEW_WINDOW,
+ &(GList){link_target_file, NULL},
+ NULL, NULL);
+}
+
+static void
+open_in_disks (NautilusPropertiesWindow *self)
+{
+ NautilusDBusLauncher *launcher = nautilus_dbus_launcher_get ();
+ g_autoptr (GMount) mount = NULL;
+ g_autoptr (GVolume) volume = NULL;
+ g_autofree gchar *device_identifier = NULL;
+ GVariant *parameters;
+
+ mount = nautilus_file_get_mount (get_original_file (self));
+ volume = (mount != NULL) ? g_mount_get_volume (mount) : NULL;
+
+ if (volume != NULL)
+ {
+ device_identifier = g_volume_get_identifier (volume,
+ G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+ }
+ else
+ {
+ g_autoptr (GFile) location = NULL;
+ g_autofree gchar *path = NULL;
+ g_autoptr (GUnixMountEntry) mount_entry = NULL;
+
+ location = nautilus_file_get_location (get_original_file (self));
+ path = g_file_get_path (location);
+ mount_entry = (path != NULL) ? g_unix_mount_at (path, NULL) : NULL;
+ if (mount_entry != NULL)
+ {
+ device_identifier = g_strdup (g_unix_mount_get_device_path (mount_entry));
+ }
+ }
+
+ if (device_identifier != NULL)
+ {
+ parameters = g_variant_new_parsed ("(objectpath '/org/gnome/DiskUtility', "
+ "@aay [], {'options': <{'block-device': <%s>}> })",
+ device_identifier);
+ }
+ else
+ {
+ parameters = g_variant_new_parsed ("(objectpath '/org/gnome/DiskUtility', @aay [], @a{sv} {})");
+ }
+
+ nautilus_dbus_launcher_call (launcher,
+ NAUTILUS_DBUS_LAUNCHER_DISKS,
+ "CommandLine", parameters,
+ GTK_WINDOW (self));
+}
+
+static void
+add_updatable_label (NautilusPropertiesWindow *self,
+ GtkWidget *label,
+ const char *file_attribute)
+{
+ g_object_set_data_full (G_OBJECT (label), "file_attribute",
+ g_strdup (file_attribute), g_free);
+
+ self->value_fields = g_list_prepend (self->value_fields, label);
+}
+
+static void
+setup_basic_page (NautilusPropertiesWindow *self)
+{
+ gboolean should_show_locations_list_box = FALSE;
+
+ /* Icon pixmap */
+
+ setup_image_widget (self, should_show_custom_icon_buttons (self));
+
+ self->icon_chooser = NULL;
+
+ if (!is_multi_file_window (self))
+ {
+ setup_star_button (self);
+ }
+
+ update_name_field (self);
+
+ if (should_show_volume_usage (self))
+ {
+ gtk_widget_show (self->disk_list_box);
+ setup_volume_usage_widget (self);
+ }
+
+ if (should_show_file_type (self))
+ {
+ gtk_widget_show (self->type_value_label);
+ add_updatable_label (self, self->type_value_label, "detailed_type");
+ }
+
+ if (should_show_link_target (self))
+ {
+ gtk_widget_show (self->link_target_row);
+ add_updatable_label (self, self->link_target_value_label, "link_target");
+
+ should_show_locations_list_box = TRUE;
+ }
+
+ if (is_multi_file_window (self) ||
+ nautilus_file_is_directory (get_target_file (self)))
+ {
+ /* We have a more efficient way to measure used space in volumes. */
+ if (!is_volume_properties (self))
+ {
+ gtk_widget_show (self->contents_box);
+ setup_contents_field (self);
+ }
+ }
+ else
+ {
+ gtk_widget_show (self->size_value_label);
+ add_updatable_label (self, self->size_value_label, "size");
+ }
+
+ if (should_show_location_info (self))
+ {
+ gtk_widget_show (self->parent_folder_row);
+ add_updatable_label (self, self->parent_folder_value_label, "where");
+
+ should_show_locations_list_box = TRUE;
+ }
+
+ if (should_show_trashed_info (self))
+ {
+ gtk_widget_show (self->trashed_list_box);
+ add_updatable_label (self, self->original_folder_value_label, "trash_orig_path");
+ add_updatable_label (self, self->trashed_on_value_label, "trashed_on_full");
+ }
+
+ if (should_show_modified_date (self))
+ {
+ gtk_widget_show (self->times_list_box);
+ gtk_widget_show (self->modified_row);
+ add_updatable_label (self, self->modified_value_label, "date_modified_full");
+ }
+
+ if (should_show_created_date (self))
+ {
+ gtk_widget_show (self->created_row);
+ gtk_widget_show (self->times_list_box);
+ add_updatable_label (self, self->created_value_label, "date_created_full");
+ }
+
+ if (should_show_accessed_date (self))
+ {
+ gtk_widget_show (self->times_list_box);
+ gtk_widget_show (self->accessed_row);
+ add_updatable_label (self, self->accessed_value_label, "date_accessed_full");
+ }
+
+ if (should_show_free_space (self))
+ {
+ /* We have a more efficient way to measure free space in volumes. */
+ if (!is_volume_properties (self))
+ {
+ gtk_widget_show (self->free_space_value_label);
+ add_updatable_label (self, self->free_space_value_label, "free_space");
+ }
+ }
+
+ if (should_show_locations_list_box)
+ {
+ gtk_widget_show (self->locations_list_box);
+ }
+}
+
+static FilterType
+files_get_filter_type (NautilusPropertiesWindow *self)
+{
+ FilterType filter_type = NO_FILES_OR_FOLDERS;
+
+ for (GList *l = self->target_files; l != NULL && filter_type != FILES_AND_FOLDERS; l = l->next)
+ {
+ NautilusFile *file = NAUTILUS_FILE (l->data);
+ if (nautilus_file_is_directory (file))
+ {
+ filter_type |= FOLDERS_ONLY;
+ }
+ else
+ {
+ filter_type |= FILES_ONLY;
+ }
+ }
+
+ return filter_type;
+}
+
+static gboolean
+file_matches_filter_type (NautilusFile *file,
+ FilterType filter_type)
+{
+ gboolean is_directory = nautilus_file_is_directory (file);
+
+ switch (filter_type)
+ {
+ case FILES_AND_FOLDERS:
+ {
+ return TRUE;
+ }
+
+ case FILES_ONLY:
+ {
+ return !is_directory;
+ }
+
+ case FOLDERS_ONLY:
+ {
+ return is_directory;
+ }
+
+ default:
+ {
+ return FALSE;
+ }
+ }
+}
+
+static gboolean
+files_has_changable_permissions_directory (NautilusPropertiesWindow *self)
+{
+ GList *l;
+ gboolean changable = FALSE;
+
+ for (l = self->target_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+ file = NAUTILUS_FILE (l->data);
+ if (nautilus_file_is_directory (file) &&
+ nautilus_file_can_get_permissions (file) &&
+ nautilus_file_can_set_permissions (file))
+ {
+ changable = TRUE;
+ }
+ else
+ {
+ changable = FALSE;
+ break;
+ }
+ }
+
+ return changable;
+}
+
+static void
+start_long_operation (NautilusPropertiesWindow *self)
+{
+ if (self->long_operation_underway == 0)
+ {
+ /* start long operation */
+ gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "wait");
+ }
+ self->long_operation_underway++;
+}
+
+static void
+end_long_operation (NautilusPropertiesWindow *self)
+{
+ if (gtk_native_get_surface (GTK_NATIVE (self)) != NULL &&
+ self->long_operation_underway == 1)
+ {
+ /* finished !! */
+ gtk_widget_set_cursor (GTK_WIDGET (self), NULL);
+ }
+ self->long_operation_underway--;
+}
+
+static void
+permission_change_callback (NautilusFile *file,
+ GFile *res_loc,
+ GError *error,
+ gpointer callback_data)
+{
+ g_autoptr (NautilusPropertiesWindow) self = NAUTILUS_PROPERTIES_WINDOW (callback_data);
+ g_assert (self != NULL);
+
+ end_long_operation (self);
+
+ /* Report the error if it's an error. */
+ nautilus_report_error_setting_permissions (file, error, GTK_WINDOW (self));
+}
+
+static void
+update_permissions (NautilusPropertiesWindow *self,
+ guint32 vfs_new_perm,
+ guint32 vfs_mask,
+ FilterType filter_type,
+ gboolean use_original)
+{
+ for (GList *l = self->target_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file = NAUTILUS_FILE (l->data);
+ guint32 permissions;
+
+ if (!nautilus_file_can_get_permissions (file))
+ {
+ continue;
+ }
+
+ if (!nautilus_file_can_get_permissions (file) || !file_matches_filter_type (file, filter_type))
+ {
+ continue;
+ }
+
+ permissions = nautilus_file_get_permissions (file);
+ if (use_original)
+ {
+ gpointer ptr;
+ if (g_hash_table_lookup_extended (self->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 (self);
+ g_object_ref (self);
+ nautilus_file_set_permissions
+ (file, permissions,
+ permission_change_callback,
+ self);
+ }
+}
+
+static void
+execution_bit_changed (NautilusPropertiesWindow *self,
+ GParamSpec *params,
+ GtkWidget *widget)
+{
+ const guint32 permission_mask = UNIX_PERM_USER_EXEC | UNIX_PERM_GROUP_EXEC | UNIX_PERM_OTHER_EXEC;
+ const FilterType filter_type = FILES_ONLY;
+
+ /* if activated from switch, switch state is already toggled, thus invert value via XOR. */
+ gboolean active = gtk_switch_get_state (self->execution_switch) ^ GTK_IS_SWITCH (widget);
+ gboolean set_executable = !active;
+
+ update_permissions (self,
+ set_executable ? permission_mask : 0,
+ permission_mask,
+ filter_type,
+ FALSE);
+}
+
+static gboolean
+should_show_exectution_switch (NautilusPropertiesWindow *self)
+{
+ g_autofree gchar *mime_type = NULL;
+
+ if (is_multi_file_window (self))
+ {
+ return FALSE;
+ }
+
+ mime_type = nautilus_file_get_mime_type (get_target_file (self));
+ return g_content_type_can_be_executable (mime_type);
+}
+
+static void
+update_execution_row (GtkWidget *row,
+ TargetPermissions *target_perm)
+{
+ NautilusPropertiesWindow *self = target_perm->window;
+
+ if (!should_show_exectution_switch (self))
+ {
+ gtk_widget_hide (GTK_WIDGET (self->execution_row));
+ }
+ else
+ {
+ g_signal_handlers_block_by_func (self->execution_switch,
+ G_CALLBACK (execution_bit_changed),
+ self);
+
+ gtk_switch_set_state (self->execution_switch,
+ target_perm->file_exec_permissions == PERMISSION_EXEC);
+
+ g_signal_handlers_unblock_by_func (self->execution_switch,
+ G_CALLBACK (execution_bit_changed),
+ self);
+
+ gtk_widget_set_sensitive (row,
+ target_perm->can_set_any_file_permission);
+
+ gtk_widget_show (GTK_WIDGET (self->execution_row));
+ }
+}
+
+static void
+on_permission_row_change (AdwComboRow *row,
+ GParamSpec *pspec,
+ NautilusPropertiesWindow *self)
+{
+ GListModel *list = adw_combo_row_get_model (row);
+ guint position = adw_combo_row_get_selected (row);
+ g_autoptr (NautilusPermissionEntry) entry = NULL;
+ FilterType filter_type;
+ gboolean use_original;
+ PermissionType type;
+ PermissionValue mask;
+ guint32 vfs_new_perm, vfs_mask;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ if (position == GTK_INVALID_LIST_POSITION)
+ {
+ return;
+ }
+
+ filter_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "filter-type"));
+ type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "permission-type"));
+
+ mask = PERMISSION_READ | PERMISSION_WRITE | ((filter_type == FOLDERS_ONLY) * PERMISSION_EXEC);
+ vfs_mask = permission_to_vfs (type, mask);
+
+ entry = g_list_model_get_item (list, position);
+ vfs_new_perm = permission_to_vfs (type, entry->permission_value);
+ use_original = entry->permission_value & PERMISSION_INCONSISTENT;
+
+ update_permissions (self, vfs_new_perm, vfs_mask,
+ filter_type, use_original);
+}
+
+static void
+list_store_append_nautilus_permission_entry (GListStore *list,
+ PermissionValue permission_value,
+ gboolean describes_folder)
+{
+ g_autoptr (NautilusPermissionEntry) entry = g_object_new (NAUTILUS_TYPE_PERMISSION_ENTRY, NULL);
+
+ entry->name = g_strdup (permission_value_to_string (permission_value, describes_folder));
+ entry->permission_value = permission_value;
+
+ g_list_store_append (list, entry);
+}
+
+static gint
+get_permission_value_list_position (GListModel *list,
+ PermissionValue wanted_permissions)
+{
+ const guint n_entries = g_list_model_get_n_items (list);
+
+ for (guint position = 0; position < n_entries; position += 1)
+ {
+ g_autoptr (NautilusPermissionEntry) entry = g_list_model_get_item (list, position);
+ if (entry->permission_value == wanted_permissions)
+ {
+ return position;
+ }
+ }
+
+ return -1;
+}
+
+static void
+update_permission_row (AdwComboRow *row,
+ TargetPermissions *target_perm)
+{
+ NautilusPropertiesWindow *self = target_perm->window;
+ PermissionType type;
+ PermissionValue permissions_to_show;
+ FilterType filter_type;
+ gboolean is_folder;
+ GListModel *model;
+ gint position;
+
+ model = adw_combo_row_get_model (row);
+
+ filter_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "filter-type"));
+ is_folder = (FOLDERS_ONLY == filter_type);
+ type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "permission-type"));
+
+ permissions_to_show = is_folder ? target_perm->folder_permissions[type] :
+ target_perm->file_permissions[type] & ~PERMISSION_EXEC;
+
+ g_signal_handlers_block_by_func (G_OBJECT (row),
+ G_CALLBACK (on_permission_row_change),
+ self);
+
+ position = get_permission_value_list_position (model, permissions_to_show);
+
+ if (position == GTK_INVALID_LIST_POSITION)
+ {
+ /* configured permissions not listed, create new entry */
+ position = g_list_model_get_n_items (model);
+ list_store_append_nautilus_permission_entry (G_LIST_STORE (model), permissions_to_show, is_folder);
+ }
+
+ adw_combo_row_set_selected (row, position);
+
+ /* Also enable if no files found (for recursive
+ * file changes when only selecting folders) */
+ gtk_widget_set_sensitive (GTK_WIDGET (row), is_folder ?
+ target_perm->can_set_all_folder_permission :
+ target_perm->can_set_all_file_permission);
+
+ g_signal_handlers_unblock_by_func (G_OBJECT (row),
+ G_CALLBACK (on_permission_row_change),
+ self);
+}
+
+static void
+setup_permissions_combo_box (GtkComboBox *combo,
+ PermissionType type,
+ FilterType filter_type)
+{
+ g_autoptr (GtkListStore) store = NULL;
+ GtkCellRenderer *cell;
+ GtkTreeIter iter;
+
+ store = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_STRING);
+ gtk_combo_box_set_model (combo, GTK_TREE_MODEL (store));
+ gtk_combo_box_set_id_column (GTK_COMBO_BOX (combo), COLUMN_ID);
+
+ g_object_set_data (G_OBJECT (combo), "filter-type", GINT_TO_POINTER (filter_type));
+ g_object_set_data (G_OBJECT (combo), "permission-type", GINT_TO_POINTER (type));
+
+ if (filter_type == FOLDERS_ONLY)
+ {
+ if (type != PERMISSION_USER)
+ {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ /* Translators: this is referred to the permissions
+ * the user has in a directory.
+ */
+ COLUMN_NAME, _("None"),
+ COLUMN_VALUE, PERMISSION_NONE,
+ COLUMN_ID, "none",
+ -1);
+ }
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_NAME, _("List files only"),
+ COLUMN_VALUE, PERMISSION_READ,
+ COLUMN_ID, "r",
+ -1);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_NAME, _("Access files"),
+ COLUMN_VALUE, PERMISSION_READ | PERMISSION_EXEC,
+ COLUMN_ID, "rx",
+ -1);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_NAME, _("Create and delete files"),
+ COLUMN_VALUE, PERMISSION_READ | PERMISSION_EXEC | PERMISSION_WRITE,
+ COLUMN_ID, "rwx",
+ -1);
+ }
+ else
+ {
+ if (type != PERMISSION_USER)
+ {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_NAME, _("None"),
+ COLUMN_VALUE, PERMISSION_NONE,
+ COLUMN_ID, "none",
+ -1);
+ }
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_NAME, _("Read-only"),
+ COLUMN_VALUE, PERMISSION_READ,
+ COLUMN_ID, "r",
+ -1);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_NAME, _("Read and write"),
+ COLUMN_VALUE, PERMISSION_READ | PERMISSION_WRITE,
+ COLUMN_ID, "rw",
+ -1);
+ }
+
+ cell = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell,
+ "text", COLUMN_NAME,
+ NULL);
+}
+
+static gboolean
+all_can_get_permissions (GList *file_list)
+{
+ GList *l;
+ for (l = file_list; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (l->data);
+
+ if (!nautilus_file_can_get_permissions (file))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+all_can_set_permissions (GList *file_list)
+{
+ GList *l;
+ for (l = file_list; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (l->data);
+
+ if (!nautilus_file_can_set_permissions (file))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static GHashTable *
+get_initial_permissions (GList *file_list)
+{
+ GHashTable *ret;
+ GList *l;
+
+ ret = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
+
+ for (l = file_list; l != NULL; l = l->next)
+ {
+ guint32 permissions;
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (l->data);
+
+ permissions = nautilus_file_get_permissions (file);
+ g_hash_table_insert (ret, file,
+ GINT_TO_POINTER (permissions));
+ }
+
+ return ret;
+}
+
+static GListModel *
+create_permission_list_model (PermissionType type,
+ FilterType filter_type)
+{
+ GListStore *store = g_list_store_new (NAUTILUS_TYPE_PERMISSION_ENTRY);
+
+ if (type != PERMISSION_USER)
+ {
+ list_store_append_nautilus_permission_entry (store, PERMISSION_NONE, /* unused */ FALSE);
+ }
+
+ if (filter_type == FOLDERS_ONLY)
+ {
+ list_store_append_nautilus_permission_entry (store, PERMISSION_READ, TRUE);
+ list_store_append_nautilus_permission_entry (store, PERMISSION_READ | PERMISSION_EXEC, TRUE);
+ list_store_append_nautilus_permission_entry (store, PERMISSION_READ | PERMISSION_EXEC | PERMISSION_WRITE, TRUE);
+ }
+ else
+ {
+ list_store_append_nautilus_permission_entry (store, PERMISSION_READ, FALSE);
+ list_store_append_nautilus_permission_entry (store, PERMISSION_READ | PERMISSION_WRITE, FALSE);
+ }
+
+ return G_LIST_MODEL (store);
+}
+
+static void
+create_permissions_row (NautilusPropertiesWindow *self,
+ AdwComboRow *row,
+ PermissionType permission_type,
+ FilterType filter_type)
+{
+ g_autoptr (GtkExpression) expression = NULL;
+ g_autoptr (GListModel) model = NULL;
+
+ expression = gtk_property_expression_new (NAUTILUS_TYPE_PERMISSION_ENTRY, NULL, "name");
+ adw_combo_row_set_expression (row, expression);
+
+ gtk_widget_show (GTK_WIDGET (row));
+
+ g_object_set_data (G_OBJECT (row), "permission-type", GINT_TO_POINTER (permission_type));
+ g_object_set_data (G_OBJECT (row), "filter-type", GINT_TO_POINTER (filter_type));
+ model = create_permission_list_model (permission_type, filter_type);
+ adw_combo_row_set_model (row, model);
+
+ self->permission_rows = g_list_prepend (self->permission_rows, row);
+ g_signal_connect (row, "notify::selected", G_CALLBACK (on_permission_row_change), self);
+}
+
+static void
+create_simple_permissions (NautilusPropertiesWindow *self)
+{
+ FilterType filter_type = files_get_filter_type (self);
+
+ g_assert (filter_type != NO_FILES_OR_FOLDERS);
+
+ setup_ownership_row (self, self->owner_row);
+ setup_ownership_row (self, self->group_row);
+
+ if (filter_type == FILES_AND_FOLDERS)
+ {
+ /* owner */
+ create_permissions_row (self, self->owner_folder_access_row,
+ PERMISSION_USER, FOLDERS_ONLY);
+ create_permissions_row (self, self->owner_file_access_row,
+ PERMISSION_USER, FILES_ONLY);
+ /* group */
+ create_permissions_row (self, self->group_folder_access_row,
+ PERMISSION_GROUP, FOLDERS_ONLY);
+ create_permissions_row (self, self->group_file_access_row,
+ PERMISSION_GROUP, FILES_ONLY);
+ /* others */
+ create_permissions_row (self, self->others_folder_access_row,
+ PERMISSION_OTHER, FOLDERS_ONLY);
+ create_permissions_row (self, self->others_file_access_row,
+ PERMISSION_OTHER, FILES_ONLY);
+ }
+ else
+ {
+ create_permissions_row (self, self->owner_access_row,
+ PERMISSION_USER, filter_type);
+ create_permissions_row (self, self->group_access_row,
+ PERMISSION_GROUP, filter_type);
+ create_permissions_row (self, self->others_access_row,
+ PERMISSION_OTHER, filter_type);
+ }
+
+ /* Connect execution bit switch, independent of whether it will be visible or not. */
+ g_signal_connect_swapped (self->execution_row, "activated",
+ G_CALLBACK (execution_bit_changed),
+ self);
+ g_signal_connect_swapped (self->execution_switch, "notify::active",
+ G_CALLBACK (execution_bit_changed),
+ self);
+}
+
+static void
+set_recursive_permissions_done (gboolean success,
+ gpointer callback_data)
+{
+ g_autoptr (NautilusPropertiesWindow) self = NAUTILUS_PROPERTIES_WINDOW (callback_data);
+ end_long_operation (self);
+}
+
+static void
+on_change_permissions_response (GtkDialog *dialog,
+ int response,
+ NautilusPropertiesWindow *self)
+{
+ guint32 file_permission, file_permission_mask;
+ guint32 dir_permission, dir_permission_mask;
+ guint32 vfs_mask, vfs_new_perm;
+ GtkWidget *combo;
+ gboolean use_original;
+ FilterType filter_type;
+ GList *l;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ PermissionType type;
+ int new_perm, mask;
+
+ if (response != GTK_RESPONSE_OK)
+ {
+ g_clear_pointer (&self->change_permission_combos, g_list_free);
+ gtk_window_destroy (GTK_WINDOW (dialog));
+ return;
+ }
+
+ file_permission = 0;
+ file_permission_mask = 0;
+ dir_permission = 0;
+ dir_permission_mask = 0;
+
+ /* Simple mode, minus exec checkbox */
+ for (l = self->change_permission_combos; l != NULL; l = l->next)
+ {
+ combo = l->data;
+
+ if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
+ {
+ continue;
+ }
+
+ type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type"));
+ filter_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "filter-type"));
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+ gtk_tree_model_get (model, &iter,
+ COLUMN_VALUE, &new_perm,
+ COLUMN_USE_ORIGINAL, &use_original, -1);
+ if (use_original)
+ {
+ continue;
+ }
+ vfs_new_perm = permission_to_vfs (type, new_perm);
+
+ mask = PERMISSION_READ | PERMISSION_WRITE;
+ if (filter_type == FOLDERS_ONLY)
+ {
+ mask |= PERMISSION_EXEC;
+ }
+ vfs_mask = permission_to_vfs (type, mask);
+
+ if (filter_type == FOLDERS_ONLY)
+ {
+ dir_permission_mask |= vfs_mask;
+ dir_permission |= vfs_new_perm;
+ }
+ else
+ {
+ file_permission_mask |= vfs_mask;
+ file_permission |= vfs_new_perm;
+ }
+ }
+
+ for (l = self->target_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (l->data);
+
+ if (nautilus_file_is_directory (file) &&
+ nautilus_file_can_set_permissions (file))
+ {
+ g_autofree gchar *uri = NULL;
+
+ uri = nautilus_file_get_uri (file);
+ start_long_operation (self);
+ g_object_ref (self);
+ nautilus_file_set_permissions_recursive (uri,
+ file_permission,
+ file_permission_mask,
+ dir_permission,
+ dir_permission_mask,
+ set_recursive_permissions_done,
+ self);
+ }
+ }
+ g_clear_pointer (&self->change_permission_combos, g_list_free);
+ gtk_window_destroy (GTK_WINDOW (dialog));
+}
+
+static void
+set_active_from_umask (GtkComboBox *combo,
+ PermissionType type,
+ FilterType filter_type)
+{
+ mode_t initial;
+ mode_t mask;
+ mode_t p;
+ const char *id;
+
+ if (filter_type == FOLDERS_ONLY)
+ {
+ initial = (S_IRWXU | S_IRWXG | S_IRWXO);
+ }
+ else
+ {
+ initial = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+ }
+
+ umask (mask = umask (0));
+
+ p = ~mask & initial;
+
+ if (type == PERMISSION_USER)
+ {
+ p &= ~(S_IRWXG | S_IRWXO);
+ if ((p & S_IRWXU) == S_IRWXU)
+ {
+ id = "rwx";
+ }
+ else if ((p & (S_IRUSR | S_IWUSR)) == (S_IRUSR | S_IWUSR))
+ {
+ id = "rw";
+ }
+ else if ((p & (S_IRUSR | S_IXUSR)) == (S_IRUSR | S_IXUSR))
+ {
+ id = "rx";
+ }
+ else if ((p & S_IRUSR) == S_IRUSR)
+ {
+ id = "r";
+ }
+ else
+ {
+ id = "none";
+ }
+ }
+ else if (type == PERMISSION_GROUP)
+ {
+ p &= ~(S_IRWXU | S_IRWXO);
+ if ((p & S_IRWXG) == S_IRWXG)
+ {
+ id = "rwx";
+ }
+ else if ((p & (S_IRGRP | S_IWGRP)) == (S_IRGRP | S_IWGRP))
+ {
+ id = "rw";
+ }
+ else if ((p & (S_IRGRP | S_IXGRP)) == (S_IRGRP | S_IXGRP))
+ {
+ id = "rx";
+ }
+ else if ((p & S_IRGRP) == S_IRGRP)
+ {
+ id = "r";
+ }
+ else
+ {
+ id = "none";
+ }
+ }
+ else
+ {
+ p &= ~(S_IRWXU | S_IRWXG);
+ if ((p & S_IRWXO) == S_IRWXO)
+ {
+ id = "rwx";
+ }
+ else if ((p & (S_IROTH | S_IWOTH)) == (S_IROTH | S_IWOTH))
+ {
+ id = "rw";
+ }
+ else if ((p & (S_IROTH | S_IXOTH)) == (S_IROTH | S_IXOTH))
+ {
+ id = "rx";
+ }
+ else if ((p & S_IROTH) == S_IROTH)
+ {
+ id = "r";
+ }
+ else
+ {
+ id = "none";
+ }
+ }
+
+ gtk_combo_box_set_active_id (combo, id);
+}
+
+static void
+on_change_permissions_clicked (GtkWidget *button,
+ NautilusPropertiesWindow *self)
+{
+ GtkWidget *dialog;
+ GtkComboBox *combo;
+ g_autoptr (GtkBuilder) change_permissions_builder = NULL;
+
+ change_permissions_builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-file-properties-change-permissions.ui");
+
+ dialog = GTK_WIDGET (gtk_builder_get_object (change_permissions_builder, "change_permissions_dialog"));
+ gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self));
+
+ /* Owner Permissions */
+ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "file_owner_combo"));
+ setup_permissions_combo_box (combo, PERMISSION_USER, FILES_ONLY);
+ self->change_permission_combos = g_list_prepend (self->change_permission_combos,
+ combo);
+ set_active_from_umask (combo, PERMISSION_USER, FILES_ONLY);
+
+ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "folder_owner_combo"));
+ setup_permissions_combo_box (combo, PERMISSION_USER, FOLDERS_ONLY);
+ self->change_permission_combos = g_list_prepend (self->change_permission_combos,
+ combo);
+ set_active_from_umask (combo, PERMISSION_USER, FOLDERS_ONLY);
+
+ /* Group Permissions */
+ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "file_group_combo"));
+ setup_permissions_combo_box (combo, PERMISSION_GROUP, FILES_ONLY);
+ self->change_permission_combos = g_list_prepend (self->change_permission_combos,
+ combo);
+ set_active_from_umask (combo, PERMISSION_GROUP, FILES_ONLY);
+
+ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "folder_group_combo"));
+ setup_permissions_combo_box (combo, PERMISSION_GROUP, FOLDERS_ONLY);
+ self->change_permission_combos = g_list_prepend (self->change_permission_combos,
+ combo);
+ set_active_from_umask (combo, PERMISSION_GROUP, FOLDERS_ONLY);
+
+ /* Others Permissions */
+ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "file_other_combo"));
+ setup_permissions_combo_box (combo, PERMISSION_OTHER, FILES_ONLY);
+ self->change_permission_combos = g_list_prepend (self->change_permission_combos,
+ combo);
+ set_active_from_umask (combo, PERMISSION_OTHER, FILES_ONLY);
+
+ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "folder_other_combo"));
+ setup_permissions_combo_box (combo, PERMISSION_OTHER, FOLDERS_ONLY);
+ self->change_permission_combos = g_list_prepend (self->change_permission_combos,
+ combo);
+ set_active_from_umask (combo, PERMISSION_OTHER, FOLDERS_ONLY);
+
+ g_signal_connect (dialog, "response", G_CALLBACK (on_change_permissions_response), self);
+ gtk_widget_show (dialog);
+}
+
+static void
+setup_permissions_page (NautilusPropertiesWindow *self)
+{
+ GList *file_list;
+
+ file_list = self->original_files;
+
+ self->initial_permissions = NULL;
+
+ if (all_can_get_permissions (file_list) && all_can_get_permissions (self->target_files))
+ {
+ self->initial_permissions = get_initial_permissions (self->target_files);
+ self->has_recursive_apply = files_has_changable_permissions_directory (self);
+
+ if (!all_can_set_permissions (file_list))
+ {
+ gtk_widget_show (self->not_the_owner_label);
+ gtk_widget_show (self->bottom_prompt_seperator);
+ }
+
+ gtk_stack_set_visible_child_name (GTK_STACK (self->permissions_stack), "permissions-box");
+ create_simple_permissions (self);
+
+#ifdef HAVE_SELINUX
+ gtk_widget_show (self->security_context_list_box);
+
+ /* Stash a copy of the file attribute name in this field for the callback's sake. */
+ g_object_set_data_full (G_OBJECT (self->security_context_value_label), "file_attribute",
+ g_strdup ("selinux_context"), g_free);
+
+ self->value_fields = g_list_prepend (self->value_fields,
+ self->security_context_value_label);
+#endif
+
+ if (self->has_recursive_apply)
+ {
+ gtk_widget_show (self->change_permissions_button_box);
+ g_signal_connect (self->change_permissions_button, "clicked",
+ G_CALLBACK (on_change_permissions_clicked),
+ self);
+ }
+ }
+ else
+ {
+ /*
+ * This if block only gets executed if its a single file window,
+ * in which case the label text needs to be different from the
+ * default label text. The default label text for a multifile
+ * window is set in nautilus-properties-window.ui so no else block.
+ */
+ if (!is_multi_file_window (self))
+ {
+ g_autofree gchar *file_name = NULL;
+ g_autofree gchar *prompt_text = NULL;
+
+ file_name = nautilus_file_get_display_name (get_target_file (self));
+ prompt_text = g_strdup_printf (_("The permissions of “%s” could not be determined."), file_name);
+ adw_status_page_set_description (ADW_STATUS_PAGE (self->unknown_permissions_page), prompt_text);
+ }
+
+ gtk_stack_set_visible_child_name (GTK_STACK (self->permissions_stack), "permission-indeterminable");
+ }
+}
+
+static void
+refresh_extension_model_pages (NautilusPropertiesWindow *self)
+{
+ g_autoptr (GListStore) extensions_list = g_list_store_new (NAUTILUS_TYPE_PROPERTIES_MODEL);
+ g_autolist (NautilusPropertiesModel) all_models = NULL;
+ g_autolist (GObject) providers =
+ nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_PROPERTIES_MODEL_PROVIDER);
+
+ for (GList *l = providers; l != NULL; l = l->next)
+ {
+ GList *models = nautilus_properties_model_provider_get_models (l->data, self->original_files);
+
+ all_models = g_list_concat (all_models, models);
+ }
+
+ for (GList *l = all_models; l != NULL; l = l->next)
+ {
+ g_list_store_append (extensions_list, NAUTILUS_PROPERTIES_MODEL (l->data));
+ }
+
+ gtk_widget_set_visible (self->extension_models_list_box,
+ g_list_model_get_n_items (G_LIST_MODEL (extensions_list)) > 0);
+
+ gtk_list_box_bind_model (GTK_LIST_BOX (self->extension_models_list_box),
+ G_LIST_MODEL (extensions_list),
+ (GtkListBoxCreateWidgetFunc) add_extension_model_page,
+ self,
+ NULL);
+}
+
+static gboolean
+should_show_permissions (NautilusPropertiesWindow *self)
+{
+ GList *l;
+
+ /* Don't show permissions for Trash and Computer since they're not
+ * really file system objects.
+ */
+ for (l = self->original_files; l != NULL; l = l->next)
+ {
+ if (nautilus_file_is_in_trash (NAUTILUS_FILE (l->data)) ||
+ nautilus_file_is_in_recent (NAUTILUS_FILE (l->data)))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static char *
+get_pending_key (GList *file_list)
+{
+ GList *uris = NULL;
+ GList *l;
+ GString *key;
+ char *ret;
+
+ uris = NULL;
+ for (l = file_list; l != NULL; l = l->next)
+ {
+ uris = g_list_prepend (uris, nautilus_file_get_uri (NAUTILUS_FILE (l->data)));
+ }
+ uris = g_list_sort (uris, (GCompareFunc) strcmp);
+
+ key = g_string_new ("");
+ for (l = uris; l != NULL; l = l->next)
+ {
+ g_string_append (key, l->data);
+ g_string_append (key, ";");
+ }
+
+ g_list_free_full (uris, g_free);
+
+ ret = key->str;
+ g_string_free (key, FALSE);
+
+ return ret;
+}
+
+static StartupData *
+startup_data_new (GList *original_files,
+ GList *target_files,
+ const char *pending_key,
+ GtkWidget *parent_widget,
+ GtkWindow *parent_window,
+ const char *startup_id,
+ NautilusPropertiesWindowCallback callback,
+ gpointer callback_data,
+ NautilusPropertiesWindow *window)
+{
+ StartupData *data;
+ GList *l;
+
+ data = g_new0 (StartupData, 1);
+ data->original_files = nautilus_file_list_copy (original_files);
+ data->target_files = nautilus_file_list_copy (target_files);
+ data->parent_widget = parent_widget;
+ data->parent_window = parent_window;
+ data->startup_id = g_strdup (startup_id);
+ data->pending_key = g_strdup (pending_key);
+ data->pending_files = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
+ data->callback = callback;
+ data->callback_data = callback_data;
+ data->window = window;
+
+ for (l = data->target_files; l != NULL; l = l->next)
+ {
+ g_hash_table_insert (data->pending_files, l->data, l->data);
+ }
+
+ return data;
+}
+
+static void
+startup_data_free (StartupData *data)
+{
+ nautilus_file_list_free (data->original_files);
+ nautilus_file_list_free (data->target_files);
+ g_hash_table_destroy (data->pending_files);
+ g_free (data->pending_key);
+ g_free (data->startup_id);
+ g_free (data);
+}
+
+static void
+file_changed_callback (NautilusFile *file,
+ gpointer user_data)
+{
+ NautilusPropertiesWindow *self = NAUTILUS_PROPERTIES_WINDOW (user_data);
+
+ if (!g_list_find (self->changed_files, file))
+ {
+ nautilus_file_ref (file);
+ self->changed_files = g_list_prepend (self->changed_files, file);
+ schedule_files_update (self);
+ }
+}
+
+static NautilusPropertiesWindow *
+create_properties_window (StartupData *startup_data)
+{
+ NautilusPropertiesWindow *window;
+ GList *l;
+
+ window = NAUTILUS_PROPERTIES_WINDOW (g_object_new (NAUTILUS_TYPE_PROPERTIES_WINDOW,
+ NULL));
+
+ window->original_files = nautilus_file_list_copy (startup_data->original_files);
+
+ window->target_files = nautilus_file_list_copy (startup_data->target_files);
+
+ if (startup_data->parent_widget)
+ {
+ gtk_window_set_display (GTK_WINDOW (window),
+ gtk_widget_get_display (startup_data->parent_widget));
+ }
+
+ if (startup_data->parent_window)
+ {
+ gtk_window_set_transient_for (GTK_WINDOW (window), startup_data->parent_window);
+ }
+
+ if (startup_data->startup_id)
+ {
+ gtk_window_set_startup_id (GTK_WINDOW (window), startup_data->startup_id);
+ }
+
+ /* Start monitoring the file attributes we display. Note that some
+ * of the attributes are for the original file, and some for the
+ * target files.
+ */
+
+ for (l = window->original_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+ NautilusFileAttributes attributes;
+
+ file = NAUTILUS_FILE (l->data);
+
+ attributes =
+ NAUTILUS_FILE_ATTRIBUTES_FOR_ICON |
+ NAUTILUS_FILE_ATTRIBUTE_INFO;
+
+ nautilus_file_monitor_add (file,
+ &window->original_files,
+ attributes);
+ }
+
+ for (l = window->target_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+ NautilusFileAttributes attributes;
+
+ file = NAUTILUS_FILE (l->data);
+
+ attributes = 0;
+ if (nautilus_file_is_directory (file))
+ {
+ attributes |= NAUTILUS_FILE_ATTRIBUTE_DEEP_COUNTS;
+ }
+
+ attributes |= NAUTILUS_FILE_ATTRIBUTE_INFO;
+ nautilus_file_monitor_add (file, &window->target_files, attributes);
+ }
+
+ for (l = window->target_files; l != NULL; l = l->next)
+ {
+ g_signal_connect_object (NAUTILUS_FILE (l->data),
+ "changed",
+ G_CALLBACK (file_changed_callback),
+ G_OBJECT (window),
+ 0);
+ }
+
+ for (l = window->original_files; l != NULL; l = l->next)
+ {
+ g_signal_connect_object (NAUTILUS_FILE (l->data),
+ "changed",
+ G_CALLBACK (file_changed_callback),
+ G_OBJECT (window),
+ 0);
+ }
+
+ /* Create the pages. */
+ setup_basic_page (window);
+
+ if (should_show_permissions (window))
+ {
+ setup_permissions_page (window);
+ gtk_widget_show (window->permissions_navigation_row);
+ }
+
+ if (should_show_exectution_switch (window))
+ {
+ gtk_widget_show (GTK_WIDGET (window->execution_row));
+ }
+
+ /* Add available extension models pages */
+ refresh_extension_model_pages (window);
+
+ /* Update from initial state */
+ properties_window_update (window, NULL);
+
+ return window;
+}
+
+static GList *
+get_target_file_list (GList *original_files)
+{
+ return g_list_copy_deep (original_files,
+ (GCopyFunc) get_target_file_for_original_file,
+ NULL);
+}
+
+static void
+properties_window_finish (StartupData *data)
+{
+ gboolean cancel_timed_wait;
+
+ if (data->parent_widget != NULL)
+ {
+ g_signal_handlers_disconnect_by_data (data->parent_widget,
+ data);
+ }
+ if (data->window != NULL)
+ {
+ g_signal_handlers_disconnect_by_data (data->window,
+ data);
+ }
+
+ cancel_timed_wait = (data->window == NULL && !data->cancelled);
+ remove_pending (data, TRUE, cancel_timed_wait);
+
+ startup_data_free (data);
+}
+
+static void
+cancel_create_properties_window_callback (gpointer callback_data)
+{
+ StartupData *data;
+
+ data = callback_data;
+ data->cancelled = TRUE;
+
+ properties_window_finish (data);
+}
+
+static void
+parent_widget_destroyed_callback (GtkWidget *widget,
+ gpointer callback_data)
+{
+ g_assert (widget == ((StartupData *) callback_data)->parent_widget);
+
+ properties_window_finish ((StartupData *) callback_data);
+}
+
+static void
+cancel_call_when_ready_callback (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ nautilus_file_cancel_call_when_ready
+ (NAUTILUS_FILE (key),
+ is_directory_ready_callback,
+ user_data);
+}
+
+static void
+remove_pending (StartupData *startup_data,
+ gboolean cancel_call_when_ready,
+ gboolean cancel_timed_wait)
+{
+ if (cancel_call_when_ready)
+ {
+ g_hash_table_foreach (startup_data->pending_files,
+ cancel_call_when_ready_callback,
+ startup_data);
+ }
+ if (cancel_timed_wait)
+ {
+ eel_timed_wait_stop
+ (cancel_create_properties_window_callback, startup_data);
+ }
+ if (startup_data->pending_key != NULL)
+ {
+ g_hash_table_remove (pending_lists, startup_data->pending_key);
+ }
+}
+
+static gboolean
+widget_on_destroy (GtkWidget *widget,
+ gpointer user_data)
+{
+ StartupData *data = (StartupData *) user_data;
+
+
+ if (data->callback != NULL)
+ {
+ data->callback (data->callback_data);
+ }
+
+ properties_window_finish (data);
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+is_directory_ready_callback (NautilusFile *file,
+ gpointer data)
+{
+ StartupData *startup_data;
+
+ startup_data = data;
+
+ g_hash_table_remove (startup_data->pending_files, file);
+
+ if (g_hash_table_size (startup_data->pending_files) == 0)
+ {
+ NautilusPropertiesWindow *new_window;
+
+ new_window = create_properties_window (startup_data);
+
+ startup_data->window = new_window;
+
+ remove_pending (startup_data, FALSE, TRUE);
+
+ gtk_window_present (GTK_WINDOW (new_window));
+ g_signal_connect (GTK_WIDGET (new_window), "destroy",
+ G_CALLBACK (widget_on_destroy), startup_data);
+
+ /* We wish the label to be selectable, but not selected by default. */
+ gtk_label_select_region (GTK_LABEL (new_window->name_value_label), -1, -1);
+ }
+}
+
+void
+nautilus_properties_window_present (GList *original_files,
+ GtkWidget *parent_widget,
+ const gchar *startup_id,
+ NautilusPropertiesWindowCallback callback,
+ gpointer callback_data)
+{
+ GList *l, *next;
+ GtkWindow *parent_window;
+ StartupData *startup_data;
+ g_autolist (NautilusFile) target_files = NULL;
+ g_autofree char *pending_key = NULL;
+
+ g_return_if_fail (original_files != NULL);
+ g_return_if_fail (parent_widget == NULL || GTK_IS_WIDGET (parent_widget));
+
+ if (pending_lists == NULL)
+ {
+ pending_lists = g_hash_table_new (g_str_hash, g_str_equal);
+ }
+
+ pending_key = get_pending_key (original_files);
+
+ /* Look to see if we're already waiting for a window for this file. */
+ if (g_hash_table_lookup (pending_lists, pending_key) != NULL)
+ {
+ /* FIXME: No callback is done if this happen. In practice, it's a quite
+ * corner case
+ */
+ return;
+ }
+
+ target_files = get_target_file_list (original_files);
+
+ if (parent_widget)
+ {
+ parent_window = GTK_WINDOW (gtk_widget_get_ancestor (parent_widget, GTK_TYPE_WINDOW));
+ }
+ else
+ {
+ parent_window = NULL;
+ }
+
+ startup_data = startup_data_new (original_files,
+ target_files,
+ pending_key,
+ parent_widget,
+ parent_window,
+ startup_id,
+ callback,
+ callback_data,
+ NULL);
+
+ /* Wait until we can tell whether it's a directory before showing, since
+ * some one-time layout decisions depend on that info.
+ */
+
+ g_hash_table_insert (pending_lists, startup_data->pending_key, startup_data->pending_key);
+ if (parent_widget)
+ {
+ g_signal_connect (parent_widget, "destroy",
+ G_CALLBACK (parent_widget_destroyed_callback), startup_data);
+ }
+
+ eel_timed_wait_start
+ (cancel_create_properties_window_callback,
+ startup_data,
+ _("Creating Properties window."),
+ parent_window == NULL ? NULL : GTK_WINDOW (parent_window));
+
+ for (l = startup_data->target_files; l != NULL; l = next)
+ {
+ next = l->next;
+ nautilus_file_call_when_ready
+ (NAUTILUS_FILE (l->data),
+ NAUTILUS_FILE_ATTRIBUTE_INFO,
+ is_directory_ready_callback,
+ startup_data);
+ }
+}
+
+static void
+real_dispose (GObject *object)
+{
+ NautilusPropertiesWindow *self;
+
+ self = NAUTILUS_PROPERTIES_WINDOW (object);
+
+ unschedule_or_cancel_group_change (self);
+ unschedule_or_cancel_owner_change (self);
+
+ g_list_foreach (self->original_files,
+ (GFunc) nautilus_file_monitor_remove,
+ &self->original_files);
+ g_clear_list (&self->original_files, (GDestroyNotify) nautilus_file_unref);
+
+ g_list_foreach (self->target_files,
+ (GFunc) nautilus_file_monitor_remove,
+ &self->target_files);
+ g_clear_list (&self->target_files, (GDestroyNotify) nautilus_file_unref);
+
+ g_clear_list (&self->changed_files, (GDestroyNotify) nautilus_file_unref);
+
+ g_clear_handle_id (&self->deep_count_spinner_timeout_id, g_source_remove);
+
+ while (self->deep_count_files)
+ {
+ stop_deep_count_for_file (self, self->deep_count_files->data);
+ }
+
+ g_clear_list (&self->permission_rows, NULL);
+
+ g_clear_list (&self->change_permission_combos, NULL);
+
+ g_clear_pointer (&self->initial_permissions, g_hash_table_destroy);
+
+ g_clear_list (&self->value_fields, NULL);
+
+ g_clear_handle_id (&self->update_directory_contents_timeout_id, g_source_remove);
+ g_clear_handle_id (&self->update_files_timeout_id, g_source_remove);
+
+ G_OBJECT_CLASS (nautilus_properties_window_parent_class)->dispose (object);
+}
+
+static void
+real_finalize (GObject *object)
+{
+ NautilusPropertiesWindow *self;
+
+ self = NAUTILUS_PROPERTIES_WINDOW (object);
+
+ g_list_free_full (self->mime_list, g_free);
+
+ G_OBJECT_CLASS (nautilus_properties_window_parent_class)->finalize (object);
+}
+
+/* icon selection callback to set the image of the file object to the selected file */
+static void
+set_icon (const char *icon_uri,
+ NautilusPropertiesWindow *self)
+{
+ NautilusFile *file;
+ g_autofree gchar *icon_path = NULL;
+
+ g_assert (icon_uri != NULL);
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ icon_path = g_filename_from_uri (icon_uri, NULL, NULL);
+ /* we don't allow remote URIs */
+ if (icon_path != NULL)
+ {
+ GList *l;
+
+ for (l = self->original_files; l != NULL; l = l->next)
+ {
+ g_autofree gchar *file_uri = NULL;
+ g_autoptr (GFile) file_location = NULL;
+ g_autoptr (GFile) icon_location = NULL;
+ g_autofree gchar *real_icon_uri = NULL;
+
+ file = NAUTILUS_FILE (l->data);
+ file_uri = nautilus_file_get_uri (file);
+ file_location = nautilus_file_get_location (file);
+ icon_location = g_file_new_for_uri (icon_uri);
+
+ /* ’Tis a little bit of a misnomer. Actually a path. */
+ real_icon_uri = g_file_get_relative_path (icon_location,
+ file_location);
+
+ if (real_icon_uri == NULL)
+ {
+ real_icon_uri = g_strdup (icon_uri);
+ }
+
+ nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL, real_icon_uri);
+ }
+ }
+}
+
+static void
+custom_icon_file_chooser_response_cb (GtkDialog *dialog,
+ gint response,
+ NautilusPropertiesWindow *self)
+{
+ switch (response)
+ {
+ case GTK_RESPONSE_NO:
+ {
+ reset_icon (self);
+ }
+ break;
+
+ case GTK_RESPONSE_OK:
+ {
+ g_autoptr (GFile) location = NULL;
+ g_autofree gchar *uri = NULL;
+
+ location = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
+ if (location != NULL)
+ {
+ uri = g_file_get_uri (location);
+ set_icon (uri, self);
+ }
+ else
+ {
+ reset_icon (self);
+ }
+ }
+ break;
+
+ default:
+ {
+ }
+ break;
+ }
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+}
+
+static void
+select_image_button_callback (GtkWidget *widget,
+ NautilusPropertiesWindow *self)
+{
+ GtkWidget *dialog;
+ GtkFileFilter *filter;
+ GList *l;
+ NautilusFile *file;
+ gboolean revert_is_sensitive;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ dialog = self->icon_chooser;
+
+ if (dialog == NULL)
+ {
+ g_autoptr (GFile) pictures_location = NULL;
+ dialog = gtk_file_chooser_dialog_new (_("Select Custom Icon"), GTK_WINDOW (self),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ _("_Revert"), GTK_RESPONSE_NO,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Open"), GTK_RESPONSE_OK,
+ NULL);
+ pictures_location = g_file_new_for_path (g_get_user_special_dir (G_USER_DIRECTORY_PICTURES));
+ gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog),
+ pictures_location,
+ NULL);
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_add_pixbuf_formats (filter);
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+ self->icon_chooser = dialog;
+
+ g_object_add_weak_pointer (G_OBJECT (dialog),
+ (gpointer *) &self->icon_chooser);
+ }
+
+ /* it's likely that the user wants to pick an icon that is inside a local directory */
+ if (g_list_length (self->original_files) == 1)
+ {
+ file = NAUTILUS_FILE (self->original_files->data);
+
+ if (nautilus_file_is_directory (file))
+ {
+ g_autoptr (GFile) image_location = NULL;
+
+ image_location = nautilus_file_get_location (file);
+
+ if (image_location != NULL)
+ {
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog),
+ image_location,
+ NULL);
+ }
+ }
+ }
+
+ revert_is_sensitive = FALSE;
+ for (l = self->original_files; l != NULL; l = l->next)
+ {
+ g_autofree gchar *image_path = NULL;
+
+ file = NAUTILUS_FILE (l->data);
+ image_path = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL);
+ revert_is_sensitive = (image_path != NULL);
+
+ 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), self);
+ gtk_widget_show (dialog);
+}
+
+static void
+nautilus_properties_window_class_init (NautilusPropertiesWindowClass *klass)
+{
+ GtkWidgetClass *widget_class;
+ GObjectClass *oclass;
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+ oclass = G_OBJECT_CLASS (klass);
+ oclass->dispose = real_dispose;
+ oclass->finalize = real_finalize;
+
+ gtk_widget_class_add_binding (widget_class,
+ GDK_KEY_Escape, 0,
+ (GtkShortcutFunc) gtk_window_close, NULL);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-properties-window.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, page_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_image);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_button_image);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, star_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, name_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, type_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, type_file_system_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, size_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, contents_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, contents_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, contents_spinner);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, bottom_prompt_seperator);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_list_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_space_level_bar);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_space_used_value);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_space_free_value);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_space_capacity_value);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, locations_list_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, link_target_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, link_target_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, parent_folder_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, parent_folder_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, trashed_list_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, trashed_on_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, original_folder_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, times_list_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, modified_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, modified_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, created_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, created_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, accessed_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, accessed_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, permissions_navigation_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, permissions_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, extension_models_list_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, free_space_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, permissions_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, not_the_owner_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, unknown_permissions_page);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_access_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_folder_access_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_file_access_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_access_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_folder_access_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_file_access_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_access_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_folder_access_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_file_access_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, execution_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, execution_switch);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, security_context_list_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, security_context_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, change_permissions_button_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, change_permissions_button);
+
+ gtk_widget_class_bind_template_callback (widget_class, star_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, open_in_disks);
+ gtk_widget_class_bind_template_callback (widget_class, open_parent_folder);
+ gtk_widget_class_bind_template_callback (widget_class, open_link_target);
+ gtk_widget_class_bind_template_callback (widget_class, navigate_main_page);
+ gtk_widget_class_bind_template_callback (widget_class, navigate_permissions_page);
+}
+
+static void
+nautilus_properties_window_init (NautilusPropertiesWindow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}