thunar-usagebars/thunar/thunar-permissions-chooser.c
4aiman e969f94310 thunar 4.20.2 upstream
Tree of tag thunar-4.20.2 (commit 11a519f) from https://gitlab.xfce.org/xfce/thunar.
Upstream history is not included here to keep the repository small.
2026-11-03 22:42:48 +03:00

1474 lines
53 KiB
C

/* vi:set et ai sw=2 sts=2 ts=2: */
/*-
* Copyright (c) 2005-2006 Benedikt Meurer <benny@xfce.org>
* Copyright (c) 2009 Jannis Pohlmann <jannis@xfce.org>
* Copyright (c) 2012 Nick Schermer <nick@xfce.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "thunar/thunar-dialogs.h"
#include "thunar/thunar-enum-types.h"
#include "thunar/thunar-gobject-extensions.h"
#include "thunar/thunar-gtk-extensions.h"
#include "thunar/thunar-io-jobs.h"
#include "thunar/thunar-pango-extensions.h"
#include "thunar/thunar-permissions-chooser.h"
#include "thunar/thunar-preferences.h"
#include "thunar/thunar-private.h"
#include "thunar/thunar-user.h"
#include <exo/exo.h>
#include <libxfce4ui/libxfce4ui.h>
/* Use native strlcpy() if available */
#if defined(HAVE_STRLCPY)
#define g_strlcpy(dst, src, size) (strlcpy ((dst), (src), (size)))
#endif
/* Property identifiers */
enum
{
PROP_0,
PROP_FILES,
PROP_MUTABLE,
};
/* Column identifiers for the group combo box */
enum
{
THUNAR_PERMISSIONS_STORE_COLUMN_NAME,
THUNAR_PERMISSIONS_STORE_COLUMN_GID,
THUNAR_PERMISSIONS_STORE_N_COLUMNS,
};
static void
thunar_permissions_chooser_finalize (GObject *object);
static void
thunar_permissions_chooser_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static void
thunar_permissions_chooser_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static gint
thunar_permissions_chooser_ask_recursive (ThunarPermissionsChooser *chooser);
static void
thunar_permissions_chooser_change_group (ThunarPermissionsChooser *chooser,
guint32 gid);
static gboolean
thunar_permissions_chooser_change_mode (ThunarPermissionsChooser *chooser,
ThunarFileMode dir_mask,
ThunarFileMode dir_mode,
ThunarFileMode file_mask,
ThunarFileMode file_mode);
static void
thunar_permissions_chooser_access_changed (ThunarPermissionsChooser *chooser,
GtkWidget *combo);
static void
thunar_permissions_chooser_file_changed (ThunarPermissionsChooser *chooser);
static void
thunar_permissions_chooser_group_changed (ThunarPermissionsChooser *chooser,
GtkWidget *combo);
static void
thunar_permissions_chooser_program_toggled (ThunarPermissionsChooser *chooser,
GtkWidget *button);
static void
thunar_permissions_chooser_fixperm_clicked (ThunarPermissionsChooser *chooser,
GtkWidget *button);
static ThunarJobResponse
thunar_permissions_chooser_job_ask (ThunarPermissionsChooser *chooser,
const gchar *message,
ThunarJobResponse choices,
ThunarJob *job);
static void
thunar_permissions_chooser_job_cancel (ThunarPermissionsChooser *chooser);
static void
thunar_permissions_chooser_job_error (ThunarPermissionsChooser *chooser,
GError *error,
ThunarJob *job);
static void
thunar_permissions_chooser_job_finished (ThunarPermissionsChooser *chooser,
ThunarJob *job);
static void
thunar_permissions_chooser_job_percent (ThunarPermissionsChooser *chooser,
gdouble percent,
ThunarJob *job);
static void
thunar_permissions_chooser_job_start (ThunarPermissionsChooser *chooser,
ThunarJob *job,
gboolean recursive);
static gboolean
thunar_permissions_chooser_row_separator (GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data);
static GList *
thunar_permissions_chooser_get_files (ThunarPermissionsChooser *chooser);
static void
thunar_permissions_chooser_set_files (ThunarPermissionsChooser *chooser,
GList *files);
struct _ThunarPermissionsChooserClass
{
GtkVBoxClass __parent__;
};
struct _ThunarPermissionsChooser
{
GtkVBox __parent__;
GList *files;
/* the main grid widget, which contains everything but the job control stuff */
GtkWidget *grid;
GtkWidget *user_label;
GtkWidget *group_combo;
GtkWidget *access_combos[3];
GtkWidget *program_button;
GtkWidget *fixperm_label;
GtkWidget *fixperm_button;
/* job control stuff */
ThunarJob *job;
GtkWidget *job_progress;
};
G_DEFINE_TYPE (ThunarPermissionsChooser, thunar_permissions_chooser, GTK_TYPE_BOX)
static void
thunar_permissions_chooser_class_init (ThunarPermissionsChooserClass *klass)
{
GObjectClass *gobject_class;
gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = thunar_permissions_chooser_finalize;
gobject_class->get_property = thunar_permissions_chooser_get_property;
gobject_class->set_property = thunar_permissions_chooser_set_property;
/**
* ThunarPermissionsChooser:file:
*
* The #ThunarFile whose permissions will be edited/viewed.
**/
g_object_class_install_property (gobject_class,
PROP_FILES,
g_param_spec_boxed ("files", "files", "files",
THUNARX_TYPE_FILE_INFO_LIST,
EXO_PARAM_READWRITE));
/**
* ThunarPermissionsChooser:mutable:
*
* Whether the current #ThunarFile<!---->s permissions are
* mutable.
**/
g_object_class_install_property (gobject_class,
PROP_MUTABLE,
g_param_spec_boolean ("mutable",
"mutable",
"mutable",
FALSE,
EXO_PARAM_READABLE));
}
static void
thunar_permissions_chooser_init (ThunarPermissionsChooser *chooser)
{
GtkCellRenderer *renderer_text;
GtkWidget *separator;
GtkWidget *button;
GtkWidget *label;
GtkWidget *image;
GtkWidget *hbox;
gint row = 0;
/* setup the chooser */
gtk_container_set_border_width (GTK_CONTAINER (chooser), 12);
gtk_orientable_set_orientation (GTK_ORIENTABLE (chooser), GTK_ORIENTATION_VERTICAL);
/* allocate the shared renderer for the various combo boxes */
renderer_text = gtk_cell_renderer_text_new ();
chooser->grid = gtk_grid_new ();
gtk_grid_set_column_spacing (GTK_GRID (chooser->grid), 12);
gtk_grid_set_row_spacing (GTK_GRID (chooser->grid), 6);
gtk_box_pack_start (GTK_BOX (chooser), chooser->grid, TRUE, TRUE, 0);
gtk_widget_show (chooser->grid);
label = gtk_label_new (_("Owner:"));
gtk_label_set_xalign (GTK_LABEL (label), 1.0f);
gtk_label_set_attributes (GTK_LABEL (label), thunar_pango_attr_list_bold ());
gtk_grid_attach (GTK_GRID (chooser->grid), label, 0, row, 1, 1);
gtk_widget_show (label);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
gtk_widget_set_hexpand (hbox, TRUE);
gtk_grid_attach (GTK_GRID (chooser->grid), hbox, 1, row, 1, 1);
gtk_widget_show (hbox);
chooser->user_label = gtk_label_new (_("Unknown"));
gtk_label_set_xalign (GTK_LABEL (chooser->user_label), 0.0f);
gtk_box_pack_start (GTK_BOX (hbox), chooser->user_label, TRUE, TRUE, 0);
thunar_gtk_label_set_a11y_relation (GTK_LABEL (label), chooser->user_label);
gtk_widget_show (chooser->user_label);
row += 1;
label = gtk_label_new_with_mnemonic (_("_Access:"));
gtk_label_set_xalign (GTK_LABEL (label), 1.0f);
gtk_label_set_attributes (GTK_LABEL (label), thunar_pango_attr_list_bold ());
gtk_grid_attach (GTK_GRID (chooser->grid), label, 0, row, 1, 1);
gtk_widget_show (label);
chooser->access_combos[2] = gtk_combo_box_new ();
gtk_label_set_mnemonic_widget (GTK_LABEL (label), chooser->access_combos[2]);
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (chooser->access_combos[2]), renderer_text, TRUE);
gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (chooser->access_combos[2]), renderer_text, "text", 0);
g_object_bind_property (G_OBJECT (chooser), "mutable",
G_OBJECT (chooser->access_combos[2]), "sensitive",
G_BINDING_SYNC_CREATE);
g_signal_connect_swapped (G_OBJECT (chooser->access_combos[2]), "changed", G_CALLBACK (thunar_permissions_chooser_access_changed), chooser);
gtk_widget_set_hexpand (chooser->access_combos[2], TRUE);
gtk_grid_attach (GTK_GRID (chooser->grid), chooser->access_combos[2], 1, row, 1, 1);
thunar_gtk_label_set_a11y_relation (GTK_LABEL (label), chooser->access_combos[2]);
gtk_widget_show (chooser->access_combos[2]);
row += 1;
separator = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
g_object_set (separator, "margin", 4, NULL);
gtk_grid_attach (GTK_GRID (chooser->grid), separator, 0, row, 2, 1);
gtk_widget_show (separator);
row += 1;
label = gtk_label_new_with_mnemonic (_("Gro_up:"));
gtk_label_set_xalign (GTK_LABEL (label), 1.0f);
gtk_label_set_attributes (GTK_LABEL (label), thunar_pango_attr_list_bold ());
gtk_grid_attach (GTK_GRID (chooser->grid), label, 0, row, 1, 1);
gtk_widget_show (label);
chooser->group_combo = gtk_combo_box_new ();
gtk_label_set_mnemonic_widget (GTK_LABEL (label), chooser->group_combo);
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (chooser->group_combo), renderer_text, TRUE);
gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (chooser->group_combo), renderer_text, "text", THUNAR_PERMISSIONS_STORE_COLUMN_NAME);
gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (chooser->group_combo), thunar_permissions_chooser_row_separator, NULL, NULL);
g_object_bind_property (G_OBJECT (chooser), "mutable",
G_OBJECT (chooser->group_combo), "sensitive",
G_BINDING_SYNC_CREATE);
g_signal_connect_swapped (G_OBJECT (chooser->group_combo), "changed", G_CALLBACK (thunar_permissions_chooser_group_changed), chooser);
gtk_widget_set_hexpand (chooser->group_combo, TRUE);
gtk_grid_attach (GTK_GRID (chooser->grid), chooser->group_combo, 1, row, 1, 1);
thunar_gtk_label_set_a11y_relation (GTK_LABEL (label), chooser->group_combo);
gtk_widget_show (chooser->group_combo);
row += 1;
label = gtk_label_new_with_mnemonic (_("Acc_ess:"));
gtk_label_set_xalign (GTK_LABEL (label), 1.0f);
gtk_label_set_attributes (GTK_LABEL (label), thunar_pango_attr_list_bold ());
gtk_grid_attach (GTK_GRID (chooser->grid), label, 0, row, 1, 1);
gtk_widget_show (label);
chooser->access_combos[1] = gtk_combo_box_new ();
gtk_label_set_mnemonic_widget (GTK_LABEL (label), chooser->access_combos[1]);
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (chooser->access_combos[1]), renderer_text, TRUE);
gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (chooser->access_combos[1]), renderer_text, "text", 0);
g_object_bind_property (G_OBJECT (chooser), "mutable",
G_OBJECT (chooser->access_combos[1]), "sensitive",
G_BINDING_SYNC_CREATE);
g_signal_connect_swapped (G_OBJECT (chooser->access_combos[1]), "changed", G_CALLBACK (thunar_permissions_chooser_access_changed), chooser);
gtk_widget_set_hexpand (chooser->access_combos[1], TRUE);
gtk_grid_attach (GTK_GRID (chooser->grid), chooser->access_combos[1], 1, row, 1, 1);
thunar_gtk_label_set_a11y_relation (GTK_LABEL (label), chooser->access_combos[1]);
gtk_widget_show (chooser->access_combos[1]);
row += 1;
separator = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
g_object_set (separator, "margin", 4, NULL);
gtk_grid_attach (GTK_GRID (chooser->grid), separator, 0, row, 2, 1);
gtk_widget_show (separator);
row += 1;
label = gtk_label_new (_("Others"));
gtk_label_set_xalign (GTK_LABEL (label), 1.0f);
gtk_label_set_attributes (GTK_LABEL (label), thunar_pango_attr_list_bold ());
gtk_grid_attach (GTK_GRID (chooser->grid), label, 0, row, 1, 1);
gtk_widget_show (label);
row += 1;
label = gtk_label_new_with_mnemonic (_("Acce_ss:"));
gtk_label_set_xalign (GTK_LABEL (label), 1.0f);
gtk_label_set_attributes (GTK_LABEL (label), thunar_pango_attr_list_bold ());
gtk_grid_attach (GTK_GRID (chooser->grid), label, 0, row, 1, 1);
gtk_widget_show (label);
chooser->access_combos[0] = gtk_combo_box_new ();
gtk_label_set_mnemonic_widget (GTK_LABEL (label), chooser->access_combos[0]);
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (chooser->access_combos[0]), renderer_text, TRUE);
gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (chooser->access_combos[0]), renderer_text, "text", 0);
g_object_bind_property (G_OBJECT (chooser), "mutable",
G_OBJECT (chooser->access_combos[0]), "sensitive",
G_BINDING_SYNC_CREATE);
g_signal_connect_swapped (G_OBJECT (chooser->access_combos[0]), "changed", G_CALLBACK (thunar_permissions_chooser_access_changed), chooser);
gtk_widget_set_hexpand (chooser->access_combos[0], TRUE);
gtk_grid_attach (GTK_GRID (chooser->grid), chooser->access_combos[0], 1, row, 1, 1);
thunar_gtk_label_set_a11y_relation (GTK_LABEL (label), chooser->access_combos[0]);
gtk_widget_show (chooser->access_combos[0]);
row += 1;
separator = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
g_object_set (separator, "margin", 4, NULL);
gtk_grid_attach (GTK_GRID (chooser->grid), separator, 0, row, 2, 1);
gtk_widget_show (separator);
row += 1;
label = gtk_label_new (_("Program:"));
gtk_label_set_xalign (GTK_LABEL (label), 1.0f);
gtk_label_set_attributes (GTK_LABEL (label), thunar_pango_attr_list_bold ());
gtk_grid_attach (GTK_GRID (chooser->grid), label, 0, row, 1, 1);
gtk_widget_show (label);
chooser->program_button = gtk_check_button_new_with_mnemonic (_("Allow this file to _run as a program"));
g_object_bind_property (G_OBJECT (chooser->program_button), "visible",
G_OBJECT (label), "visible",
G_BINDING_SYNC_CREATE);
g_object_bind_property (G_OBJECT (chooser), "mutable",
G_OBJECT (chooser->program_button), "sensitive",
G_BINDING_SYNC_CREATE);
g_signal_connect_swapped (G_OBJECT (chooser->program_button), "toggled", G_CALLBACK (thunar_permissions_chooser_program_toggled), chooser);
gtk_grid_attach (GTK_GRID (chooser->grid), chooser->program_button, 1, row, 1, 1);
thunar_gtk_label_set_a11y_relation (GTK_LABEL (label), chooser->program_button);
gtk_widget_show (chooser->program_button);
row += 1;
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
g_object_bind_property (G_OBJECT (chooser), "mutable",
G_OBJECT (hbox), "sensitive",
G_BINDING_SYNC_CREATE);
g_object_bind_property (G_OBJECT (chooser->program_button), "visible",
G_OBJECT (hbox), "visible",
G_BINDING_SYNC_CREATE);
gtk_grid_attach (GTK_GRID (chooser->grid), hbox, 1, row, 1, 1);
gtk_widget_show (hbox);
image = gtk_image_new_from_icon_name ("dialog-warning", GTK_ICON_SIZE_LARGE_TOOLBAR);
gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
gtk_widget_show (image);
label = gtk_label_new (_("Allowing untrusted programs to run presents a security risk to your system."));
gtk_label_set_xalign (GTK_LABEL (label), 0.0f);
gtk_label_set_attributes (GTK_LABEL (label), thunar_pango_attr_list_small_italic ());
gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
gtk_widget_show (label);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
g_object_bind_property (G_OBJECT (chooser), "mutable",
G_OBJECT (hbox), "sensitive",
G_BINDING_SYNC_CREATE);
gtk_grid_attach (GTK_GRID (chooser->grid), hbox, 1, row, 1, 1);
gtk_widget_show (hbox);
image = gtk_image_new_from_icon_name ("dialog-warning", GTK_ICON_SIZE_LARGE_TOOLBAR);
gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
gtk_widget_show (image);
chooser->fixperm_label = gtk_label_new (_("The folder permissions are inconsistent, you may not be able to work with files in this folder."));
gtk_label_set_xalign (GTK_LABEL (chooser->fixperm_label), 0.0f);
gtk_label_set_attributes (GTK_LABEL (chooser->fixperm_label), thunar_pango_attr_list_small_italic ());
gtk_label_set_line_wrap (GTK_LABEL (chooser->fixperm_label), TRUE);
g_object_bind_property (G_OBJECT (chooser->fixperm_label), "visible",
G_OBJECT (hbox), "visible",
G_BINDING_SYNC_CREATE);
gtk_box_pack_start (GTK_BOX (hbox), chooser->fixperm_label, TRUE, TRUE, 0);
gtk_widget_show (chooser->fixperm_label);
row += 1;
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
gtk_grid_attach (GTK_GRID (chooser->grid), hbox, 1, row, 1, 1);
gtk_widget_show (hbox);
chooser->fixperm_button = gtk_button_new_with_mnemonic (_("Correct _folder permissions..."));
gtk_widget_set_tooltip_text (chooser->fixperm_button, _("Click here to automatically fix the folder permissions."));
g_signal_connect_swapped (G_OBJECT (chooser->fixperm_button), "clicked", G_CALLBACK (thunar_permissions_chooser_fixperm_clicked), chooser);
g_object_bind_property (G_OBJECT (chooser->fixperm_button), "visible",
G_OBJECT (hbox), "visible",
G_BINDING_SYNC_CREATE);
gtk_box_pack_end (GTK_BOX (hbox), chooser->fixperm_button, FALSE, FALSE, 0);
gtk_widget_show (chooser->fixperm_button);
/* the job control stuff */
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
gtk_box_pack_start (GTK_BOX (chooser), hbox, FALSE, FALSE, 0);
chooser->job_progress = gtk_progress_bar_new ();
gtk_progress_bar_set_text (GTK_PROGRESS_BAR (chooser->job_progress), _("Please wait..."));
g_object_bind_property (G_OBJECT (chooser->job_progress), "visible",
G_OBJECT (hbox), "visible",
G_BINDING_SYNC_CREATE);
gtk_box_pack_start (GTK_BOX (hbox), chooser->job_progress, TRUE, TRUE, 0);
button = gtk_button_new ();
gtk_widget_set_tooltip_text (button, _("Stop applying permissions recursively."));
g_signal_connect_swapped (G_OBJECT (button), "clicked", G_CALLBACK (thunar_permissions_chooser_job_cancel), chooser);
gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
gtk_widget_show (button);
}
static void
thunar_permissions_chooser_finalize (GObject *object)
{
ThunarPermissionsChooser *chooser = THUNAR_PERMISSIONS_CHOOSER (object);
/* cancel any pending job */
if (G_UNLIKELY (chooser->job != NULL))
{
/* cancel the job (if not already done) */
exo_job_cancel (EXO_JOB (chooser->job));
/* disconnect from the job */
g_signal_handlers_disconnect_by_data (chooser->job, chooser);
g_object_unref (chooser->job);
chooser->job = NULL;
}
/* drop the reference on the file (if any) */
thunar_permissions_chooser_set_files (chooser, NULL);
(*G_OBJECT_CLASS (thunar_permissions_chooser_parent_class)->finalize) (object);
}
static void
thunar_permissions_chooser_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
ThunarPermissionsChooser *chooser = THUNAR_PERMISSIONS_CHOOSER (object);
GList *lp;
switch (prop_id)
{
case PROP_FILES:
g_value_set_boxed (value, thunar_permissions_chooser_get_files (chooser));
break;
case PROP_MUTABLE:
for (lp = chooser->files; lp != NULL; lp = lp->next)
if (!thunar_file_is_chmodable (THUNAR_FILE (lp->data)))
break;
g_value_set_boolean (value, (chooser->files != NULL) && (lp == NULL));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
thunar_permissions_chooser_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
ThunarPermissionsChooser *chooser = THUNAR_PERMISSIONS_CHOOSER (object);
switch (prop_id)
{
case PROP_FILES:
thunar_permissions_chooser_set_files (chooser, g_value_get_boxed (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
thunar_permissions_chooser_ask_recursive (ThunarPermissionsChooser *chooser)
{
ThunarRecursivePermissionsMode mode;
ThunarPreferences *preferences;
GtkWidget *toplevel;
GtkWidget *dialog;
GtkWidget *button;
GtkWidget *label;
GtkWidget *image;
GtkWidget *hbox;
GtkWidget *vbox;
gint response;
/* grab a reference on the preferences */
preferences = thunar_preferences_get ();
/* determine the current recursive permissions mode */
g_object_get (G_OBJECT (preferences), "misc-recursive-permissions", &mode, NULL);
/* check if we should ask the user first */
if (G_UNLIKELY (mode == THUNAR_RECURSIVE_PERMISSIONS_ASK))
{
/* determine the toplevel widget for the chooser */
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (chooser));
/* allocate the question dialog */
dialog = gtk_dialog_new_with_buttons (_("Question"), GTK_WINDOW (toplevel),
GTK_DIALOG_DESTROY_WITH_PARENT
| GTK_DIALOG_MODAL,
_("_Cancel"), GTK_RESPONSE_CANCEL,
_("_No"), GTK_RESPONSE_NO,
_("_Yes"), GTK_RESPONSE_YES,
NULL);
gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_YES);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
gtk_container_set_border_width (GTK_CONTAINER (hbox), 8);
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), hbox, TRUE, TRUE, 0);
gtk_widget_show (hbox);
image = gtk_image_new_from_icon_name ("dialog-question", GTK_ICON_SIZE_DIALOG);
gtk_widget_set_halign (image, GTK_ALIGN_CENTER);
gtk_widget_set_valign (image, GTK_ALIGN_START);
gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
gtk_widget_show (image);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
gtk_widget_show (vbox);
label = gtk_label_new (_("Apply recursively?"));
gtk_label_set_xalign (GTK_LABEL (label), 0.0f);
gtk_label_set_attributes (GTK_LABEL (label), thunar_pango_attr_list_big_bold ());
gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
gtk_widget_show (label);
label = gtk_label_new (_("Do you want to apply your changes recursively to\nall files and subfolders below the selected folder?"));
gtk_label_set_xalign (GTK_LABEL (label), 0.0f);
gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
gtk_widget_show (label);
button = gtk_check_button_new_with_mnemonic (_("Do _not ask me again"));
gtk_widget_set_tooltip_text (button, _("If you select this option your choice will be remembered and you won't be asked "
"again. You can use the preferences dialog to alter your choice afterwards."));
gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
gtk_widget_show (button);
/* run the dialog and save the selected option (if requested) */
response = gtk_dialog_run (GTK_DIALOG (dialog));
switch (response)
{
case GTK_RESPONSE_YES:
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
g_object_set (G_OBJECT (preferences), "misc-recursive-permissions", THUNAR_RECURSIVE_PERMISSIONS_ALWAYS, NULL);
break;
case GTK_RESPONSE_NO:
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
g_object_set (G_OBJECT (preferences), "misc-recursive-permissions", THUNAR_RECURSIVE_PERMISSIONS_NEVER, NULL);
break;
default:
break;
}
/* destroy the dialog resources */
gtk_widget_destroy (dialog);
}
else if (mode == THUNAR_RECURSIVE_PERMISSIONS_ALWAYS)
{
response = GTK_RESPONSE_YES;
}
else
{
response = GTK_RESPONSE_NO;
}
/* release the reference on the preferences */
g_object_unref (G_OBJECT (preferences));
return response;
}
static gboolean
thunar_permissions_chooser_has_directory (ThunarPermissionsChooser *chooser)
{
GList *lp;
for (lp = chooser->files; lp != NULL; lp = lp->next)
if (thunar_file_is_directory (THUNAR_FILE (lp->data)))
return TRUE;
return FALSE;
}
static gboolean
thunar_permissions_chooser_is_fixable_directory (ThunarFile *file)
{
ThunarFileMode mode;
_thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
if (!thunar_file_is_directory (file)
|| !thunar_file_is_chmodable (file))
return FALSE;
mode = thunar_file_get_mode (file);
return ((mode & 0111) != ((mode >> 2) & 0111));
}
static gboolean
thunar_permissions_chooser_has_fixable_directory (ThunarPermissionsChooser *chooser)
{
GList *lp;
for (lp = chooser->files; lp != NULL; lp = lp->next)
if (thunar_permissions_chooser_is_fixable_directory (THUNAR_FILE (lp->data)))
return TRUE;
return FALSE;
}
/* free returned list with g_list_free_full (file_list, g_object_unref); */
static GList *
thunar_permissions_chooser_get_file_list (ThunarPermissionsChooser *chooser)
{
GList *file_list = NULL;
GList *lp;
GFile *gfile;
for (lp = chooser->files; lp != NULL; lp = lp->next)
{
gfile = thunar_file_get_file (THUNAR_FILE (lp->data));
_thunar_assert (G_IS_FILE (gfile));
file_list = g_list_prepend (file_list, g_object_ref (G_OBJECT (gfile)));
}
return file_list;
}
static void
thunar_permissions_chooser_change_group (ThunarPermissionsChooser *chooser,
guint32 gid)
{
ThunarJob *job;
gboolean recursive = FALSE;
gint response;
GList *file_list;
_thunar_return_if_fail (THUNAR_IS_PERMISSIONS_CHOOSER (chooser));
_thunar_return_if_fail (chooser->files != NULL);
/* check if we should operate recursively */
if (thunar_permissions_chooser_has_directory (chooser))
{
response = thunar_permissions_chooser_ask_recursive (chooser);
switch (response)
{
case GTK_RESPONSE_YES:
recursive = TRUE;
break;
case GTK_RESPONSE_NO:
recursive = FALSE;
break;
default: /* cancelled by the user */
thunar_permissions_chooser_file_changed (chooser);
return;
}
}
/* try to allocate the new job */
file_list = thunar_permissions_chooser_get_file_list (chooser);
job = thunar_io_jobs_change_group (file_list, gid, recursive);
exo_job_launch (EXO_JOB (job));
thunar_permissions_chooser_job_start (chooser, job, recursive);
g_list_free_full (file_list, g_object_unref);
g_object_unref (job);
}
static gboolean
thunar_permissions_chooser_change_mode (ThunarPermissionsChooser *chooser,
ThunarFileMode dir_mask,
ThunarFileMode dir_mode,
ThunarFileMode file_mask,
ThunarFileMode file_mode)
{
ThunarJob *job;
gboolean recursive = FALSE;
gint response;
GList *file_list;
_thunar_return_val_if_fail (THUNAR_IS_PERMISSIONS_CHOOSER (chooser), FALSE);
_thunar_return_val_if_fail (chooser->files != NULL, FALSE);
/* check if we should operate recursively */
if (thunar_permissions_chooser_has_directory (chooser))
{
response = thunar_permissions_chooser_ask_recursive (chooser);
switch (response)
{
case GTK_RESPONSE_YES:
recursive = TRUE;
break;
case GTK_RESPONSE_NO:
recursive = FALSE;
break;
default: /* cancelled by the user */
thunar_permissions_chooser_file_changed (chooser);
return FALSE;
}
}
/* try to allocate the new job */
file_list = thunar_permissions_chooser_get_file_list (chooser);
job = thunar_io_jobs_change_mode (file_list, dir_mask, dir_mode, file_mask, file_mode, recursive);
exo_job_launch (EXO_JOB (job));
thunar_permissions_chooser_job_start (chooser, job, recursive);
g_list_free_full (file_list, g_object_unref);
g_object_unref (job);
return TRUE;
}
static void
thunar_permissions_chooser_access_changed (ThunarPermissionsChooser *chooser,
GtkWidget *combo)
{
ThunarFileMode file_mask;
ThunarFileMode file_mode;
ThunarFileMode dir_mask;
ThunarFileMode dir_mode;
guint n;
gint active_mode;
GtkTreeModel *model;
GtkTreeIter iter;
_thunar_return_if_fail (THUNAR_IS_PERMISSIONS_CHOOSER (chooser));
_thunar_return_if_fail (GTK_IS_COMBO_BOX (combo));
/* leave if the active mode is varying */
active_mode = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
if (active_mode > 3)
return;
/* determine the new mode from the combo box */
for (n = 0; n < G_N_ELEMENTS (chooser->access_combos) && chooser->access_combos[n] != combo; ++n)
;
dir_mode = file_mode = (active_mode << 1) << (n * 3);
dir_mask = file_mask = 0006 << (n * 3);
/* keep exec bit in sync for folders */
if (thunar_permissions_chooser_has_directory (chooser))
{
/* if either read or write is, set exec as well, else unset exec as well */
if ((dir_mode & (0004 << (n * 3))) != 0)
dir_mode |= (0001 << (n * 3));
dir_mask = 0007 << (n * 3);
}
/* change the permissions */
if (thunar_permissions_chooser_change_mode (chooser, dir_mask, dir_mode, file_mask, file_mode))
{
/* for better feedback remove the varying item */
model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
if (gtk_tree_model_get_iter_from_string (model, &iter, "4"))
gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
}
}
static gint
group_compare (gconstpointer group_a,
gconstpointer group_b,
gpointer group_primary)
{
guint32 group_primary_id;
guint32 group_a_id = thunar_group_get_id (THUNAR_GROUP (group_a));
guint32 group_b_id = thunar_group_get_id (THUNAR_GROUP (group_b));
/* check if the groups are equal */
if (group_a_id == group_b_id)
return 0;
/* the primary group is always sorted first */
if (group_primary != NULL)
{
group_primary_id = thunar_group_get_id (THUNAR_GROUP (group_primary));
if (group_a_id == group_primary_id)
return -1;
else if (group_b_id == group_primary_id)
return 1;
}
/* system groups (< 100) are always sorted last */
if (group_a_id < 100 && group_b_id >= 100)
return 1;
else if (group_b_id < 100 && group_a_id >= 100)
return -1;
/* otherwise just sort by name */
return g_ascii_strcasecmp (thunar_group_get_name (THUNAR_GROUP (group_a)),
thunar_group_get_name (THUNAR_GROUP (group_b)));
}
static void
thunar_permissions_chooser_file_changed (ThunarPermissionsChooser *chooser)
{
ThunarFile *file;
ThunarUserManager *user_manager;
ThunarFileMode mode = 0;
ThunarGroup *group = NULL;
ThunarUser *user = NULL;
GtkListStore *store;
GtkTreeIter iter;
const gchar *user_name;
const gchar *real_name;
GList *groups = NULL;
GList *lp;
gchar buffer[1024];
guint n;
guint n_files = 0;
gint modes[3] = {
0,
};
gint file_modes[3];
GtkListStore *access_store;
_thunar_return_if_fail (THUNAR_IS_PERMISSIONS_CHOOSER (chooser));
/* compare multiple files */
for (lp = chooser->files; lp != NULL; lp = lp->next)
{
file = THUNAR_FILE (lp->data);
/* transform the file modes in r/w/r+w for each group */
mode = thunar_file_get_mode (file);
for (n = 0; n < 3; n++)
file_modes[n] = ((mode >> (n * 3)) & 0007) >> 1;
if (n_files == 0)
{
/* get information of the first file */
user = thunar_file_get_user (file);
group = thunar_file_get_group (file);
for (n = 0; n < 3; n++)
modes[n] = file_modes[n];
}
else
{
/* unset the file info if it is different from the other files */
if (user != NULL && user != thunar_file_get_user (file))
user = NULL;
if (group != NULL && group != thunar_file_get_group (file))
group = NULL;
for (n = 0; n < 3; n++)
if (file_modes[n] != modes[n])
modes[n] = 4;
}
n_files++;
}
file = THUNAR_FILE (chooser->files->data);
/* allocate a new store for the group combo box */
g_signal_handlers_block_by_func (G_OBJECT (chooser->group_combo), thunar_permissions_chooser_group_changed, chooser);
store = gtk_list_store_new (THUNAR_PERMISSIONS_STORE_N_COLUMNS, G_TYPE_STRING, G_TYPE_UINT);
gtk_combo_box_set_model (GTK_COMBO_BOX (chooser->group_combo), GTK_TREE_MODEL (store));
/* determine the owner of the new file */
if (G_LIKELY (user != NULL))
{
/* determine sane display name for the owner */
user_name = thunar_user_get_name (user);
real_name = thunar_user_get_real_name (user);
if (G_LIKELY (real_name != NULL))
g_snprintf (buffer, sizeof (buffer), "%s (%s)", real_name, user_name);
else
g_strlcpy (buffer, user_name, sizeof (buffer));
gtk_label_set_text (GTK_LABEL (chooser->user_label), buffer);
}
else
{
gtk_label_set_text (GTK_LABEL (chooser->user_label),
n_files > 1 ? _("Mixed file owners") :_("Unknown file owner"));
}
/* check if we have superuser privileges */
if (G_UNLIKELY (geteuid () == 0))
{
/* determine all groups in the system */
user_manager = thunar_user_manager_get_default ();
groups = thunar_user_manager_get_all_groups (user_manager);
g_object_unref (G_OBJECT (user_manager));
}
else
{
if (G_UNLIKELY (user == NULL && n_files > 1))
{
/* get groups of the active user */
user_manager = thunar_user_manager_get_default ();
user = thunar_user_manager_get_user_by_id (user_manager, geteuid ());
g_object_unref (G_OBJECT (user_manager));
}
/* determine the groups for the user and take a copy */
if (G_LIKELY (user != NULL))
{
groups = g_list_copy (thunar_user_get_groups (user));
g_list_foreach (groups, (GFunc) (void (*) (void)) g_object_ref, NULL);
}
}
/* make sure that the group list includes the file group */
if (G_UNLIKELY (group != NULL && g_list_find (groups, group) == NULL))
groups = g_list_prepend (groups, g_object_ref (G_OBJECT (group)));
/* sort the groups according to group_compare() */
groups = g_list_sort_with_data (groups, group_compare, group);
/* add the groups to the store */
for (lp = groups, n = 0; lp != NULL; lp = lp->next)
{
/* append a separator after the primary group and after the user-groups (not system groups) */
if (group != NULL
&& thunar_group_get_id (groups->data) == thunar_group_get_id (group)
&& lp != groups && n == 0)
{
gtk_list_store_append (store, &iter);
n += 1;
}
else if (lp != groups && thunar_group_get_id (lp->data) < 100 && n == 1)
{
gtk_list_store_append (store, &iter);
n += 1;
}
/* append a new item for the group */
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter,
THUNAR_PERMISSIONS_STORE_COLUMN_NAME, thunar_group_get_name (lp->data),
THUNAR_PERMISSIONS_STORE_COLUMN_GID, thunar_group_get_id (lp->data),
-1);
/* set the active iter for the combo box if this group is the primary group */
if (G_UNLIKELY (lp->data == group))
gtk_combo_box_set_active_iter (GTK_COMBO_BOX (chooser->group_combo), &iter);
}
/* cleanup */
if (G_LIKELY (user != NULL))
g_object_unref (G_OBJECT (user));
if (G_LIKELY (group != NULL))
g_object_unref (G_OBJECT (group));
g_list_free_full (groups, g_object_unref);
/* determine the file mode and update the combo boxes */
for (n = 0; n < G_N_ELEMENTS (chooser->access_combos); ++n)
{
g_signal_handlers_block_by_func (G_OBJECT (chooser->access_combos[n]), thunar_permissions_chooser_access_changed, chooser);
/* allocate the store for the permission combos */
access_store = gtk_list_store_new (1, G_TYPE_STRING);
gtk_list_store_insert_with_values (access_store, NULL, 0, 0, _("None"), -1); /* 0000 */
gtk_list_store_insert_with_values (access_store, NULL, 1, 0, _("Write only"), -1); /* 0002 */
gtk_list_store_insert_with_values (access_store, NULL, 2, 0, _("Read only"), -1); /* 0004 */
gtk_list_store_insert_with_values (access_store, NULL, 3, 0, _("Read & Write"), -1); /* 0006 */
if (modes[n] == 4)
gtk_list_store_insert_with_values (access_store, NULL, 4, 0, _("Varying (no change)"), -1);
gtk_combo_box_set_model (GTK_COMBO_BOX (chooser->access_combos[n]), GTK_TREE_MODEL (access_store));
gtk_combo_box_set_active (GTK_COMBO_BOX (chooser->access_combos[n]), modes[n]);
g_signal_handlers_unblock_by_func (G_OBJECT (chooser->access_combos[n]), thunar_permissions_chooser_access_changed, chooser);
g_object_unref (G_OBJECT (access_store));
}
/* update the program setting based on the mode (only visible for regular files) */
g_signal_handlers_block_by_func (G_OBJECT (chooser->program_button), thunar_permissions_chooser_program_toggled, chooser);
g_object_set (G_OBJECT (chooser->program_button), "visible", thunar_file_is_regular (file), NULL);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chooser->program_button), (mode & 0111) != 0);
g_signal_handlers_unblock_by_func (G_OBJECT (chooser->program_button), thunar_permissions_chooser_program_toggled, chooser);
/* update the "inconsistent folder permissions" warning and the "fix permissions" button based on the mode */
if (thunar_permissions_chooser_has_fixable_directory (chooser))
{
/* always display the warning even if we cannot fix it */
gtk_widget_show (chooser->fixperm_label);
gtk_widget_show (chooser->fixperm_button);
}
else
{
/* hide both the warning text and the fix button */
gtk_widget_hide (chooser->fixperm_button);
gtk_widget_hide (chooser->fixperm_label);
}
/* release our reference on the new combo store and unblock the combo */
g_signal_handlers_unblock_by_func (G_OBJECT (chooser->group_combo), thunar_permissions_chooser_group_changed, chooser);
g_object_unref (G_OBJECT (store));
/* emit notification on "mutable", so all widgets update their sensitivity */
g_object_notify (G_OBJECT (chooser), "mutable");
}
static void
thunar_permissions_chooser_group_changed (ThunarPermissionsChooser *chooser,
GtkWidget *combo)
{
GtkTreeModel *model;
GtkTreeIter iter;
guint32 gid;
_thunar_return_if_fail (THUNAR_IS_PERMISSIONS_CHOOSER (chooser));
_thunar_return_if_fail (chooser->group_combo == combo);
_thunar_return_if_fail (GTK_IS_COMBO_BOX (combo));
/* verify that we have a valid file */
if (G_UNLIKELY (chooser->files == NULL))
return;
/* determine the tree model from the combo box */
model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
/* determine the iterator for the selected item */
if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
{
/* determine the group id for the selected item... */
gtk_tree_model_get (model, &iter, THUNAR_PERMISSIONS_STORE_COLUMN_GID, &gid, -1);
/* ...and try to change the group to the new gid */
thunar_permissions_chooser_change_group (chooser, gid);
}
}
static void
thunar_permissions_chooser_program_toggled (ThunarPermissionsChooser *chooser,
GtkWidget *button)
{
ThunarFileMode mode;
_thunar_return_if_fail (THUNAR_IS_PERMISSIONS_CHOOSER (chooser));
_thunar_return_if_fail (chooser->program_button == button);
_thunar_return_if_fail (GTK_IS_TOGGLE_BUTTON (button));
/* verify that we have a valid file */
if (G_UNLIKELY (chooser->files == NULL))
return;
/* determine the new mode based on the toggle state */
mode = (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) ? 0111 : 0000;
/* apply the new mode (only the executable bits for files) */
thunar_permissions_chooser_change_mode (chooser, 0000, 0000, 0111, mode);
}
static void
thunar_permissions_chooser_fixperm_clicked (ThunarPermissionsChooser *chooser,
GtkWidget *button)
{
ThunarFileMode mode;
GtkWidget *dialog;
GtkWidget *window;
ThunarJob *job;
gint response;
GList *lp;
GList file_list;
_thunar_return_if_fail (THUNAR_IS_PERMISSIONS_CHOOSER (chooser));
_thunar_return_if_fail (chooser->fixperm_button == button);
_thunar_return_if_fail (GTK_IS_BUTTON (button));
_thunar_return_if_fail (thunar_permissions_chooser_has_fixable_directory (chooser));
/* verify that we have a valid file */
if (G_UNLIKELY (chooser->files == NULL))
return;
/* determine the toplevel widget */
window = gtk_widget_get_toplevel (GTK_WIDGET (chooser));
if (G_UNLIKELY (window == NULL))
return;
/* popup a confirm dialog */
dialog = gtk_message_dialog_new (GTK_WINDOW (window),
GTK_DIALOG_DESTROY_WITH_PARENT
| GTK_DIALOG_MODAL,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_NONE,
_("Correct folder permissions automatically?"));
gtk_window_set_title (GTK_WINDOW (dialog), _("Folder permissions"));
gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Cancel"), GTK_RESPONSE_CANCEL);
gtk_dialog_add_button (GTK_DIALOG (dialog), _("Correct _folder permissions"), GTK_RESPONSE_OK);
gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("The folder permissions will be reset to a consistent state. Only users "
"allowed to read the contents of this folder will be allowed to enter the "
"folder afterwards."));
response = gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
/* check if we should apply the changes */
if (response == GTK_RESPONSE_OK)
{
for (lp = chooser->files; lp != NULL; lp = lp->next)
{
/* skip files that are fine */
if (!thunar_permissions_chooser_is_fixable_directory (THUNAR_FILE (lp->data)))
continue;
/* determine the current mode */
mode = thunar_file_get_mode (THUNAR_FILE (lp->data));
/* determine the new mode (making sure the owner can read/enter the folder) */
mode = (THUNAR_FILE_MODE_USR_READ | THUNAR_FILE_MODE_USR_EXEC)
| (((mode & THUNAR_FILE_MODE_GRP_READ) != 0) ? THUNAR_FILE_MODE_GRP_EXEC : 0)
| (((mode & THUNAR_FILE_MODE_OTH_READ) != 0) ? THUNAR_FILE_MODE_OTH_EXEC : 0);
file_list.prev = NULL;
file_list.data = thunar_file_get_file (THUNAR_FILE (lp->data));
file_list.next = NULL;
/* try to allocate the new job */
job = thunar_io_jobs_change_mode (&file_list,
0511, mode, 0000, 0000, FALSE);
exo_job_launch (EXO_JOB (job));
/* handle the job */
thunar_permissions_chooser_job_start (chooser, job, FALSE);
g_object_unref (job);
}
}
}
static ThunarJobResponse
thunar_permissions_chooser_job_ask (ThunarPermissionsChooser *chooser,
const gchar *message,
ThunarJobResponse choices,
ThunarJob *job)
{
GtkWidget *toplevel;
_thunar_return_val_if_fail (THUNAR_IS_PERMISSIONS_CHOOSER (chooser), THUNAR_JOB_RESPONSE_CANCEL);
_thunar_return_val_if_fail (g_utf8_validate (message, -1, NULL), THUNAR_JOB_RESPONSE_CANCEL);
_thunar_return_val_if_fail (THUNAR_IS_JOB (job), THUNAR_JOB_RESPONSE_CANCEL);
_thunar_return_val_if_fail (chooser->job == job, THUNAR_JOB_RESPONSE_CANCEL);
/* be sure to display the progress bar prior to opening the question dialog */
gtk_widget_show_now (chooser->job_progress);
/* determine the toplevel window for the chooser */
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (chooser));
if (G_UNLIKELY (toplevel == NULL))
return THUNAR_JOB_RESPONSE_CANCEL;
/* display the question dialog */
return thunar_dialogs_show_job_ask (GTK_WINDOW (toplevel), message, choices);
}
static void
thunar_permissions_chooser_job_cancel (ThunarPermissionsChooser *chooser)
{
_thunar_return_if_fail (THUNAR_IS_PERMISSIONS_CHOOSER (chooser));
/* verify that we have a job to cancel */
if (G_UNLIKELY (chooser->job == NULL))
return;
/* cancel the job (if not already done) */
exo_job_cancel (EXO_JOB (chooser->job));
/* disconnect from the job */
g_signal_handlers_disconnect_by_data (chooser->job, chooser);
g_object_unref (G_OBJECT (chooser->job));
chooser->job = NULL;
/* hide the progress bar */
gtk_widget_hide (chooser->job_progress);
/* make the remaining widgets sensitive again */
gtk_widget_set_sensitive (chooser->grid, TRUE);
}
static void
thunar_permissions_chooser_job_error (ThunarPermissionsChooser *chooser,
GError *error,
ThunarJob *job)
{
GtkWidget *toplevel;
_thunar_return_if_fail (THUNAR_IS_PERMISSIONS_CHOOSER (chooser));
_thunar_return_if_fail (error != NULL && error->message != NULL);
_thunar_return_if_fail (THUNAR_IS_JOB (job));
_thunar_return_if_fail (chooser->job == job);
/* be sure to display the progress bar prior to opening the error dialog */
gtk_widget_show_now (chooser->job_progress);
/* determine the toplevel widget for the chooser */
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (chooser));
if (G_UNLIKELY (toplevel == NULL))
return;
/* popup the error message dialog */
xfce_dialog_show_error (GTK_WINDOW (toplevel), error, _("Error while changing file permissions"));
}
static void
thunar_permissions_chooser_job_finished (ThunarPermissionsChooser *chooser,
ThunarJob *job)
{
_thunar_return_if_fail (THUNAR_IS_PERMISSIONS_CHOOSER (chooser));
_thunar_return_if_fail (THUNAR_IS_JOB (job));
_thunar_return_if_fail (chooser->job == job);
/* we can just use job_cancel(), since the job is already done */
thunar_permissions_chooser_job_cancel (chooser);
}
static void
thunar_permissions_chooser_job_percent (ThunarPermissionsChooser *chooser,
gdouble percent,
ThunarJob *job)
{
_thunar_return_if_fail (THUNAR_IS_PERMISSIONS_CHOOSER (chooser));
_thunar_return_if_fail (THUNAR_IS_JOB (job));
_thunar_return_if_fail (chooser->job == job);
gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (chooser->job_progress), percent / 100.0);
gtk_widget_show (chooser->job_progress);
}
static void
thunar_permissions_chooser_job_start (ThunarPermissionsChooser *chooser,
ThunarJob *job,
gboolean recursive)
{
_thunar_return_if_fail (THUNAR_IS_PERMISSIONS_CHOOSER (chooser));
_thunar_return_if_fail (THUNAR_IS_JOB (job));
_thunar_return_if_fail (chooser->job == NULL);
/* take a reference to the job and connect signals */
chooser->job = g_object_ref (job);
g_signal_connect_swapped (job, "ask", G_CALLBACK (thunar_permissions_chooser_job_ask), chooser);
g_signal_connect_swapped (job, "error", G_CALLBACK (thunar_permissions_chooser_job_error), chooser);
g_signal_connect_swapped (job, "finished", G_CALLBACK (thunar_permissions_chooser_job_finished), chooser);
/* don't connect percent for single file operations */
if (G_UNLIKELY (recursive))
g_signal_connect_swapped (job, "percent", G_CALLBACK (thunar_permissions_chooser_job_percent), chooser);
/* setup the progress bar */
gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (chooser->job_progress), 0.0);
/* make the majority of widgets insensitive if doing recursively */
gtk_widget_set_sensitive (chooser->grid, !recursive);
}
static gboolean
thunar_permissions_chooser_row_separator (GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data)
{
gchar *name;
/* determine the value of the "name" column */
gtk_tree_model_get (model, iter, THUNAR_PERMISSIONS_STORE_COLUMN_NAME, &name, -1);
if (G_LIKELY (name != NULL))
{
g_free (name);
return FALSE;
}
return TRUE;
}
/**
* thunar_permissions_chooser_get_files:
* @chooser : a #ThunarPermissionsChooser.
*
* Returns the #ThunarFile's associated with the specified @chooser.
*
* Return value: the file associated with @chooser.
**/
GList *
thunar_permissions_chooser_get_files (ThunarPermissionsChooser *chooser)
{
_thunar_return_val_if_fail (THUNAR_IS_PERMISSIONS_CHOOSER (chooser), NULL);
return chooser->files;
}
/**
* thunar_permissions_chooser_set_files:
* @chooser : a #ThunarPermissionsChooser.
* @files : a list of #ThunarFiles or %NULL.
*
* Associates @chooser with the specified @file.
**/
void
thunar_permissions_chooser_set_files (ThunarPermissionsChooser *chooser,
GList *files)
{
GList *lp;
_thunar_return_if_fail (THUNAR_IS_PERMISSIONS_CHOOSER (chooser));
/* check if we already use that file */
if (G_UNLIKELY (chooser->files == files))
return;
/* disconnect from the previous files */
for (lp = chooser->files; lp != NULL; lp = lp->next)
{
_thunar_assert (THUNAR_IS_FILE (lp->data));
g_signal_handlers_disconnect_by_func (G_OBJECT (lp->data), thunar_permissions_chooser_file_changed, chooser);
g_object_unref (G_OBJECT (lp->data));
}
g_list_free (chooser->files);
/* activate the new file */
chooser->files = g_list_copy (files);
/* connect to the new files */
for (lp = chooser->files; lp != NULL; lp = lp->next)
{
/* take a reference on the file */
_thunar_assert (THUNAR_IS_FILE (lp->data));
g_object_ref (G_OBJECT (lp->data));
/* stay informed about changes */
g_signal_connect_swapped (G_OBJECT (lp->data), "changed", G_CALLBACK (thunar_permissions_chooser_file_changed), chooser);
}
/* update our state */
if (chooser->files != NULL)
thunar_permissions_chooser_file_changed (chooser);
/* notify listeners */
g_object_notify (G_OBJECT (chooser), "files");
}
/**
* thunar_permissions_chooser_new:
*
* Allocates a new #ThunarPermissionsChooser instance.
*
* Return value: the newly allocated #ThunarPermissionsChooser.
**/
GtkWidget *
thunar_permissions_chooser_new (void)
{
return g_object_new (THUNAR_TYPE_PERMISSIONS_CHOOSER, NULL);
}