1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2
3 /* fm-properties-window.c - window that lets user modify file properties
4
5 Copyright (C) 2000 Eazel, Inc.
6
7 The Mate Library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public License as
9 published by the Free Software Foundation; either version 2 of the
10 License, or (at your option) any later version.
11
12 The Mate Library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public
18 License along with the Mate Library; see the file COPYING.LIB. If not,
19 write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21
22 Authors: Darin Adler <darin@bentspoon.com>
23 */
24
25 #include <config.h>
26 #include <string.h>
27 #include <cairo.h>
28
29 #include <gtk/gtk.h>
30 #include <gdk/gdkkeysyms.h>
31 #include <glib/gi18n.h>
32 #include <sys/stat.h>
33
34 #include <eel/eel-accessibility.h>
35 #include <eel/eel-gdk-pixbuf-extensions.h>
36 #include <eel/eel-glib-extensions.h>
37 #include <eel/eel-mate-extensions.h>
38 #include <eel/eel-gtk-extensions.h>
39 #include <eel/eel-labeled-image.h>
40 #include <eel/eel-stock-dialogs.h>
41 #include <eel/eel-vfs-extensions.h>
42 #include <eel/eel-wrap-table.h>
43
44 #include <libcaja-extension/caja-property-page-provider.h>
45
46 #include <libcaja-private/caja-mime-application-chooser.h>
47 #include <libcaja-private/caja-entry.h>
48 #include <libcaja-private/caja-extensions.h>
49 #include <libcaja-private/caja-file-attributes.h>
50 #include <libcaja-private/caja-file-operations.h>
51 #include <libcaja-private/caja-desktop-icon-file.h>
52 #include <libcaja-private/caja-global-preferences.h>
53 #include <libcaja-private/caja-emblem-utils.h>
54 #include <libcaja-private/caja-link.h>
55 #include <libcaja-private/caja-metadata.h>
56 #include <libcaja-private/caja-module.h>
57 #include <libcaja-private/caja-mime-actions.h>
58
59 #include "fm-properties-window.h"
60 #include "fm-ditem-page.h"
61 #include "fm-error-reporting.h"
62
63 #if HAVE_SYS_VFS_H
64 #include <sys/vfs.h>
65 #elif HAVE_SYS_MOUNT_H
66 #if HAVE_SYS_PARAM_H
67 #include <sys/param.h>
68 #endif
69 #include <sys/mount.h>
70 #endif
71
72 #define USED_FILL_R 0.988235294
73 #define USED_FILL_G 0.91372549
74 #define USED_FILL_B 0.309803922
75
76 #define FREE_FILL_R 0.447058824
77 #define FREE_FILL_G 0.623529412
78 #define FREE_FILL_B 0.811764706
79
80 #define PREVIEW_IMAGE_WIDTH 96
81
82 #define ROW_PAD 6
83
84 static GHashTable *windows;
85 static GHashTable *pending_lists;
86
87 struct _FMPropertiesWindowPrivate {
88 GList *original_files;
89 GList *target_files;
90
91 GtkNotebook *notebook;
92
93 GtkGrid *basic_grid;
94
95 GtkWidget *icon_button;
96 GtkWidget *icon_image;
97 GtkWidget *icon_chooser;
98
99 GtkLabel *name_label;
100 GtkWidget *name_field;
101 unsigned int name_row;
102 char *pending_name;
103
104 GtkLabel *directory_contents_title_field;
105 GtkLabel *directory_contents_value_field;
106 guint update_directory_contents_timeout_id;
107 guint update_files_timeout_id;
108
109 GList *emblem_buttons;
110 GHashTable *initial_emblems;
111
112 CajaFile *group_change_file;
113 char *group_change_group;
114 unsigned int group_change_timeout;
115 CajaFile *owner_change_file;
116 char *owner_change_owner;
117 unsigned int owner_change_timeout;
118
119 GList *permission_buttons;
120 GList *permission_combos;
121 GHashTable *initial_permissions;
122 gboolean has_recursive_apply;
123
124 GList *value_fields;
125
126 GList *mime_list;
127
128 gboolean deep_count_finished;
129
130 guint total_count;
131 goffset total_size;
132 goffset total_size_on_disk;
133
134 guint long_operation_underway;
135
136 GList *changed_files;
137
138 guint64 volume_capacity;
139 guint64 volume_free;
140
141 GdkRGBA used_color;
142 GdkRGBA free_color;
143 GdkRGBA used_stroke_color;
144 GdkRGBA free_stroke_color;
145 };
146
147 typedef enum {
148 PERMISSIONS_CHECKBOXES_READ,
149 PERMISSIONS_CHECKBOXES_WRITE,
150 PERMISSIONS_CHECKBOXES_EXECUTE
151 } CheckboxType;
152
153 enum {
154 TITLE_COLUMN,
155 VALUE_COLUMN,
156 COLUMN_COUNT
157 };
158
159 typedef struct {
160 GList *original_files;
161 GList *target_files;
162 GtkWidget *parent_widget;
163 char *pending_key;
164 GHashTable *pending_files;
165 } StartupData;
166
167 /* drag and drop definitions */
168
169 enum {
170 TARGET_URI_LIST,
171 TARGET_MATE_URI_LIST,
172 TARGET_RESET_BACKGROUND
173 };
174
175 static const GtkTargetEntry target_table[] = {
176 { "text/uri-list", 0, TARGET_URI_LIST },
177 { "x-special/mate-icon-list", 0, TARGET_MATE_URI_LIST },
178 { "x-special/mate-reset-background", 0, TARGET_RESET_BACKGROUND }
179 };
180
181 #define DIRECTORY_CONTENTS_UPDATE_INTERVAL 200 /* milliseconds */
182 #define FILES_UPDATE_INTERVAL 200 /* milliseconds */
183 #define STANDARD_EMBLEM_HEIGHT 52
184 #define EMBLEM_LABEL_SPACING 2
185
186 /*
187 * A timeout before changes through the user/group combo box will be applied.
188 * When quickly changing owner/groups (i.e. by keyboard or scroll wheel),
189 * this ensures that the GUI doesn't end up unresponsive.
190 *
191 * Both combos react on changes by scheduling a new change and unscheduling
192 * or cancelling old pending changes.
193 */
194 #define CHOWN_CHGRP_TIMEOUT 300 /* milliseconds */
195
196 static void directory_contents_value_field_update (FMPropertiesWindow *window);
197 static void file_changed_callback (CajaFile *file,
198 gpointer user_data);
199 static void permission_button_update (FMPropertiesWindow *window,
200 GtkToggleButton *button);
201 static void permission_combo_update (FMPropertiesWindow *window,
202 GtkComboBox *combo);
203 static void value_field_update (FMPropertiesWindow *window,
204 GtkLabel *field);
205 static void properties_window_update (FMPropertiesWindow *window,
206 GList *files);
207 static void is_directory_ready_callback (CajaFile *file,
208 gpointer data);
209 static void cancel_group_change_callback (FMPropertiesWindow *window);
210 static void cancel_owner_change_callback (FMPropertiesWindow *window);
211 static void parent_widget_destroyed_callback (GtkWidget *widget,
212 gpointer callback_data);
213 static void select_image_button_callback (GtkWidget *widget,
214 FMPropertiesWindow *properties_window);
215 static void set_icon (const char *icon_path,
216 FMPropertiesWindow *properties_window);
217 static void remove_pending (StartupData *data,
218 gboolean cancel_call_when_ready,
219 gboolean cancel_timed_wait,
220 gboolean cancel_destroy_handler);
221 static void append_extension_pages (FMPropertiesWindow *window);
222
223 static gboolean name_field_focus_out (CajaEntry *name_field,
224 GdkEventFocus *event,
225 gpointer callback_data);
226 static void name_field_activate (CajaEntry *name_field,
227 gpointer callback_data);
228 static GtkLabel *attach_ellipsizing_value_label (GtkGrid *grid,
229 GtkWidget *sibling,
230
231 const char *initial_text);
232
233 static GtkWidget* create_pie_widget (FMPropertiesWindow *window);
234
235 G_DEFINE_TYPE_WITH_PRIVATE (FMPropertiesWindow, fm_properties_window, GTK_TYPE_DIALOG);
236
237 static gboolean
is_multi_file_window(FMPropertiesWindow * window)238 is_multi_file_window (FMPropertiesWindow *window)
239 {
240 GList *l;
241 int count;
242
243 count = 0;
244
245 for (l = window->details->original_files; l != NULL; l = l->next) {
246 if (!caja_file_is_gone (CAJA_FILE (l->data))) {
247 count++;
248 if (count > 1) {
249 return TRUE;
250 }
251 }
252 }
253
254 return FALSE;
255 }
256
257 static int
get_not_gone_original_file_count(FMPropertiesWindow * window)258 get_not_gone_original_file_count (FMPropertiesWindow *window)
259 {
260 GList *l;
261 int count;
262
263 count = 0;
264
265 for (l = window->details->original_files; l != NULL; l = l->next) {
266 if (!caja_file_is_gone (CAJA_FILE (l->data))) {
267 count++;
268 }
269 }
270
271 return count;
272 }
273
274 static CajaFile *
get_original_file(FMPropertiesWindow * window)275 get_original_file (FMPropertiesWindow *window)
276 {
277 g_return_val_if_fail (!is_multi_file_window (window), NULL);
278
279 if (window->details->original_files == NULL) {
280 return NULL;
281 }
282
283 return CAJA_FILE (window->details->original_files->data);
284 }
285
286 static CajaFile *
get_target_file_for_original_file(CajaFile * file)287 get_target_file_for_original_file (CajaFile *file)
288 {
289 CajaFile *target_file;
290
291 target_file = NULL;
292 if (CAJA_IS_DESKTOP_ICON_FILE (file)) {
293 CajaDesktopLink *link;
294
295 link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (file));
296
297 if (link != NULL) {
298 GFile *location;
299
300 /* map to linked URI for these types of links */
301 location = caja_desktop_link_get_activation_location (link);
302
303 if (location) {
304 target_file = caja_file_get (location);
305 g_object_unref (location);
306 }
307
308 g_object_unref (link);
309 }
310 } else {
311 char *uri_to_display;
312
313 uri_to_display = caja_file_get_activation_uri (file);
314
315 if (uri_to_display != NULL) {
316 target_file = caja_file_get_by_uri (uri_to_display);
317 g_free (uri_to_display);
318 }
319 }
320
321 if (target_file != NULL) {
322 return target_file;
323 }
324
325 /* Ref passed-in file here since we've decided to use it. */
326 caja_file_ref (file);
327 return file;
328 }
329
330 static CajaFile *
get_target_file(FMPropertiesWindow * window)331 get_target_file (FMPropertiesWindow *window)
332 {
333 return CAJA_FILE (window->details->target_files->data);
334 }
335
336 static void
add_prompt(GtkWidget * vbox,const char * prompt_text,gboolean pack_at_start)337 add_prompt (GtkWidget *vbox, const char *prompt_text, gboolean pack_at_start)
338 {
339 GtkWidget *prompt;
340
341 prompt = gtk_label_new (prompt_text);
342 gtk_label_set_justify (GTK_LABEL (prompt), GTK_JUSTIFY_LEFT);
343 gtk_label_set_line_wrap (GTK_LABEL (prompt), TRUE);
344 gtk_widget_show (prompt);
345 if (pack_at_start) {
346 gtk_box_pack_start (GTK_BOX (vbox), prompt, FALSE, FALSE, 0);
347 } else {
348 gtk_box_pack_end (GTK_BOX (vbox), prompt, FALSE, FALSE, 0);
349 }
350 }
351
352 static void
add_prompt_and_separator(GtkWidget * vbox,const char * prompt_text)353 add_prompt_and_separator (GtkWidget *vbox, const char *prompt_text)
354 {
355 GtkWidget *separator_line;
356
357 add_prompt (vbox, prompt_text, FALSE);
358
359 separator_line = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
360
361 gtk_widget_show (separator_line);
362 gtk_box_pack_end (GTK_BOX (vbox), separator_line, TRUE, TRUE, 2*ROW_PAD);
363 }
364
365 static void
get_image_for_properties_window(FMPropertiesWindow * window,char ** icon_name,GdkPixbuf ** icon_pixbuf)366 get_image_for_properties_window (FMPropertiesWindow *window,
367 char **icon_name,
368 GdkPixbuf **icon_pixbuf)
369 {
370 CajaIconInfo *icon, *new_icon;
371 GList *l;
372 gint icon_scale;
373
374 icon = NULL;
375 icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (window->details->notebook));
376
377 for (l = window->details->original_files; l != NULL; l = l->next) {
378 CajaFile *file;
379
380 file = CAJA_FILE (l->data);
381
382 if (!icon) {
383 icon = caja_file_get_icon (file, CAJA_ICON_SIZE_STANDARD, icon_scale,
384 CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS |
385 CAJA_FILE_ICON_FLAGS_IGNORE_VISITING);
386 } else {
387 new_icon = caja_file_get_icon (file, CAJA_ICON_SIZE_STANDARD, icon_scale,
388 CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS |
389 CAJA_FILE_ICON_FLAGS_IGNORE_VISITING);
390 if (!new_icon || new_icon != icon) {
391 g_object_unref (icon);
392 g_object_unref (new_icon);
393 icon = NULL;
394 break;
395 }
396 g_object_unref (new_icon);
397 }
398 }
399
400 if (!icon) {
401 icon = caja_icon_info_lookup_from_name ("text-x-generic",
402 CAJA_ICON_SIZE_STANDARD,
403 icon_scale);
404 }
405
406 if (icon_name != NULL) {
407 *icon_name = g_strdup (caja_icon_info_get_used_name (icon));
408 }
409
410 if (icon_pixbuf != NULL) {
411 *icon_pixbuf = caja_icon_info_get_pixbuf_at_size (icon, CAJA_ICON_SIZE_STANDARD);
412 }
413
414 g_object_unref (icon);
415 }
416
417
418 static void
update_properties_window_icon(FMPropertiesWindow * window)419 update_properties_window_icon (FMPropertiesWindow *window)
420 {
421 GdkPixbuf *pixbuf;
422 cairo_surface_t *surface;
423 char *name;
424
425 get_image_for_properties_window (window, &name, &pixbuf);
426
427 if (name != NULL) {
428 gtk_window_set_icon_name (GTK_WINDOW (window), name);
429 } else {
430 gtk_window_set_icon (GTK_WINDOW (window), pixbuf);
431 }
432
433 surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, gtk_widget_get_scale_factor (GTK_WIDGET (window)),
434 gtk_widget_get_window (GTK_WIDGET (window)));
435 gtk_image_set_from_surface (GTK_IMAGE (window->details->icon_image), surface);
436
437 g_free (name);
438 g_object_unref (pixbuf);
439 cairo_surface_destroy (surface);
440 }
441
442 /* utility to test if a uri refers to a local image */
443 static gboolean
uri_is_local_image(const char * uri)444 uri_is_local_image (const char *uri)
445 {
446 GdkPixbuf *pixbuf;
447 char *image_path;
448
449 image_path = g_filename_from_uri (uri, NULL, NULL);
450 if (image_path == NULL) {
451 return FALSE;
452 }
453
454 pixbuf = gdk_pixbuf_new_from_file (image_path, NULL);
455 g_free (image_path);
456
457 if (pixbuf == NULL) {
458 return FALSE;
459 }
460 g_object_unref (pixbuf);
461 return TRUE;
462 }
463
464
465 static void
reset_icon(FMPropertiesWindow * properties_window)466 reset_icon (FMPropertiesWindow *properties_window)
467 {
468 GList *l;
469
470 for (l = properties_window->details->original_files; l != NULL; l = l->next) {
471 CajaFile *file;
472
473 file = CAJA_FILE (l->data);
474
475 caja_file_set_metadata (file,
476 CAJA_METADATA_KEY_ICON_SCALE,
477 NULL, NULL);
478 caja_file_set_metadata (file,
479 CAJA_METADATA_KEY_CUSTOM_ICON,
480 NULL, NULL);
481 }
482 }
483
484
485 static void
fm_properties_window_drag_data_received(GtkWidget * widget,GdkDragContext * context,int x,int y,GtkSelectionData * selection_data,guint info,guint time)486 fm_properties_window_drag_data_received (GtkWidget *widget, GdkDragContext *context,
487 int x, int y,
488 GtkSelectionData *selection_data,
489 guint info, guint time)
490 {
491 char **uris;
492 gboolean exactly_one;
493 GtkImage *image;
494 GtkWindow *window;
495
496 image = GTK_IMAGE (widget);
497 window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (image)));
498
499 if (info == TARGET_RESET_BACKGROUND) {
500 reset_icon (FM_PROPERTIES_WINDOW (window));
501
502 return;
503 }
504
505 uris = g_strsplit (gtk_selection_data_get_data (selection_data), "\r\n", 0);
506 exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0');
507
508
509 if (!exactly_one) {
510 eel_show_error_dialog
511 (_("You cannot assign more than one custom icon at a time!"),
512 _("Please drag just one image to set a custom icon."),
513 window);
514 } else {
515 if (uri_is_local_image (uris[0])) {
516 set_icon (uris[0], FM_PROPERTIES_WINDOW (window));
517 } else {
518 GFile *f;
519
520 f = g_file_new_for_uri (uris[0]);
521 if (!g_file_is_native (f)) {
522 eel_show_error_dialog
523 (_("The file that you dropped is not local."),
524 _("You can only use local images as custom icons."),
525 window);
526
527 } else {
528 eel_show_error_dialog
529 (_("The file that you dropped is not an image."),
530 _("You can only use local images as custom icons."),
531 window);
532 }
533 g_object_unref (f);
534 }
535 }
536 g_strfreev (uris);
537 }
538
539 static GtkWidget *
create_image_widget(FMPropertiesWindow * window,gboolean is_customizable)540 create_image_widget (FMPropertiesWindow *window,
541 gboolean is_customizable)
542 {
543 GtkWidget *button;
544 GtkWidget *image;
545
546 image = gtk_image_new ();
547 window->details->icon_image = image;
548
549 update_properties_window_icon (window);
550 gtk_widget_show (image);
551
552 button = NULL;
553 if (is_customizable) {
554 button = gtk_button_new ();
555 gtk_container_add (GTK_CONTAINER (button), image);
556
557 /* prepare the image to receive dropped objects to assign custom images */
558 gtk_drag_dest_set (GTK_WIDGET (image),
559 GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
560 target_table, G_N_ELEMENTS (target_table),
561 GDK_ACTION_COPY | GDK_ACTION_MOVE);
562
563 g_signal_connect (image, "drag_data_received",
564 G_CALLBACK (fm_properties_window_drag_data_received), NULL);
565 g_signal_connect (button, "clicked",
566 G_CALLBACK (select_image_button_callback), window);
567 }
568
569 window->details->icon_button = button;
570
571 return button != NULL ? button : image;
572 }
573
574 static void
set_name_field(FMPropertiesWindow * window,const gchar * original_name,const gchar * name)575 set_name_field (FMPropertiesWindow *window,
576 const gchar *original_name,
577 const gchar *name)
578 {
579 gboolean new_widget;
580 gboolean use_label;
581
582 /* There are four cases here:
583 * 1) Changing the text of a label
584 * 2) Changing the text of an entry
585 * 3) Creating label (potentially replacing entry)
586 * 4) Creating entry (potentially replacing label)
587 */
588 use_label = is_multi_file_window (window) || !caja_file_can_rename (get_original_file (window));
589 new_widget = !window->details->name_field || (use_label ? CAJA_IS_ENTRY (window->details->name_field) : GTK_IS_LABEL (window->details->name_field));
590
591 if (new_widget) {
592 if (window->details->name_field) {
593 gtk_widget_destroy (window->details->name_field);
594 }
595
596 if (use_label) {
597 window->details->name_field = GTK_WIDGET
598 (attach_ellipsizing_value_label (window->details->basic_grid,
599 GTK_WIDGET (window->details->name_label),
600 name));
601
602 } else {
603 window->details->name_field = caja_entry_new ();
604 gtk_entry_set_text (GTK_ENTRY (window->details->name_field), name);
605 gtk_widget_show (window->details->name_field);
606 gtk_grid_attach_next_to (window->details->basic_grid, window->details->name_field,
607 GTK_WIDGET (window->details->name_label),
608 GTK_POS_RIGHT, 1, 1);
609
610 gtk_label_set_mnemonic_widget (GTK_LABEL (window->details->name_label), window->details->name_field);
611
612 g_signal_connect_object (window->details->name_field, "focus_out_event",
613 G_CALLBACK (name_field_focus_out), window, 0);
614 g_signal_connect_object (window->details->name_field, "activate",
615 G_CALLBACK (name_field_activate), window, 0);
616 }
617
618 gtk_widget_show (window->details->name_field);
619 }
620 /* Only replace text if the file's name has changed. */
621 else if (original_name == NULL || strcmp (original_name, name) != 0) {
622
623 if (use_label) {
624 gtk_label_set_text (GTK_LABEL (window->details->name_field), name);
625 } else {
626 /* Only reset the text if it's different from what is
627 * currently showing. This causes minimal ripples (e.g.
628 * selection change).
629 */
630 gchar *displayed_name = gtk_editable_get_chars (GTK_EDITABLE (window->details->name_field), 0, -1);
631 if (strcmp (displayed_name, name) != 0) {
632 gtk_entry_set_text (GTK_ENTRY (window->details->name_field), name);
633 }
634 g_free (displayed_name);
635 }
636 }
637 }
638
639 static void
update_name_field(FMPropertiesWindow * window)640 update_name_field (FMPropertiesWindow *window)
641 {
642 CajaFile *file;
643
644 gtk_label_set_text_with_mnemonic (window->details->name_label,
645 ngettext ("_Name:", "_Names:",
646 get_not_gone_original_file_count (window)));
647
648 if (is_multi_file_window (window)) {
649 /* Multifile property dialog, show all names */
650 GString *str;
651 char *name;
652 gboolean first;
653 GList *l;
654
655 str = g_string_new ("");
656
657 first = TRUE;
658
659 for (l = window->details->target_files; l != NULL; l = l->next) {
660 file = CAJA_FILE (l->data);
661
662 if (!caja_file_is_gone (file)) {
663 if (!first) {
664 g_string_append (str, ", ");
665 }
666 first = FALSE;
667
668 name = caja_file_get_display_name (file);
669 g_string_append (str, name);
670 g_free (name);
671 }
672 }
673 set_name_field (window, NULL, str->str);
674 g_string_free (str, TRUE);
675 } else {
676 const char *original_name = NULL;
677 char *current_name;
678
679 file = get_original_file (window);
680
681 if (file == NULL || caja_file_is_gone (file)) {
682 current_name = g_strdup ("");
683 } else {
684 current_name = caja_file_get_display_name (file);
685 }
686
687 /* If the file name has changed since the original name was stored,
688 * update the text in the text field, possibly (deliberately) clobbering
689 * an edit in progress. If the name hasn't changed (but some other
690 * aspect of the file might have), then don't clobber changes.
691 */
692 if (window->details->name_field) {
693 original_name = (const char *) g_object_get_data (G_OBJECT (window->details->name_field), "original_name");
694 }
695
696 set_name_field (window, original_name, current_name);
697
698 if (original_name == NULL ||
699 g_strcmp0 (original_name, current_name) != 0) {
700 g_object_set_data_full (G_OBJECT (window->details->name_field),
701 "original_name",
702 current_name,
703 g_free);
704 } else {
705 g_free (current_name);
706 }
707 }
708 }
709
710 static void
name_field_restore_original_name(CajaEntry * name_field)711 name_field_restore_original_name (CajaEntry *name_field)
712 {
713 const char *original_name;
714 char *displayed_name;
715
716 original_name = (const char *) g_object_get_data (G_OBJECT (name_field),
717 "original_name");
718
719 if (!original_name) {
720 return;
721 }
722
723 displayed_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1);
724
725 if (strcmp (original_name, displayed_name) != 0) {
726 gtk_entry_set_text (GTK_ENTRY (name_field), original_name);
727 }
728 caja_entry_select_all (name_field);
729
730 g_free (displayed_name);
731 }
732
733 static void
rename_callback(CajaFile * file,GFile * res_loc,GError * error,gpointer callback_data)734 rename_callback (CajaFile *file, GFile *res_loc, GError *error, gpointer callback_data)
735 {
736 FMPropertiesWindow *window;
737
738 window = FM_PROPERTIES_WINDOW (callback_data);
739
740 /* Complain to user if rename failed. */
741 if (error != NULL) {
742 fm_report_error_renaming_file (file,
743 window->details->pending_name,
744 error,
745 GTK_WINDOW (window));
746 if (window->details->name_field != NULL) {
747 name_field_restore_original_name (CAJA_ENTRY (window->details->name_field));
748 }
749 }
750
751 g_object_unref (window);
752 }
753
754 static void
set_pending_name(FMPropertiesWindow * window,const char * name)755 set_pending_name (FMPropertiesWindow *window, const char *name)
756 {
757 g_free (window->details->pending_name);
758 window->details->pending_name = g_strdup (name);
759 }
760
761 static void
name_field_done_editing(CajaEntry * name_field,FMPropertiesWindow * window)762 name_field_done_editing (CajaEntry *name_field, FMPropertiesWindow *window)
763 {
764 CajaFile *file;
765 char *new_name;
766
767 g_return_if_fail (CAJA_IS_ENTRY (name_field));
768
769 /* Don't apply if the dialog has more than one file */
770 if (is_multi_file_window (window)) {
771 return;
772 }
773
774 file = get_original_file (window);
775
776 /* This gets called when the window is closed, which might be
777 * caused by the file having been deleted.
778 */
779 if (file == NULL || caja_file_is_gone (file)) {
780 return;
781 }
782
783 new_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1);
784
785 /* Special case: silently revert text if new text is empty. */
786 if (strlen (new_name) == 0) {
787 name_field_restore_original_name (CAJA_ENTRY (name_field));
788 } else {
789 const char *original_name;
790
791 original_name = (const char *) g_object_get_data (G_OBJECT (window->details->name_field),
792 "original_name");
793 /* Don't rename if not changed since we read the display name.
794 This is needed so that we don't save the display name to the
795 file when nothing is changed */
796 if (strcmp (new_name, original_name) != 0) {
797 set_pending_name (window, new_name);
798 g_object_ref (window);
799 caja_file_rename (file, new_name,
800 rename_callback, window);
801 }
802 }
803
804 g_free (new_name);
805 }
806
807 static gboolean
name_field_focus_out(CajaEntry * name_field,GdkEventFocus * event,gpointer callback_data)808 name_field_focus_out (CajaEntry *name_field,
809 GdkEventFocus *event,
810 gpointer callback_data)
811 {
812 g_assert (FM_IS_PROPERTIES_WINDOW (callback_data));
813
814 if (gtk_widget_get_sensitive (GTK_WIDGET (name_field))) {
815 name_field_done_editing (name_field, FM_PROPERTIES_WINDOW (callback_data));
816 }
817
818 return FALSE;
819 }
820
821 static void
name_field_activate(CajaEntry * name_field,gpointer callback_data)822 name_field_activate (CajaEntry *name_field, gpointer callback_data)
823 {
824 g_assert (CAJA_IS_ENTRY (name_field));
825 g_assert (FM_IS_PROPERTIES_WINDOW (callback_data));
826
827 /* Accept changes. */
828 name_field_done_editing (name_field, FM_PROPERTIES_WINDOW (callback_data));
829
830 caja_entry_select_all_at_idle (name_field);
831 }
832
833 static gboolean
file_has_keyword(CajaFile * file,const char * keyword)834 file_has_keyword (CajaFile *file, const char *keyword)
835 {
836 GList *keywords, *word;
837
838 keywords = caja_file_get_keywords (file);
839 word = g_list_find_custom (keywords, keyword, (GCompareFunc) strcmp);
840 g_list_free_full (keywords, g_free);
841
842 return (word != NULL);
843 }
844
845 static void
get_initial_emblem_state(FMPropertiesWindow * window,const char * name,GList ** on,GList ** off)846 get_initial_emblem_state (FMPropertiesWindow *window,
847 const char *name,
848 GList **on,
849 GList **off)
850 {
851 GList *l;
852
853 *on = NULL;
854 *off = NULL;
855
856 for (l = window->details->original_files; l != NULL; l = l->next) {
857 GList *initial_emblems;
858
859 initial_emblems = g_hash_table_lookup (window->details->initial_emblems,
860 l->data);
861
862 if (g_list_find_custom (initial_emblems, name, (GCompareFunc) strcmp)) {
863 *on = g_list_prepend (*on, l->data);
864 } else {
865 *off = g_list_prepend (*off, l->data);
866 }
867 }
868 }
869
870 static void
emblem_button_toggled(GtkToggleButton * button,FMPropertiesWindow * window)871 emblem_button_toggled (GtkToggleButton *button,
872 FMPropertiesWindow *window)
873 {
874 GList *l;
875 GList *keywords;
876 GList *word;
877 char *name;
878 GList *files_on;
879 GList *files_off;
880
881 name = g_object_get_data (G_OBJECT (button), "caja_emblem_name");
882
883 files_on = NULL;
884 files_off = NULL;
885 if (gtk_toggle_button_get_active (button)
886 && !gtk_toggle_button_get_inconsistent (button)) {
887 /* Go to the initial state unless the initial state was
888 consistent */
889 get_initial_emblem_state (window, name,
890 &files_on, &files_off);
891
892 if (!(files_on && files_off)) {
893 g_list_free (files_on);
894 g_list_free (files_off);
895 files_on = g_list_copy (window->details->original_files);
896 files_off = NULL;
897 }
898 } else if (gtk_toggle_button_get_inconsistent (button)
899 && !gtk_toggle_button_get_active (button)) {
900 files_on = g_list_copy (window->details->original_files);
901 files_off = NULL;
902 } else {
903 files_off = g_list_copy (window->details->original_files);
904 files_on = NULL;
905 }
906
907 g_signal_handlers_block_by_func (G_OBJECT (button),
908 G_CALLBACK (emblem_button_toggled),
909 window);
910
911 gtk_toggle_button_set_active (button, files_on != NULL);
912 gtk_toggle_button_set_inconsistent (button, files_on && files_off);
913
914 g_signal_handlers_unblock_by_func (G_OBJECT (button),
915 G_CALLBACK (emblem_button_toggled),
916 window);
917
918 for (l = files_on; l != NULL; l = l->next) {
919 CajaFile *file;
920
921 file = CAJA_FILE (l->data);
922
923 keywords = caja_file_get_keywords (file);
924
925 word = g_list_find_custom (keywords, name, (GCompareFunc)strcmp);
926 if (!word) {
927 keywords = g_list_prepend (keywords, g_strdup (name));
928 }
929 caja_file_set_keywords (file, keywords);
930 g_list_free_full (keywords, g_free);
931 }
932
933 for (l = files_off; l != NULL; l = l->next) {
934 CajaFile *file;
935
936 file = CAJA_FILE (l->data);
937
938 keywords = caja_file_get_keywords (file);
939
940 word = g_list_find_custom (keywords, name, (GCompareFunc)strcmp);
941 if (word) {
942 keywords = g_list_remove_link (keywords, word);
943 g_list_free_full (word, g_free);
944 }
945 caja_file_set_keywords (file, keywords);
946 g_list_free_full (keywords, g_free);
947 }
948
949 g_list_free (files_on);
950 g_list_free (files_off);
951 }
952
953 static void
emblem_button_update(FMPropertiesWindow * window,GtkToggleButton * button)954 emblem_button_update (FMPropertiesWindow *window,
955 GtkToggleButton *button)
956 {
957 GList *l;
958 char *name;
959 gboolean all_set;
960 gboolean all_unset;
961
962 name = g_object_get_data (G_OBJECT (button), "caja_emblem_name");
963
964 all_set = TRUE;
965 all_unset = TRUE;
966 for (l = window->details->original_files; l != NULL; l = l->next) {
967 gboolean has_keyword;
968 CajaFile *file;
969
970 file = CAJA_FILE (l->data);
971
972 has_keyword = file_has_keyword (file, name);
973
974 if (has_keyword) {
975 all_unset = FALSE;
976 } else {
977 all_set = FALSE;
978 }
979 }
980
981 g_signal_handlers_block_by_func (G_OBJECT (button),
982 G_CALLBACK (emblem_button_toggled),
983 window);
984
985 gtk_toggle_button_set_active (button, !all_unset);
986 gtk_toggle_button_set_inconsistent (button, !all_unset && !all_set);
987
988 g_signal_handlers_unblock_by_func (G_OBJECT (button),
989 G_CALLBACK (emblem_button_toggled),
990 window);
991
992 }
993
994 static void
update_properties_window_title(FMPropertiesWindow * window)995 update_properties_window_title (FMPropertiesWindow *window)
996 {
997 char *title;
998
999 g_return_if_fail (GTK_IS_WINDOW (window));
1000
1001 title = g_strdup_printf (_("Properties"));
1002
1003 if (!is_multi_file_window (window)) {
1004 CajaFile *file;
1005
1006 file = get_original_file (window);
1007
1008 if (file != NULL) {
1009 char *name;
1010
1011 g_free (title);
1012 name = caja_file_get_display_name (file);
1013 title = g_strdup_printf (_("%s Properties"), name);
1014 g_free (name);
1015 }
1016 }
1017
1018 gtk_window_set_title (GTK_WINDOW (window), title);
1019
1020 g_free (title);
1021 }
1022
1023 static void
clear_extension_pages(FMPropertiesWindow * window)1024 clear_extension_pages (FMPropertiesWindow *window)
1025 {
1026 int i;
1027 int num_pages;
1028 GtkWidget *page = NULL;
1029
1030 num_pages = gtk_notebook_get_n_pages
1031 (GTK_NOTEBOOK (window->details->notebook));
1032
1033 for (i = 0; i < num_pages; i++) {
1034 page = gtk_notebook_get_nth_page
1035 (GTK_NOTEBOOK (window->details->notebook), i);
1036
1037 if (g_object_get_data (G_OBJECT (page), "is-extension-page")) {
1038 gtk_notebook_remove_page
1039 (GTK_NOTEBOOK (window->details->notebook), i);
1040 num_pages--;
1041 i--;
1042 }
1043 }
1044 }
1045
1046 static void
refresh_extension_pages(FMPropertiesWindow * window)1047 refresh_extension_pages (FMPropertiesWindow *window)
1048 {
1049 clear_extension_pages (window);
1050 append_extension_pages (window);
1051 }
1052
1053 static void
remove_from_dialog(FMPropertiesWindow * window,CajaFile * file)1054 remove_from_dialog (FMPropertiesWindow *window,
1055 CajaFile *file)
1056 {
1057 int index;
1058 GList *original_link;
1059 GList *target_link;
1060 CajaFile *original_file;
1061 CajaFile *target_file;
1062
1063 index = g_list_index (window->details->target_files, file);
1064 if (index == -1) {
1065 index = g_list_index (window->details->original_files, file);
1066 g_return_if_fail (index != -1);
1067 }
1068
1069 original_link = g_list_nth (window->details->original_files, index);
1070 target_link = g_list_nth (window->details->target_files, index);
1071
1072 g_return_if_fail (original_link && target_link);
1073
1074 original_file = CAJA_FILE (original_link->data);
1075 target_file = CAJA_FILE (target_link->data);
1076
1077 window->details->original_files = g_list_remove_link (window->details->original_files, original_link);
1078 g_list_free (original_link);
1079
1080 window->details->target_files = g_list_remove_link (window->details->target_files, target_link);
1081 g_list_free (target_link);
1082
1083 g_hash_table_remove (window->details->initial_emblems, original_file);
1084 g_hash_table_remove (window->details->initial_permissions, target_file);
1085
1086 g_signal_handlers_disconnect_by_func (original_file,
1087 G_CALLBACK (file_changed_callback),
1088 window);
1089 g_signal_handlers_disconnect_by_func (target_file,
1090 G_CALLBACK (file_changed_callback),
1091 window);
1092
1093 caja_file_monitor_remove (original_file, &window->details->original_files);
1094 caja_file_monitor_remove (target_file, &window->details->target_files);
1095
1096 caja_file_unref (original_file);
1097 caja_file_unref (target_file);
1098
1099 }
1100
1101 static gboolean
mime_list_equal(GList * a,GList * b)1102 mime_list_equal (GList *a, GList *b)
1103 {
1104 while (a && b) {
1105 if (strcmp (a->data, b->data)) {
1106 return FALSE;
1107 }
1108 a = a->next;
1109 b = b->next;
1110 }
1111
1112 return (a == b);
1113 }
1114
1115 static GList *
get_mime_list(FMPropertiesWindow * window)1116 get_mime_list (FMPropertiesWindow *window)
1117 {
1118 GList *ret;
1119 GList *l;
1120
1121 ret = NULL;
1122 for (l = window->details->target_files; l != NULL; l = l->next) {
1123 ret = g_list_append (ret, caja_file_get_mime_type (CAJA_FILE (l->data)));
1124 }
1125 ret = g_list_reverse (ret);
1126 return ret;
1127 }
1128
1129 static void
properties_window_update(FMPropertiesWindow * window,GList * files)1130 properties_window_update (FMPropertiesWindow *window,
1131 GList *files)
1132 {
1133 GList *l;
1134 GList *mime_list;
1135 GList *tmp;
1136 CajaFile *changed_file = NULL;
1137 gboolean dirty_original = FALSE;
1138 gboolean dirty_target = FALSE;
1139
1140 if (files == NULL) {
1141 dirty_original = TRUE;
1142 dirty_target = TRUE;
1143 }
1144
1145 for (tmp = files; tmp != NULL; tmp = tmp->next) {
1146 changed_file = CAJA_FILE (tmp->data);
1147
1148 if (changed_file && caja_file_is_gone (changed_file)) {
1149 /* Remove the file from the property dialog */
1150 remove_from_dialog (window, changed_file);
1151 changed_file = NULL;
1152
1153 if (window->details->original_files == NULL) {
1154 return;
1155 }
1156 }
1157 if (changed_file == NULL ||
1158 g_list_find (window->details->original_files, changed_file)) {
1159 dirty_original = TRUE;
1160 }
1161 if (changed_file == NULL ||
1162 g_list_find (window->details->target_files, changed_file)) {
1163 dirty_target = TRUE;
1164 }
1165
1166 }
1167
1168 if (dirty_original) {
1169 update_properties_window_title (window);
1170 update_properties_window_icon (window);
1171 update_name_field (window);
1172
1173 for (l = window->details->emblem_buttons; l != NULL; l = l->next) {
1174 emblem_button_update (window, GTK_TOGGLE_BUTTON (l->data));
1175 }
1176
1177 /* If any of the value fields start to depend on the original
1178 * value, value_field_updates should be added here */
1179 }
1180
1181 if (dirty_target) {
1182 for (l = window->details->permission_buttons; l != NULL; l = l->next) {
1183 permission_button_update (window, GTK_TOGGLE_BUTTON (l->data));
1184 }
1185
1186 for (l = window->details->permission_combos; l != NULL; l = l->next) {
1187 permission_combo_update (window, GTK_COMBO_BOX (l->data));
1188 }
1189
1190 for (l = window->details->value_fields; l != NULL; l = l->next) {
1191 value_field_update (window, GTK_LABEL (l->data));
1192 }
1193 }
1194
1195 mime_list = get_mime_list (window);
1196
1197 if (!window->details->mime_list) {
1198 window->details->mime_list = mime_list;
1199 } else {
1200 if (!mime_list_equal (window->details->mime_list, mime_list)) {
1201 refresh_extension_pages (window);
1202 }
1203
1204 g_list_free_full (window->details->mime_list, g_free);
1205 window->details->mime_list = mime_list;
1206 }
1207 }
1208
1209 static gboolean
update_files_callback(gpointer data)1210 update_files_callback (gpointer data)
1211 {
1212 FMPropertiesWindow *window;
1213
1214 window = FM_PROPERTIES_WINDOW (data);
1215
1216 window->details->update_files_timeout_id = 0;
1217
1218 properties_window_update (window, window->details->changed_files);
1219
1220 if (window->details->original_files == NULL) {
1221 /* Close the window if no files are left */
1222 gtk_widget_destroy (GTK_WIDGET (window));
1223 } else {
1224 caja_file_list_free (window->details->changed_files);
1225 window->details->changed_files = NULL;
1226 }
1227
1228 return FALSE;
1229 }
1230
1231 static void
schedule_files_update(FMPropertiesWindow * window)1232 schedule_files_update (FMPropertiesWindow *window)
1233 {
1234 g_assert (FM_IS_PROPERTIES_WINDOW (window));
1235
1236 if (window->details->update_files_timeout_id == 0) {
1237 window->details->update_files_timeout_id
1238 = g_timeout_add (FILES_UPDATE_INTERVAL,
1239 update_files_callback,
1240 window);
1241 }
1242 }
1243
1244 static gboolean
file_list_attributes_identical(GList * file_list,const char * attribute_name)1245 file_list_attributes_identical (GList *file_list, const char *attribute_name)
1246 {
1247 gboolean identical;
1248 char *first_attr;
1249 GList *l;
1250
1251 first_attr = NULL;
1252 identical = TRUE;
1253
1254 for (l = file_list; l != NULL; l = l->next) {
1255 CajaFile *file;
1256
1257 file = CAJA_FILE (l->data);
1258
1259 if (caja_file_is_gone (file)) {
1260 continue;
1261 }
1262
1263 if (first_attr == NULL) {
1264 first_attr = caja_file_get_string_attribute_with_default (file, attribute_name);
1265 } else {
1266 char *attr;
1267 attr = caja_file_get_string_attribute_with_default (file, attribute_name);
1268 if (strcmp (attr, first_attr)) {
1269 identical = FALSE;
1270 g_free (attr);
1271 break;
1272 }
1273 g_free (attr);
1274 }
1275 }
1276
1277 g_free (first_attr);
1278 return identical;
1279 }
1280
1281 static char *
file_list_get_string_attribute(GList * file_list,const char * attribute_name,const char * inconsistent_value)1282 file_list_get_string_attribute (GList *file_list,
1283 const char *attribute_name,
1284 const char *inconsistent_value)
1285 {
1286 if (file_list_attributes_identical (file_list, attribute_name)) {
1287 GList *l;
1288
1289 for (l = file_list; l != NULL; l = l->next) {
1290 CajaFile *file;
1291
1292 file = CAJA_FILE (l->data);
1293 if (!caja_file_is_gone (file)) {
1294 return caja_file_get_string_attribute_with_default
1295 (file,
1296 attribute_name);
1297 }
1298 }
1299 return g_strdup (_("unknown"));
1300 } else {
1301 return g_strdup (inconsistent_value);
1302 }
1303 }
1304
1305
1306 static gboolean
file_list_all_directories(GList * file_list)1307 file_list_all_directories (GList *file_list)
1308 {
1309 GList *l;
1310 for (l = file_list; l != NULL; l = l->next) {
1311 if (!caja_file_is_directory (CAJA_FILE (l->data))) {
1312 return FALSE;
1313 }
1314 }
1315 return TRUE;
1316 }
1317
1318 static void
value_field_update_internal(GtkLabel * label,GList * file_list)1319 value_field_update_internal (GtkLabel *label,
1320 GList *file_list)
1321 {
1322 const char *attribute_name;
1323 char *attribute_value;
1324 char *inconsistent_string;
1325
1326 g_assert (GTK_IS_LABEL (label));
1327
1328 attribute_name = g_object_get_data (G_OBJECT (label), "file_attribute");
1329 inconsistent_string = g_object_get_data (G_OBJECT (label), "inconsistent_string");
1330 attribute_value = file_list_get_string_attribute (file_list,
1331 attribute_name,
1332 inconsistent_string);
1333 if (!strcmp (attribute_name, "type") && strcmp (attribute_value, inconsistent_string)) {
1334 char *mime_type;
1335
1336 mime_type = file_list_get_string_attribute (file_list,
1337 "mime_type",
1338 inconsistent_string);
1339 if (strcmp (mime_type, inconsistent_string)) {
1340 char *tmp;
1341
1342 tmp = attribute_value;
1343 attribute_value = g_strdup_printf (C_("MIME type description (MIME type)", "%s (%s)"), attribute_value, mime_type);
1344 g_free (tmp);
1345 }
1346 g_free (mime_type);
1347 }
1348
1349 gtk_label_set_text (label, attribute_value);
1350 g_free (attribute_value);
1351 }
1352
1353 static void
value_field_update(FMPropertiesWindow * window,GtkLabel * label)1354 value_field_update (FMPropertiesWindow *window, GtkLabel *label)
1355 {
1356 gboolean use_original;
1357
1358 use_original = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (label), "show_original"));
1359
1360 value_field_update_internal (label,
1361 (use_original ?
1362 window->details->original_files :
1363 window->details->target_files));
1364 }
1365
1366 static GtkLabel *
attach_label(GtkGrid * grid,GtkWidget * sibling,const char * initial_text,gboolean ellipsize_text,gboolean selectable,gboolean mnemonic)1367 attach_label (GtkGrid *grid,
1368 GtkWidget *sibling,
1369 const char *initial_text,
1370 gboolean ellipsize_text,
1371 gboolean selectable,
1372 gboolean mnemonic)
1373 {
1374 GtkWidget *label_field;
1375
1376 if (ellipsize_text) {
1377 label_field = gtk_label_new (initial_text);
1378 gtk_label_set_ellipsize (GTK_LABEL (label_field),
1379 PANGO_ELLIPSIZE_END);
1380 } else if (mnemonic) {
1381 label_field = gtk_label_new_with_mnemonic (initial_text);
1382 } else {
1383 label_field = gtk_label_new (initial_text);
1384 }
1385
1386 if (selectable) {
1387 gtk_label_set_selectable (GTK_LABEL (label_field), TRUE);
1388 }
1389
1390 gtk_label_set_xalign (GTK_LABEL (label_field), 0);
1391 gtk_widget_show (label_field);
1392 if (ellipsize_text) {
1393 gtk_widget_set_hexpand (label_field, TRUE);
1394 gtk_label_set_max_width_chars (GTK_LABEL (label_field), 24);
1395 }
1396
1397 if (sibling != NULL) {
1398 gtk_grid_attach_next_to (grid, label_field, sibling,
1399 GTK_POS_RIGHT, 1, 1);
1400 } else {
1401 gtk_container_add (GTK_CONTAINER (grid), label_field);
1402 }
1403
1404 return GTK_LABEL (label_field);
1405 }
1406
1407 static GtkLabel *
attach_value_label(GtkGrid * grid,GtkWidget * sibling,const char * initial_text)1408 attach_value_label (GtkGrid *grid,
1409 GtkWidget *sibling,
1410 const char *initial_text)
1411 {
1412 return attach_label (grid, sibling, initial_text, FALSE, TRUE, FALSE);
1413 }
1414
1415 static GtkLabel *
attach_ellipsizing_value_label(GtkGrid * grid,GtkWidget * sibling,const char * initial_text)1416 attach_ellipsizing_value_label (GtkGrid *grid,
1417 GtkWidget *sibling,
1418 const char *initial_text)
1419 {
1420 return attach_label (grid, sibling, initial_text, TRUE, TRUE, FALSE);
1421 }
1422
1423 static GtkWidget*
attach_value_field_internal(FMPropertiesWindow * window,GtkGrid * grid,GtkWidget * sibling,const char * file_attribute_name,const char * inconsistent_string,gboolean show_original,gboolean ellipsize_text)1424 attach_value_field_internal (FMPropertiesWindow *window,
1425 GtkGrid *grid,
1426 GtkWidget *sibling,
1427 const char *file_attribute_name,
1428 const char *inconsistent_string,
1429 gboolean show_original,
1430 gboolean ellipsize_text)
1431 {
1432 GtkLabel *value_field;
1433
1434 if (ellipsize_text) {
1435 value_field = attach_ellipsizing_value_label (grid, sibling, "");
1436 } else {
1437 value_field = attach_value_label (grid, sibling, "");
1438 }
1439
1440 /* Stash a copy of the file attribute name in this field for the callback's sake. */
1441 g_object_set_data_full (G_OBJECT (value_field), "file_attribute",
1442 g_strdup (file_attribute_name), g_free);
1443
1444 g_object_set_data_full (G_OBJECT (value_field), "inconsistent_string",
1445 g_strdup (inconsistent_string), g_free);
1446
1447 g_object_set_data (G_OBJECT (value_field), "show_original", GINT_TO_POINTER (show_original));
1448
1449 window->details->value_fields = g_list_prepend (window->details->value_fields,
1450 value_field);
1451 return GTK_WIDGET(value_field);
1452 }
1453
1454 static GtkWidget*
attach_value_field(FMPropertiesWindow * window,GtkGrid * grid,GtkWidget * sibling,const char * file_attribute_name,const char * inconsistent_string,gboolean show_original)1455 attach_value_field (FMPropertiesWindow *window,
1456 GtkGrid *grid,
1457 GtkWidget *sibling,
1458 const char *file_attribute_name,
1459 const char *inconsistent_string,
1460 gboolean show_original)
1461 {
1462 return attach_value_field_internal (window,
1463 grid, sibling,
1464 file_attribute_name,
1465 inconsistent_string,
1466 show_original,
1467 FALSE);
1468 }
1469
1470 static GtkWidget*
attach_ellipsizing_value_field(FMPropertiesWindow * window,GtkGrid * grid,GtkWidget * sibling,const char * file_attribute_name,const char * inconsistent_string,gboolean show_original)1471 attach_ellipsizing_value_field (FMPropertiesWindow *window,
1472 GtkGrid *grid,
1473 GtkWidget *sibling,
1474 const char *file_attribute_name,
1475 const char *inconsistent_string,
1476 gboolean show_original)
1477 {
1478 return attach_value_field_internal (window,
1479 grid, sibling,
1480 file_attribute_name,
1481 inconsistent_string,
1482 show_original,
1483 TRUE);
1484 }
1485
1486 static void
group_change_callback(CajaFile * file,GFile * res_loc,GError * error,FMPropertiesWindow * window)1487 group_change_callback (CajaFile *file,
1488 GFile *res_loc,
1489 GError *error,
1490 FMPropertiesWindow *window)
1491 {
1492 char *group;
1493
1494 g_assert (FM_IS_PROPERTIES_WINDOW (window));
1495 g_assert (window->details->group_change_file == file);
1496
1497 group = window->details->group_change_group;
1498 g_assert (group != NULL);
1499
1500 /* Report the error if it's an error. */
1501 eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, window);
1502 fm_report_error_setting_group (file, error, GTK_WINDOW (window));
1503
1504 caja_file_unref (file);
1505 g_free (group);
1506
1507 window->details->group_change_file = NULL;
1508 window->details->group_change_group = NULL;
1509 g_object_unref (G_OBJECT (window));
1510 }
1511
1512 static void
cancel_group_change_callback(FMPropertiesWindow * window)1513 cancel_group_change_callback (FMPropertiesWindow *window)
1514 {
1515 CajaFile *file;
1516 char *group;
1517
1518 file = window->details->group_change_file;
1519 g_assert (CAJA_IS_FILE (file));
1520
1521 group = window->details->group_change_group;
1522 g_assert (group != NULL);
1523
1524 caja_file_cancel (file, (CajaFileOperationCallback) group_change_callback, window);
1525
1526 g_free (group);
1527 caja_file_unref (file);
1528
1529 window->details->group_change_file = NULL;
1530 window->details->group_change_group = NULL;
1531 g_object_unref (window);
1532 }
1533
1534 static gboolean
schedule_group_change_timeout(FMPropertiesWindow * window)1535 schedule_group_change_timeout (FMPropertiesWindow *window)
1536 {
1537 CajaFile *file;
1538 char *group;
1539
1540 g_assert (FM_IS_PROPERTIES_WINDOW (window));
1541
1542 file = window->details->group_change_file;
1543 g_assert (CAJA_IS_FILE (file));
1544
1545 group = window->details->group_change_group;
1546 g_assert (group != NULL);
1547
1548 eel_timed_wait_start
1549 ((EelCancelCallback) cancel_group_change_callback,
1550 window,
1551 _("Cancel Group Change?"),
1552 GTK_WINDOW (window));
1553
1554 caja_file_set_group
1555 (file, group,
1556 (CajaFileOperationCallback) group_change_callback, window);
1557
1558 window->details->group_change_timeout = 0;
1559 return FALSE;
1560 }
1561
1562 static void
schedule_group_change(FMPropertiesWindow * window,CajaFile * file,const char * group)1563 schedule_group_change (FMPropertiesWindow *window,
1564 CajaFile *file,
1565 const char *group)
1566 {
1567 g_assert (FM_IS_PROPERTIES_WINDOW (window));
1568 g_assert (window->details->group_change_group == NULL);
1569 g_assert (window->details->group_change_file == NULL);
1570 g_assert (CAJA_IS_FILE (file));
1571
1572 window->details->group_change_file = caja_file_ref (file);
1573 window->details->group_change_group = g_strdup (group);
1574 g_object_ref (G_OBJECT (window));
1575 window->details->group_change_timeout =
1576 g_timeout_add (CHOWN_CHGRP_TIMEOUT,
1577 (GSourceFunc) schedule_group_change_timeout,
1578 window);
1579 }
1580
1581 static void
unschedule_or_cancel_group_change(FMPropertiesWindow * window)1582 unschedule_or_cancel_group_change (FMPropertiesWindow *window)
1583 {
1584 CajaFile *file;
1585 char *group;
1586
1587 g_assert (FM_IS_PROPERTIES_WINDOW (window));
1588
1589 file = window->details->group_change_file;
1590 group = window->details->group_change_group;
1591
1592 g_assert ((file == NULL && group == NULL) ||
1593 (file != NULL && group != NULL));
1594
1595 if (file != NULL) {
1596 g_assert (CAJA_IS_FILE (file));
1597
1598 if (window->details->group_change_timeout == 0) {
1599 caja_file_cancel (file,
1600 (CajaFileOperationCallback) group_change_callback, window);
1601 eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, window);
1602 }
1603
1604 caja_file_unref (file);
1605 g_free (group);
1606
1607 window->details->group_change_file = NULL;
1608 window->details->group_change_group = NULL;
1609 g_object_unref (G_OBJECT (window));
1610 }
1611
1612 if (window->details->group_change_timeout > 0) {
1613 g_assert (file != NULL);
1614 g_source_remove (window->details->group_change_timeout);
1615 window->details->group_change_timeout = 0;
1616 }
1617 }
1618
1619 static void
changed_group_callback(GtkComboBox * combo_box,CajaFile * file)1620 changed_group_callback (GtkComboBox *combo_box, CajaFile *file)
1621 {
1622 char *group;
1623 char *cur_group;
1624
1625 g_assert (GTK_IS_COMBO_BOX (combo_box));
1626 g_assert (CAJA_IS_FILE (file));
1627
1628 group = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (combo_box));
1629 cur_group = caja_file_get_group_name (file);
1630
1631 if (group != NULL && strcmp (group, cur_group) != 0) {
1632 FMPropertiesWindow *window;
1633
1634 /* Try to change file group. If this fails, complain to user. */
1635 window = FM_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW));
1636
1637 unschedule_or_cancel_group_change (window);
1638 schedule_group_change (window, file, group);
1639 }
1640 g_free (group);
1641 g_free (cur_group);
1642 }
1643
1644 /* checks whether the given column at the first level
1645 * of model has the specified entries in the given order. */
1646 static gboolean
tree_model_entries_equal(GtkTreeModel * model,unsigned int column,GList * entries)1647 tree_model_entries_equal (GtkTreeModel *model,
1648 unsigned int column,
1649 GList *entries)
1650 {
1651 GtkTreeIter iter;
1652 gboolean empty_model;
1653
1654 g_assert (GTK_IS_TREE_MODEL (model));
1655 g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING);
1656
1657 empty_model = !gtk_tree_model_get_iter_first (model, &iter);
1658
1659 if (!empty_model && entries != NULL) {
1660 GList *l;
1661
1662 l = entries;
1663
1664 do {
1665 char *val;
1666
1667 gtk_tree_model_get (model, &iter,
1668 column, &val,
1669 -1);
1670 if ((val == NULL && l->data != NULL) ||
1671 (val != NULL && l->data == NULL) ||
1672 (val != NULL && strcmp (val, l->data))) {
1673 g_free (val);
1674 return FALSE;
1675 }
1676
1677 g_free (val);
1678 l = l->next;
1679 } while (gtk_tree_model_iter_next (model, &iter));
1680
1681 return l == NULL;
1682 } else {
1683 return (empty_model && entries == NULL) ||
1684 (!empty_model && entries != NULL);
1685 }
1686 }
1687
1688 static char *
combo_box_get_active_entry(GtkComboBox * combo_box,unsigned int column)1689 combo_box_get_active_entry (GtkComboBox *combo_box,
1690 unsigned int column)
1691 {
1692 GtkTreeIter iter;
1693 char *val;
1694
1695 g_assert (GTK_IS_COMBO_BOX (combo_box));
1696
1697 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter)) {
1698 GtkTreeModel *model;
1699
1700 model = gtk_combo_box_get_model (combo_box);
1701 g_assert (GTK_IS_TREE_MODEL (model));
1702
1703 gtk_tree_model_get (model, &iter,
1704 column, &val,
1705 -1);
1706 return val;
1707 }
1708
1709 return NULL;
1710 }
1711
1712 /* returns the index of the given entry in the the given column
1713 * at the first level of model. Returns -1 if entry can't be found
1714 * or entry is NULL.
1715 * */
1716 static int
tree_model_get_entry_index(GtkTreeModel * model,unsigned int column,const char * entry)1717 tree_model_get_entry_index (GtkTreeModel *model,
1718 unsigned int column,
1719 const char *entry)
1720 {
1721 GtkTreeIter iter;
1722 gboolean empty_model;
1723
1724 g_assert (GTK_IS_TREE_MODEL (model));
1725 g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING);
1726
1727 empty_model = !gtk_tree_model_get_iter_first (model, &iter);
1728 if (!empty_model && entry != NULL) {
1729 int index;
1730
1731 index = 0;
1732
1733 do {
1734 char *val;
1735
1736 gtk_tree_model_get (model, &iter,
1737 column, &val,
1738 -1);
1739 if (val != NULL && !strcmp (val, entry)) {
1740 g_free (val);
1741 return index;
1742 }
1743
1744 g_free (val);
1745 index++;
1746 } while (gtk_tree_model_iter_next (model, &iter));
1747 }
1748
1749 return -1;
1750 }
1751
1752
1753 static void
synch_groups_combo_box(GtkComboBox * combo_box,CajaFile * file)1754 synch_groups_combo_box (GtkComboBox *combo_box, CajaFile *file)
1755 {
1756 GList *groups;
1757 GList *node;
1758 GtkTreeModel *model;
1759 GtkListStore *store;
1760 char *current_group_name;
1761 int current_group_index;
1762
1763 g_assert (GTK_IS_COMBO_BOX (combo_box));
1764 g_assert (CAJA_IS_FILE (file));
1765
1766 if (caja_file_is_gone (file)) {
1767 return;
1768 }
1769
1770 groups = caja_file_get_settable_group_names (file);
1771
1772 model = gtk_combo_box_get_model (combo_box);
1773 store = GTK_LIST_STORE (model);
1774 g_assert (GTK_IS_LIST_STORE (model));
1775
1776 if (!tree_model_entries_equal (model, 0, groups)) {
1777 int group_index;
1778
1779 /* Clear the contents of ComboBox in a wacky way because there
1780 * is no function to clear all items and also no function to obtain
1781 * the number of items in a combobox.
1782 */
1783 gtk_list_store_clear (store);
1784
1785 for (node = groups, group_index = 0; node != NULL; node = node->next, ++group_index) {
1786 const char *group_name;
1787
1788 group_name = (const char *)node->data;
1789 gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo_box), group_name);
1790 }
1791 }
1792
1793 current_group_name = caja_file_get_group_name (file);
1794 current_group_index = tree_model_get_entry_index (model, 0, current_group_name);
1795
1796 /* If current group wasn't in list, we prepend it (with a separator).
1797 * This can happen if the current group is an id with no matching
1798 * group in the groups file.
1799 */
1800 if (current_group_index < 0 && current_group_name != NULL) {
1801 if (groups != NULL) {
1802 /* add separator */
1803 gtk_combo_box_text_prepend_text (GTK_COMBO_BOX_TEXT (combo_box), "-");
1804 }
1805
1806 gtk_combo_box_text_prepend_text (GTK_COMBO_BOX_TEXT (combo_box), current_group_name);
1807 current_group_index = 0;
1808 }
1809 gtk_combo_box_set_active (combo_box, current_group_index);
1810
1811 g_free (current_group_name);
1812 g_list_free_full (groups, g_free);
1813 }
1814
1815 static gboolean
combo_box_row_separator_func(GtkTreeModel * model,GtkTreeIter * iter,gpointer data)1816 combo_box_row_separator_func (GtkTreeModel *model,
1817 GtkTreeIter *iter,
1818 gpointer data)
1819 {
1820 gchar *text;
1821 gboolean ret;
1822
1823 gtk_tree_model_get (model, iter, 0, &text, -1);
1824
1825 if (text == NULL) {
1826 return FALSE;
1827 }
1828
1829 if (strcmp (text, "-") == 0) {
1830 ret = TRUE;
1831 } else {
1832 ret = FALSE;
1833 }
1834
1835 g_free (text);
1836 return ret;
1837 }
1838
1839 static GtkComboBox *
attach_combo_box(GtkGrid * grid,GtkWidget * sibling,gboolean two_columns)1840 attach_combo_box (GtkGrid *grid,
1841 GtkWidget *sibling,
1842 gboolean two_columns)
1843 {
1844 GtkWidget *combo_box;
1845
1846 if (!two_columns) {
1847 combo_box = gtk_combo_box_text_new ();
1848 } else {
1849 GtkTreeModel *model;
1850 GtkCellRenderer *renderer;
1851
1852 model = GTK_TREE_MODEL (gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING));
1853 combo_box = gtk_combo_box_new_with_model (model);
1854 g_object_unref (G_OBJECT (model));
1855
1856 renderer = gtk_cell_renderer_text_new ();
1857 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, TRUE);
1858 gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo_box), renderer,
1859 "text", 0);
1860
1861 }
1862
1863 gtk_widget_set_halign (combo_box, GTK_ALIGN_START);
1864
1865 gtk_widget_show (combo_box);
1866
1867 gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo_box),
1868 combo_box_row_separator_func,
1869 NULL,
1870 NULL);
1871
1872 gtk_grid_attach_next_to (grid, combo_box, sibling,
1873 GTK_POS_RIGHT, 1, 1);
1874
1875 return GTK_COMBO_BOX (combo_box);
1876 }
1877
1878 static GtkComboBox*
attach_group_combo_box(GtkGrid * grid,GtkWidget * sibling,CajaFile * file)1879 attach_group_combo_box (GtkGrid *grid,
1880 GtkWidget *sibling,
1881 CajaFile *file)
1882 {
1883 GtkComboBox *combo_box;
1884
1885 combo_box = attach_combo_box (grid, sibling, FALSE);
1886
1887 synch_groups_combo_box (combo_box, file);
1888
1889 /* Connect to signal to update menu when file changes. */
1890 g_signal_connect_object (file, "changed",
1891 G_CALLBACK (synch_groups_combo_box),
1892 combo_box, G_CONNECT_SWAPPED);
1893 g_signal_connect_data (combo_box, "changed",
1894 G_CALLBACK (changed_group_callback),
1895 caja_file_ref (file),
1896 (GClosureNotify)caja_file_unref, 0);
1897
1898 return combo_box;
1899 }
1900
1901 static void
owner_change_callback(CajaFile * file,GFile * result_location,GError * error,FMPropertiesWindow * window)1902 owner_change_callback (CajaFile *file,
1903 GFile *result_location,
1904 GError *error,
1905 FMPropertiesWindow *window)
1906 {
1907 char *owner;
1908
1909 g_assert (FM_IS_PROPERTIES_WINDOW (window));
1910 g_assert (window->details->owner_change_file == file);
1911
1912 owner = window->details->owner_change_owner;
1913 g_assert (owner != NULL);
1914
1915 /* Report the error if it's an error. */
1916 eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, window);
1917 fm_report_error_setting_owner (file, error, GTK_WINDOW (window));
1918
1919 caja_file_unref (file);
1920 g_free (owner);
1921
1922 window->details->owner_change_file = NULL;
1923 window->details->owner_change_owner = NULL;
1924 g_object_unref (G_OBJECT (window));
1925 }
1926
1927 static void
cancel_owner_change_callback(FMPropertiesWindow * window)1928 cancel_owner_change_callback (FMPropertiesWindow *window)
1929 {
1930 CajaFile *file;
1931 char *owner;
1932
1933 file = window->details->owner_change_file;
1934 g_assert (CAJA_IS_FILE (file));
1935
1936 owner = window->details->owner_change_owner;
1937 g_assert (owner != NULL);
1938
1939 caja_file_cancel (file, (CajaFileOperationCallback) owner_change_callback, window);
1940
1941 caja_file_unref (file);
1942 g_free (owner);
1943
1944 window->details->owner_change_file = NULL;
1945 window->details->owner_change_owner = NULL;
1946 g_object_unref (window);
1947 }
1948
1949 static gboolean
schedule_owner_change_timeout(FMPropertiesWindow * window)1950 schedule_owner_change_timeout (FMPropertiesWindow *window)
1951 {
1952 CajaFile *file;
1953 char *owner;
1954
1955 g_assert (FM_IS_PROPERTIES_WINDOW (window));
1956
1957 file = window->details->owner_change_file;
1958 g_assert (CAJA_IS_FILE (file));
1959
1960 owner = window->details->owner_change_owner;
1961 g_assert (owner != NULL);
1962
1963 eel_timed_wait_start
1964 ((EelCancelCallback) cancel_owner_change_callback,
1965 window,
1966 _("Cancel Owner Change?"),
1967 GTK_WINDOW (window));
1968
1969 caja_file_set_owner
1970 (file, owner,
1971 (CajaFileOperationCallback) owner_change_callback, window);
1972
1973 window->details->owner_change_timeout = 0;
1974 return FALSE;
1975 }
1976
1977 static void
schedule_owner_change(FMPropertiesWindow * window,CajaFile * file,const char * owner)1978 schedule_owner_change (FMPropertiesWindow *window,
1979 CajaFile *file,
1980 const char *owner)
1981 {
1982 g_assert (FM_IS_PROPERTIES_WINDOW (window));
1983 g_assert (window->details->owner_change_owner == NULL);
1984 g_assert (window->details->owner_change_file == NULL);
1985 g_assert (CAJA_IS_FILE (file));
1986
1987 window->details->owner_change_file = caja_file_ref (file);
1988 window->details->owner_change_owner = g_strdup (owner);
1989 g_object_ref (G_OBJECT (window));
1990 window->details->owner_change_timeout =
1991 g_timeout_add (CHOWN_CHGRP_TIMEOUT,
1992 (GSourceFunc) schedule_owner_change_timeout,
1993 window);
1994 }
1995
1996 static void
unschedule_or_cancel_owner_change(FMPropertiesWindow * window)1997 unschedule_or_cancel_owner_change (FMPropertiesWindow *window)
1998 {
1999 CajaFile *file;
2000 char *owner;
2001
2002 g_assert (FM_IS_PROPERTIES_WINDOW (window));
2003
2004 file = window->details->owner_change_file;
2005 owner = window->details->owner_change_owner;
2006
2007 g_assert ((file == NULL && owner == NULL) ||
2008 (file != NULL && owner != NULL));
2009
2010 if (file != NULL) {
2011 g_assert (CAJA_IS_FILE (file));
2012
2013 if (window->details->owner_change_timeout == 0) {
2014 caja_file_cancel (file,
2015 (CajaFileOperationCallback) owner_change_callback, window);
2016 eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, window);
2017 }
2018
2019 caja_file_unref (file);
2020 g_free (owner);
2021
2022 window->details->owner_change_file = NULL;
2023 window->details->owner_change_owner = NULL;
2024 g_object_unref (G_OBJECT (window));
2025 }
2026
2027 if (window->details->owner_change_timeout > 0) {
2028 g_assert (file != NULL);
2029 g_source_remove (window->details->owner_change_timeout);
2030 window->details->owner_change_timeout = 0;
2031 }
2032 }
2033
2034 static void
changed_owner_callback(GtkComboBox * combo_box,CajaFile * file)2035 changed_owner_callback (GtkComboBox *combo_box, CajaFile* file)
2036 {
2037 char *owner_text;
2038 char **name_array;
2039 char *new_owner;
2040 char *cur_owner;
2041
2042 g_assert (GTK_IS_COMBO_BOX (combo_box));
2043 g_assert (CAJA_IS_FILE (file));
2044
2045 owner_text = combo_box_get_active_entry (combo_box, 0);
2046 if (! owner_text)
2047 return;
2048 name_array = g_strsplit (owner_text, " - ", 2);
2049 new_owner = name_array[0];
2050 g_free (owner_text);
2051 cur_owner = caja_file_get_owner_name (file);
2052
2053 if (strcmp (new_owner, cur_owner) != 0) {
2054 FMPropertiesWindow *window;
2055
2056 /* Try to change file owner. If this fails, complain to user. */
2057 window = FM_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW));
2058
2059 unschedule_or_cancel_owner_change (window);
2060 schedule_owner_change (window, file, new_owner);
2061 }
2062 g_strfreev (name_array);
2063 g_free (cur_owner);
2064 }
2065
2066 static void
synch_user_menu(GtkComboBox * combo_box,CajaFile * file)2067 synch_user_menu (GtkComboBox *combo_box, CajaFile *file)
2068 {
2069 GList *users;
2070 GList *node;
2071 GtkTreeModel *model;
2072 GtkListStore *store;
2073 GtkTreeIter iter;
2074 char *user_name;
2075 char *owner_name;
2076 int owner_index;
2077 char **name_array;
2078
2079 g_assert (GTK_IS_COMBO_BOX (combo_box));
2080 g_assert (CAJA_IS_FILE (file));
2081
2082 if (caja_file_is_gone (file)) {
2083 return;
2084 }
2085
2086 users = caja_get_user_names ();
2087
2088 model = gtk_combo_box_get_model (combo_box);
2089 store = GTK_LIST_STORE (model);
2090 g_assert (GTK_IS_LIST_STORE (model));
2091
2092 if (!tree_model_entries_equal (model, 1, users)) {
2093 int user_index;
2094
2095 /* Clear the contents of ComboBox in a wacky way because there
2096 * is no function to clear all items and also no function to obtain
2097 * the number of items in a combobox.
2098 */
2099 gtk_list_store_clear (store);
2100
2101 for (node = users, user_index = 0; node != NULL; node = node->next, ++user_index) {
2102 char *combo_text;
2103
2104 user_name = (char *)node->data;
2105
2106 name_array = g_strsplit (user_name, "\n", 2);
2107 if (name_array[1] != NULL) {
2108 combo_text = g_strdup_printf ("%s - %s", name_array[0], name_array[1]);
2109 } else {
2110 combo_text = g_strdup (name_array[0]);
2111 }
2112
2113 gtk_list_store_append (store, &iter);
2114 gtk_list_store_set (store, &iter,
2115 0, combo_text,
2116 1, user_name,
2117 -1);
2118
2119 g_strfreev (name_array);
2120 g_free (combo_text);
2121 }
2122 }
2123
2124 owner_name = caja_file_get_string_attribute (file, "owner");
2125 owner_index = tree_model_get_entry_index (model, 0, owner_name);
2126
2127 /* If owner wasn't in list, we prepend it (with a separator).
2128 * This can happen if the owner is an id with no matching
2129 * identifier in the passwords file.
2130 */
2131 if (owner_index < 0 && owner_name != NULL) {
2132 if (users != NULL) {
2133 /* add separator */
2134 gtk_list_store_prepend (store, &iter);
2135 gtk_list_store_set (store, &iter,
2136 0, "-",
2137 1, NULL,
2138 -1);
2139 }
2140
2141 name_array = g_strsplit (owner_name, " - ", 2);
2142 if (name_array[1] != NULL) {
2143 user_name = g_strdup_printf ("%s\n%s", name_array[0], name_array[1]);
2144 } else {
2145 user_name = g_strdup (name_array[0]);
2146 }
2147 owner_index = 0;
2148
2149 gtk_list_store_prepend (store, &iter);
2150 gtk_list_store_set (store, &iter,
2151 0, owner_name,
2152 1, user_name,
2153 -1);
2154
2155 g_free (user_name);
2156 g_strfreev (name_array);
2157 }
2158
2159 gtk_combo_box_set_active (combo_box, owner_index);
2160
2161 g_free (owner_name);
2162 g_list_free_full (users, g_free);
2163 }
2164
2165 static GtkComboBox*
attach_owner_combo_box(GtkGrid * grid,GtkWidget * sibling,CajaFile * file)2166 attach_owner_combo_box (GtkGrid *grid,
2167 GtkWidget *sibling,
2168 CajaFile *file)
2169 {
2170 GtkComboBox *combo_box;
2171
2172 combo_box = attach_combo_box (grid, sibling, TRUE);
2173
2174 synch_user_menu (combo_box, file);
2175
2176 /* Connect to signal to update menu when file changes. */
2177 g_signal_connect_object (file, "changed",
2178 G_CALLBACK (synch_user_menu),
2179 combo_box, G_CONNECT_SWAPPED);
2180 g_signal_connect_data (combo_box, "changed",
2181 G_CALLBACK (changed_owner_callback),
2182 caja_file_ref (file),
2183 (GClosureNotify)caja_file_unref, 0);
2184
2185 return combo_box;
2186 }
2187
2188 static gboolean
file_has_prefix(CajaFile * file,GList * prefix_candidates)2189 file_has_prefix (CajaFile *file,
2190 GList *prefix_candidates)
2191 {
2192 GList *p;
2193 GFile *location, *candidate_location;
2194
2195 location = caja_file_get_location (file);
2196
2197 for (p = prefix_candidates; p != NULL; p = p->next) {
2198 if (file == p->data) {
2199 continue;
2200 }
2201
2202 candidate_location = caja_file_get_location (CAJA_FILE (p->data));
2203 if (g_file_has_prefix (location, candidate_location)) {
2204 g_object_unref (location);
2205 g_object_unref (candidate_location);
2206 return TRUE;
2207 }
2208 g_object_unref (candidate_location);
2209 }
2210
2211 g_object_unref (location);
2212
2213 return FALSE;
2214 }
2215
2216 static void
directory_contents_value_field_update(FMPropertiesWindow * window)2217 directory_contents_value_field_update (FMPropertiesWindow *window)
2218 {
2219 CajaRequestStatus file_status, status;
2220 char *text, *temp;
2221 guint directory_count;
2222 guint file_count;
2223 guint total_count;
2224 guint unreadable_directory_count;
2225 goffset total_size;
2226 goffset total_size_on_disk;
2227 gboolean used_two_lines;
2228 GList *l;
2229 guint file_unreadable;
2230 goffset file_size;
2231 goffset file_size_on_disk;
2232 CajaFile *file = NULL;
2233
2234 g_assert (FM_IS_PROPERTIES_WINDOW (window));
2235
2236 status = CAJA_REQUEST_DONE;
2237 file_status = CAJA_REQUEST_NOT_STARTED;
2238 total_count = window->details->total_count;
2239 total_size = window->details->total_size;
2240 total_size_on_disk = window->details->total_size_on_disk;
2241 unreadable_directory_count = FALSE;
2242
2243 for (l = window->details->target_files; l; l = l->next) {
2244 file = CAJA_FILE (l->data);
2245
2246 if (file_has_prefix (file, window->details->target_files)) {
2247 /* don't count nested files twice */
2248 continue;
2249 }
2250
2251 if (caja_file_is_directory (file)) {
2252 file_status = caja_file_get_deep_counts (file,
2253 &directory_count,
2254 &file_count,
2255 &file_unreadable,
2256 &file_size,
2257 &file_size_on_disk,
2258 TRUE);
2259 total_count += (file_count + directory_count);
2260 total_size += file_size;
2261 total_size_on_disk += file_size_on_disk;
2262
2263 if (file_unreadable) {
2264 unreadable_directory_count = TRUE;
2265 }
2266
2267 if (file_status != CAJA_REQUEST_DONE) {
2268 status = file_status;
2269 }
2270 } else {
2271 ++total_count;
2272 total_size += caja_file_get_size (file);
2273 total_size_on_disk += caja_file_get_size_on_disk (file);
2274 }
2275 }
2276
2277 /* If we've already displayed the total once, don't do another visible
2278 * count-up if the deep_count happens to get invalidated.
2279 * But still display the new total, since it might have changed.
2280 */
2281 if (window->details->deep_count_finished &&
2282 status != CAJA_REQUEST_DONE) {
2283 return;
2284 }
2285
2286 text = NULL;
2287 used_two_lines = FALSE;
2288
2289 if (total_count == 0) {
2290 switch (status) {
2291 case CAJA_REQUEST_DONE:
2292 if (unreadable_directory_count == 0) {
2293 text = g_strdup (_("nothing"));
2294 } else {
2295 text = g_strdup (_("unreadable"));
2296 }
2297
2298 break;
2299 default:
2300 text = g_strdup ("...");
2301 }
2302 } else {
2303 char *size_str;
2304 char *size_on_disk_str;
2305
2306 if (g_settings_get_boolean (caja_preferences, CAJA_PREFERENCES_USE_IEC_UNITS)) {
2307 size_str = g_format_size_full (total_size, G_FORMAT_SIZE_IEC_UNITS);
2308 size_on_disk_str = g_format_size_full (total_size_on_disk, G_FORMAT_SIZE_IEC_UNITS);
2309 } else {
2310 size_str = g_format_size (total_size);
2311 size_on_disk_str = g_format_size (total_size_on_disk);
2312 }
2313
2314 text = g_strdup_printf (ngettext("%'d item, with size %s (%s on disk)",
2315 "%'d items, totalling %s (%s on disk)",
2316 total_count),
2317 total_count, size_str, size_on_disk_str);
2318 g_free (size_str);
2319 g_free (size_on_disk_str);
2320
2321 if (unreadable_directory_count != 0) {
2322 temp = text;
2323 text = g_strconcat (temp, "\n",
2324 _("(some contents unreadable)"),
2325 NULL);
2326 g_free (temp);
2327 used_two_lines = TRUE;
2328 }
2329 }
2330
2331 gtk_label_set_text (window->details->directory_contents_value_field,
2332 text);
2333 g_free (text);
2334
2335 /* Also set the title field here, with a trailing carriage return &
2336 * space if the value field has two lines. This is a hack to get the
2337 * "Contents:" title to line up with the first line of the
2338 * 2-line value. Maybe there's a better way to do this, but I
2339 * couldn't think of one.
2340 */
2341 text = g_strdup (_("Contents:"));
2342 if (used_two_lines) {
2343 temp = text;
2344 text = g_strconcat (temp, "\n ", NULL);
2345 g_free (temp);
2346 }
2347 gtk_label_set_text (window->details->directory_contents_title_field,
2348 text);
2349 g_free (text);
2350
2351 if (status == CAJA_REQUEST_DONE) {
2352 window->details->deep_count_finished = TRUE;
2353 }
2354 }
2355
2356 static gboolean
update_directory_contents_callback(gpointer data)2357 update_directory_contents_callback (gpointer data)
2358 {
2359 FMPropertiesWindow *window;
2360
2361 window = FM_PROPERTIES_WINDOW (data);
2362
2363 window->details->update_directory_contents_timeout_id = 0;
2364 directory_contents_value_field_update (window);
2365
2366 return FALSE;
2367 }
2368
2369 static void
schedule_directory_contents_update(FMPropertiesWindow * window)2370 schedule_directory_contents_update (FMPropertiesWindow *window)
2371 {
2372 g_assert (FM_IS_PROPERTIES_WINDOW (window));
2373
2374 if (window->details->update_directory_contents_timeout_id == 0) {
2375 window->details->update_directory_contents_timeout_id
2376 = g_timeout_add (DIRECTORY_CONTENTS_UPDATE_INTERVAL,
2377 update_directory_contents_callback,
2378 window);
2379 }
2380 }
2381
2382 static GtkLabel *
attach_directory_contents_value_field(FMPropertiesWindow * window,GtkGrid * grid,GtkWidget * sibling)2383 attach_directory_contents_value_field (FMPropertiesWindow *window,
2384 GtkGrid *grid,
2385 GtkWidget *sibling)
2386 {
2387 GtkLabel *value_field;
2388 GList *l;
2389 CajaFile *file = NULL;
2390
2391 value_field = attach_value_label (grid, sibling, "");
2392
2393 g_assert (window->details->directory_contents_value_field == NULL);
2394 window->details->directory_contents_value_field = value_field;
2395
2396 gtk_label_set_line_wrap (value_field, TRUE);
2397
2398 /* Fill in the initial value. */
2399 directory_contents_value_field_update (window);
2400
2401 for (l = window->details->target_files; l; l = l->next) {
2402 file = CAJA_FILE (l->data);
2403 caja_file_recompute_deep_counts (file);
2404
2405 g_signal_connect_object (file,
2406 "updated_deep_count_in_progress",
2407 G_CALLBACK (schedule_directory_contents_update),
2408 window, G_CONNECT_SWAPPED);
2409 }
2410
2411 return value_field;
2412 }
2413
2414 static GtkLabel *
attach_title_field(GtkGrid * grid,const char * title)2415 attach_title_field (GtkGrid *grid,
2416 const char *title)
2417 {
2418 return attach_label (grid, NULL, title, FALSE, FALSE, TRUE);
2419 }
2420
2421
2422
2423 #define INCONSISTENT_STATE_STRING \
2424 "\xE2\x80\x92"
2425
2426 static void
append_title_value_pair(FMPropertiesWindow * window,GtkGrid * grid,const char * title,const char * file_attribute_name,const char * inconsistent_state,gboolean show_original)2427 append_title_value_pair (FMPropertiesWindow *window,
2428 GtkGrid *grid,
2429 const char *title,
2430 const char *file_attribute_name,
2431 const char *inconsistent_state,
2432 gboolean show_original)
2433 {
2434 GtkLabel *title_label;
2435 GtkWidget *value;
2436
2437 title_label = attach_title_field (grid, title);
2438 value = attach_value_field (window, grid, GTK_WIDGET (title_label),
2439 file_attribute_name,
2440 inconsistent_state,
2441 show_original);
2442 gtk_label_set_mnemonic_widget (title_label, value);
2443 }
2444
2445 static void
append_title_and_ellipsizing_value(FMPropertiesWindow * window,GtkGrid * grid,const char * title,const char * file_attribute_name,const char * inconsistent_state,gboolean show_original)2446 append_title_and_ellipsizing_value (FMPropertiesWindow *window,
2447 GtkGrid *grid,
2448 const char *title,
2449 const char *file_attribute_name,
2450 const char *inconsistent_state,
2451 gboolean show_original)
2452 {
2453 GtkLabel *title_label;
2454 GtkWidget *value;
2455
2456 title_label = attach_title_field (grid, title);
2457 value = attach_ellipsizing_value_field (window, grid,
2458 GTK_WIDGET (title_label),
2459 file_attribute_name,
2460 inconsistent_state,
2461 show_original);
2462 gtk_label_set_mnemonic_widget (title_label, value);
2463 }
2464
2465 static void
append_directory_contents_fields(FMPropertiesWindow * window,GtkGrid * grid)2466 append_directory_contents_fields (FMPropertiesWindow *window,
2467 GtkGrid *grid)
2468 {
2469 GtkLabel *title_field, *value_field;
2470 title_field = attach_title_field (grid, "");
2471 window->details->directory_contents_title_field = title_field;
2472 gtk_label_set_line_wrap (title_field, TRUE);
2473
2474 value_field = attach_directory_contents_value_field
2475 (window, grid, GTK_WIDGET (title_field));
2476
2477 gtk_label_set_mnemonic_widget (title_field, GTK_WIDGET(value_field));
2478 }
2479
2480 static GtkWidget *
create_page_with_hbox(GtkNotebook * notebook,const char * title)2481 create_page_with_hbox (GtkNotebook *notebook,
2482 const char *title)
2483 {
2484 GtkWidget *hbox;
2485
2486 g_assert (GTK_IS_NOTEBOOK (notebook));
2487 g_assert (title != NULL);
2488
2489 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
2490 gtk_widget_show (hbox);
2491 gtk_container_set_border_width (GTK_CONTAINER (hbox), 12);
2492 gtk_box_set_spacing (GTK_BOX (hbox), 12);
2493 gtk_notebook_append_page (notebook, hbox, gtk_label_new (title));
2494
2495 return hbox;
2496 }
2497
2498 static GtkWidget *
create_page_with_vbox(GtkNotebook * notebook,const char * title)2499 create_page_with_vbox (GtkNotebook *notebook,
2500 const char *title)
2501 {
2502 GtkWidget *vbox;
2503
2504 g_assert (GTK_IS_NOTEBOOK (notebook));
2505 g_assert (title != NULL);
2506
2507 vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
2508 gtk_widget_show (vbox);
2509
2510 gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
2511 gtk_notebook_append_page (notebook, vbox, gtk_label_new (title));
2512
2513 return vbox;
2514 }
2515
2516 static GtkWidget *
append_blank_row(GtkGrid * grid)2517 append_blank_row (GtkGrid *grid)
2518 {
2519 return GTK_WIDGET (attach_title_field (grid, ""));
2520 }
2521
2522 static void
append_blank_slim_row(GtkGrid * grid)2523 append_blank_slim_row (GtkGrid *grid)
2524 {
2525 GtkWidget *w;
2526 PangoAttribute *attribute;
2527 PangoAttrList *attr_list;
2528
2529 attr_list = pango_attr_list_new ();
2530 attribute = pango_attr_scale_new (0.30);
2531 pango_attr_list_insert (attr_list, attribute);
2532
2533 w = gtk_label_new (NULL);
2534 gtk_label_set_attributes (GTK_LABEL (w), attr_list);
2535 gtk_widget_show (w);
2536
2537 pango_attr_list_unref (attr_list);
2538
2539 gtk_container_add (GTK_CONTAINER (grid), w);
2540 }
2541
2542 static GtkWidget *
create_grid_with_standard_properties(void)2543 create_grid_with_standard_properties (void)
2544 {
2545 GtkWidget *grid;
2546
2547 grid = gtk_grid_new ();
2548 gtk_container_set_border_width (GTK_CONTAINER (grid), 6);
2549 gtk_grid_set_row_spacing (GTK_GRID (grid), ROW_PAD);
2550 gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
2551 gtk_orientable_set_orientation (GTK_ORIENTABLE (grid), GTK_ORIENTATION_VERTICAL);
2552 gtk_widget_show (grid);
2553
2554 return grid;
2555 }
2556
2557 static gboolean
is_merged_trash_directory(CajaFile * file)2558 is_merged_trash_directory (CajaFile *file)
2559 {
2560 char *file_uri;
2561 gboolean result;
2562
2563 file_uri = caja_file_get_uri (file);
2564 result = strcmp (file_uri, "trash:///") == 0;
2565 g_free (file_uri);
2566
2567 return result;
2568 }
2569
2570 static gboolean
is_computer_directory(CajaFile * file)2571 is_computer_directory (CajaFile *file)
2572 {
2573 char *file_uri;
2574 gboolean result;
2575
2576 file_uri = caja_file_get_uri (file);
2577 result = strcmp (file_uri, "computer:///") == 0;
2578 g_free (file_uri);
2579
2580 return result;
2581 }
2582
2583 static gboolean
is_network_directory(CajaFile * file)2584 is_network_directory (CajaFile *file)
2585 {
2586 char *file_uri;
2587 gboolean result;
2588
2589 file_uri = caja_file_get_uri (file);
2590 result = strcmp (file_uri, "network:///") == 0;
2591 g_free (file_uri);
2592
2593 return result;
2594 }
2595
2596 static gboolean
is_burn_directory(CajaFile * file)2597 is_burn_directory (CajaFile *file)
2598 {
2599 char *file_uri;
2600 gboolean result;
2601
2602 file_uri = caja_file_get_uri (file);
2603 result = strcmp (file_uri, "burn:///") == 0;
2604 g_free (file_uri);
2605
2606 return result;
2607 }
2608
2609 static gboolean
should_show_custom_icon_buttons(FMPropertiesWindow * window)2610 should_show_custom_icon_buttons (FMPropertiesWindow *window)
2611 {
2612 if (is_multi_file_window (window)) {
2613 return FALSE;
2614 }
2615
2616 return TRUE;
2617 }
2618
2619 static gboolean
should_show_file_type(FMPropertiesWindow * window)2620 should_show_file_type (FMPropertiesWindow *window)
2621 {
2622 if (!is_multi_file_window (window)
2623 && (is_merged_trash_directory (get_target_file (window)) ||
2624 is_computer_directory (get_target_file (window)) ||
2625 is_network_directory (get_target_file (window)) ||
2626 is_burn_directory (get_target_file (window)))) {
2627 return FALSE;
2628 }
2629
2630
2631 return TRUE;
2632 }
2633
2634 static gboolean
should_show_location_info(FMPropertiesWindow * window)2635 should_show_location_info (FMPropertiesWindow *window)
2636 {
2637 if (!is_multi_file_window (window)
2638 && (is_merged_trash_directory (get_target_file (window)) ||
2639 is_computer_directory (get_target_file (window)) ||
2640 is_network_directory (get_target_file (window)) ||
2641 is_burn_directory (get_target_file (window)))) {
2642 return FALSE;
2643 }
2644
2645 return TRUE;
2646 }
2647
2648 static gboolean
should_show_accessed_date(FMPropertiesWindow * window)2649 should_show_accessed_date (FMPropertiesWindow *window)
2650 {
2651 /* Accessed date for directory seems useless. If we some
2652 * day decide that it is useful, we should separately
2653 * consider whether it's useful for "trash:".
2654 */
2655 if (file_list_all_directories (window->details->target_files)
2656 || is_multi_file_window (window))
2657 {
2658 return FALSE;
2659 }
2660
2661 return TRUE;
2662 }
2663
2664 static gboolean
should_show_modified_date(FMPropertiesWindow * window)2665 should_show_modified_date (FMPropertiesWindow *window)
2666 {
2667 CajaFile *file;
2668
2669 if (is_multi_file_window (window))
2670 return FALSE;
2671
2672 file = get_original_file (window);
2673 if ((file != NULL) && caja_file_can_unmount (file))
2674 return FALSE;
2675
2676 return TRUE;
2677 }
2678
2679 static gboolean
should_show_link_target(FMPropertiesWindow * window)2680 should_show_link_target (FMPropertiesWindow *window)
2681 {
2682 if (!is_multi_file_window (window)
2683 && caja_file_is_symbolic_link (get_target_file (window))) {
2684 return TRUE;
2685 }
2686
2687 return FALSE;
2688 }
2689
2690 static gboolean
should_show_free_space(FMPropertiesWindow * window)2691 should_show_free_space (FMPropertiesWindow *window)
2692 {
2693
2694 if (!is_multi_file_window (window)
2695 && (is_merged_trash_directory (get_target_file (window)) ||
2696 is_computer_directory (get_target_file (window)) ||
2697 is_network_directory (get_target_file (window)) ||
2698 is_burn_directory (get_target_file (window)))) {
2699 return FALSE;
2700 }
2701
2702 if (file_list_all_directories (window->details->target_files)) {
2703 return TRUE;
2704 }
2705
2706 return FALSE;
2707 }
2708
2709 static gboolean
should_show_volume_usage(FMPropertiesWindow * window)2710 should_show_volume_usage (FMPropertiesWindow *window)
2711 {
2712 CajaFile *file;
2713 gboolean success = FALSE;
2714
2715 if (is_multi_file_window (window)) {
2716 return FALSE;
2717 }
2718
2719 file = get_original_file (window);
2720
2721 if (file == NULL) {
2722 return FALSE;
2723 }
2724
2725 if (caja_file_can_unmount (file)) {
2726 return TRUE;
2727 }
2728
2729 #ifdef TODO_GIO
2730 /* Look at is_mountpoint for activation uri */
2731 #endif
2732 return success;
2733 }
2734
2735 static void
paint_used_legend(GtkWidget * widget,cairo_t * cr,gpointer data)2736 paint_used_legend (GtkWidget *widget,
2737 cairo_t *cr,
2738 gpointer data)
2739 {
2740 FMPropertiesWindow *window;
2741 gint width, height;
2742 GtkAllocation allocation;
2743
2744 gtk_widget_get_allocation (widget, &allocation);
2745
2746 width = allocation.width;
2747 height = allocation.height;
2748
2749 window = FM_PROPERTIES_WINDOW (data);
2750
2751 cairo_rectangle (cr,
2752 2,
2753 2,
2754 width - 4,
2755 height - 4);
2756
2757 gdk_cairo_set_source_rgba (cr, &window->details->used_color);
2758 cairo_fill_preserve (cr);
2759
2760 gdk_cairo_set_source_rgba (cr, &window->details->used_stroke_color);
2761 cairo_stroke (cr);
2762 }
2763
2764 static void
paint_free_legend(GtkWidget * widget,cairo_t * cr,gpointer data)2765 paint_free_legend (GtkWidget *widget,
2766 cairo_t *cr, gpointer data)
2767 {
2768 FMPropertiesWindow *window;
2769 gint width, height;
2770 GtkAllocation allocation;
2771
2772 window = FM_PROPERTIES_WINDOW (data);
2773 gtk_widget_get_allocation (widget, &allocation);
2774
2775 width = allocation.width;
2776 height = allocation.height;
2777
2778 cairo_rectangle (cr,
2779 2,
2780 2,
2781 width - 4,
2782 height - 4);
2783
2784 gdk_cairo_set_source_rgba (cr, &window->details->free_color);
2785 cairo_fill_preserve(cr);
2786
2787 gdk_cairo_set_source_rgba (cr, &window->details->free_stroke_color);
2788 cairo_stroke (cr);
2789 }
2790
2791 static void
paint_pie_chart(GtkWidget * widget,cairo_t * cr,gpointer data)2792 paint_pie_chart (GtkWidget *widget,
2793 cairo_t *cr,
2794 gpointer data)
2795 {
2796
2797 FMPropertiesWindow *window;
2798 gint width, height;
2799 double free, used;
2800 double angle1, angle2, split, xc, yc, radius;
2801 GtkAllocation allocation;
2802
2803 window = FM_PROPERTIES_WINDOW (data);
2804 gtk_widget_get_allocation (widget, &allocation);
2805
2806 width = allocation.width;
2807 height = allocation.height;
2808
2809
2810 free = (double)window->details->volume_free / (double)window->details->volume_capacity;
2811 used = 1.0 - free;
2812
2813 angle1 = free * 2 * G_PI;
2814 angle2 = used * 2 * G_PI;
2815 split = (2 * G_PI - angle1) * .5;
2816 xc = width / 2;
2817 yc = height / 2;
2818
2819 if (width < height) {
2820 radius = width / 2 - 8;
2821 } else {
2822 radius = height / 2 - 8;
2823 }
2824
2825 if (angle1 != 2 * G_PI && angle1 != 0) {
2826 angle1 = angle1 + split;
2827 }
2828
2829 if (angle2 != 2 * G_PI && angle2 != 0) {
2830 angle2 = angle2 - split;
2831 }
2832
2833 if (used > 0) {
2834 if (free != 0) {
2835 cairo_move_to (cr,xc,yc);
2836 }
2837
2838 cairo_arc (cr, xc, yc, radius, angle1, angle2);
2839
2840 if (free != 0) {
2841 cairo_line_to (cr,xc,yc);
2842 }
2843
2844 gdk_cairo_set_source_rgba (cr, &window->details->used_color);
2845 cairo_fill_preserve (cr);
2846
2847 gdk_cairo_set_source_rgba (cr, &window->details->used_stroke_color);
2848
2849 cairo_stroke (cr);
2850 }
2851
2852 if (free > 0) {
2853 if (used != 0) {
2854 cairo_move_to (cr,xc,yc);
2855 }
2856
2857 cairo_arc_negative (cr, xc, yc, radius, angle1, angle2);
2858
2859 if (used != 0) {
2860 cairo_line_to (cr,xc,yc);
2861 }
2862
2863
2864 gdk_cairo_set_source_rgba (cr, &window->details->free_color);
2865 cairo_fill_preserve(cr);
2866
2867 gdk_cairo_set_source_rgba (cr, &window->details->free_stroke_color);
2868
2869 cairo_stroke (cr);
2870 }
2871 }
2872
2873
2874 /* Copied from gtk/gtkstyle.c */
2875
2876 static void
rgb_to_hls(gdouble * r,gdouble * g,gdouble * b)2877 rgb_to_hls (gdouble *r,
2878 gdouble *g,
2879 gdouble *b)
2880 {
2881 gdouble min;
2882 gdouble max;
2883 gdouble red;
2884 gdouble green;
2885 gdouble blue;
2886 gdouble h, l, s;
2887 gdouble delta;
2888
2889 red = *r;
2890 green = *g;
2891 blue = *b;
2892
2893 if (red > green)
2894 {
2895 if (red > blue)
2896 max = red;
2897 else
2898 max = blue;
2899
2900 if (green < blue)
2901 min = green;
2902 else
2903 min = blue;
2904 }
2905 else
2906 {
2907 if (green > blue)
2908 max = green;
2909 else
2910 max = blue;
2911
2912 if (red < blue)
2913 min = red;
2914 else
2915 min = blue;
2916 }
2917
2918 l = (max + min) / 2;
2919 s = 0;
2920 h = 0;
2921
2922 if (max != min)
2923 {
2924 if (l <= 0.5)
2925 s = (max - min) / (max + min);
2926 else
2927 s = (max - min) / (2 - max - min);
2928
2929 delta = max -min;
2930 if (red == max)
2931 h = (green - blue) / delta;
2932 else if (green == max)
2933 h = 2 + (blue - red) / delta;
2934 else if (blue == max)
2935 h = 4 + (red - green) / delta;
2936
2937 h *= 60;
2938 if (h < 0.0)
2939 h += 360;
2940 }
2941
2942 *r = h;
2943 *g = l;
2944 *b = s;
2945 }
2946
2947 static void
hls_to_rgb(gdouble * h,gdouble * l,gdouble * s)2948 hls_to_rgb (gdouble *h,
2949 gdouble *l,
2950 gdouble *s)
2951 {
2952 gdouble hue;
2953 gdouble lightness;
2954 gdouble saturation;
2955 gdouble m1, m2;
2956 gdouble r, g, b;
2957
2958 lightness = *l;
2959 saturation = *s;
2960
2961 if (lightness <= 0.5)
2962 m2 = lightness * (1 + saturation);
2963 else
2964 m2 = lightness + saturation - lightness * saturation;
2965 m1 = 2 * lightness - m2;
2966
2967 if (saturation == 0)
2968 {
2969 *h = lightness;
2970 *l = lightness;
2971 *s = lightness;
2972 }
2973 else
2974 {
2975 hue = *h + 120;
2976 while (hue > 360)
2977 hue -= 360;
2978 while (hue < 0)
2979 hue += 360;
2980
2981 if (hue < 60)
2982 r = m1 + (m2 - m1) * hue / 60;
2983 else if (hue < 180)
2984 r = m2;
2985 else if (hue < 240)
2986 r = m1 + (m2 - m1) * (240 - hue) / 60;
2987 else
2988 r = m1;
2989
2990 hue = *h;
2991 while (hue > 360)
2992 hue -= 360;
2993 while (hue < 0)
2994 hue += 360;
2995
2996 if (hue < 60)
2997 g = m1 + (m2 - m1) * hue / 60;
2998 else if (hue < 180)
2999 g = m2;
3000 else if (hue < 240)
3001 g = m1 + (m2 - m1) * (240 - hue) / 60;
3002 else
3003 g = m1;
3004
3005 hue = *h - 120;
3006 while (hue > 360)
3007 hue -= 360;
3008 while (hue < 0)
3009 hue += 360;
3010
3011 if (hue < 60)
3012 b = m1 + (m2 - m1) * hue / 60;
3013 else if (hue < 180)
3014 b = m2;
3015 else if (hue < 240)
3016 b = m1 + (m2 - m1) * (240 - hue) / 60;
3017 else
3018 b = m1;
3019
3020 *h = r;
3021 *l = g;
3022 *s = b;
3023 }
3024 }
3025 static void
_pie_style_shade(GdkRGBA * a,GdkRGBA * b,gdouble k)3026 _pie_style_shade (GdkRGBA *a,
3027 GdkRGBA *b,
3028 gdouble k)
3029 {
3030 gdouble red;
3031 gdouble green;
3032 gdouble blue;
3033
3034 red = a->red;
3035 green = a->green;
3036 blue = a->blue;
3037
3038 rgb_to_hls (&red, &green, &blue);
3039
3040 green *= k;
3041 if (green > 1.0)
3042 green = 1.0;
3043 else if (green < 0.0)
3044 green = 0.0;
3045
3046 blue *= k;
3047 if (blue > 1.0)
3048 blue = 1.0;
3049 else if (blue < 0.0)
3050 blue = 0.0;
3051
3052 hls_to_rgb (&red, &green, &blue);
3053
3054 b->red = red;
3055 b->green = green;
3056 b->blue = blue;
3057 b->alpha = a->alpha;
3058 }
3059
3060
3061 static GtkWidget*
create_pie_widget(FMPropertiesWindow * window)3062 create_pie_widget (FMPropertiesWindow *window)
3063 {
3064 CajaFile *file;
3065 GtkGrid *grid;
3066 GtkStyleContext *style;
3067
3068 GtkWidget *pie_canvas;
3069 GtkWidget *used_canvas;
3070 GtkWidget *used_label;
3071 GtkWidget *free_canvas;
3072 GtkWidget *free_label;
3073 GtkWidget *capacity_label;
3074 GtkWidget *fstype_label;
3075 gchar *capacity;
3076 gchar *used;
3077 gchar *free;
3078 gchar *uri;
3079 gchar *concat;
3080 GFile *location;
3081 GFileInfo *info;
3082
3083 if (g_settings_get_boolean (caja_preferences, CAJA_PREFERENCES_USE_IEC_UNITS)) {
3084 capacity = g_format_size_full(window->details->volume_capacity, G_FORMAT_SIZE_IEC_UNITS);
3085 free = g_format_size_full(window->details->volume_free, G_FORMAT_SIZE_IEC_UNITS);
3086 used = g_format_size_full(window->details->volume_capacity - window->details->volume_free, G_FORMAT_SIZE_IEC_UNITS);
3087 }
3088 else {
3089 capacity = g_format_size(window->details->volume_capacity);
3090 free = g_format_size(window->details->volume_free);
3091 used = g_format_size(window->details->volume_capacity - window->details->volume_free);
3092 }
3093
3094 file = get_original_file (window);
3095
3096 uri = caja_file_get_activation_uri (file);
3097
3098 grid = GTK_GRID (gtk_grid_new ());
3099 gtk_container_set_border_width (GTK_CONTAINER (grid), 5);
3100 gtk_grid_set_column_spacing (GTK_GRID (grid), 5);
3101 style = gtk_widget_get_style_context (GTK_WIDGET (grid));
3102
3103 if (!gtk_style_context_lookup_color (style, "chart_rgba_1", &window->details->used_color)) {
3104
3105 window->details->used_color.red = USED_FILL_R;
3106 window->details->used_color.green = USED_FILL_G;
3107 window->details->used_color.blue = USED_FILL_B;
3108 window->details->used_color.alpha = 1;
3109
3110 }
3111
3112
3113 if (!gtk_style_context_lookup_color (style, "chart_rgba_2", &window->details->free_color)) {
3114 window->details->free_color.red = FREE_FILL_R;
3115 window->details->free_color.green = FREE_FILL_G;
3116 window->details->free_color.blue = FREE_FILL_B;
3117 window->details->free_color.alpha = 1;
3118
3119 }
3120
3121 _pie_style_shade (&window->details->used_color, &window->details->used_stroke_color, 0.7);
3122 _pie_style_shade (&window->details->free_color, &window->details->free_stroke_color, 0.7);
3123
3124 pie_canvas = gtk_drawing_area_new ();
3125 gtk_widget_set_size_request (pie_canvas, 200, 200);
3126
3127 used_canvas = gtk_drawing_area_new ();
3128
3129 gtk_widget_set_valign (used_canvas, GTK_ALIGN_CENTER);
3130 gtk_widget_set_halign (used_canvas, GTK_ALIGN_CENTER);
3131
3132 gtk_widget_set_size_request (used_canvas, 20, 20);
3133 /* Translators: "used" refers to the capacity of the filesystem */
3134 concat = g_strconcat (used, " ", _("used"), NULL);
3135 used_label = gtk_label_new (concat);
3136 g_free (concat);
3137
3138 free_canvas = gtk_drawing_area_new ();
3139
3140 gtk_widget_set_valign (free_canvas, GTK_ALIGN_CENTER);
3141 gtk_widget_set_halign (free_canvas, GTK_ALIGN_CENTER);
3142
3143 gtk_widget_set_size_request (free_canvas, 20, 20);
3144 /* Translators: "free" refers to the capacity of the filesystem */
3145 concat = g_strconcat (free, " ", _("free"), NULL);
3146 free_label = gtk_label_new (concat);
3147 g_free (concat);
3148
3149 concat = g_strconcat (_("Total capacity:"), " ", capacity, NULL);
3150 capacity_label = gtk_label_new (concat);
3151 g_free (concat);
3152 fstype_label = gtk_label_new (NULL);
3153
3154 location = g_file_new_for_uri (uri);
3155 info = g_file_query_filesystem_info (location, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE,
3156 NULL, NULL);
3157 if (info) {
3158 const char *fs_type;
3159
3160 fs_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE);
3161
3162 if (fs_type != NULL) {
3163 concat = g_strconcat (_("Filesystem type:"), " ", fs_type, NULL);
3164 gtk_label_set_text (GTK_LABEL (fstype_label), concat);
3165 g_free (concat);
3166 }
3167
3168 g_object_unref (info);
3169 }
3170 g_object_unref (location);
3171
3172 g_free (uri);
3173 g_free (capacity);
3174 g_free (used);
3175 g_free (free);
3176
3177 gtk_container_add_with_properties (GTK_CONTAINER (grid), pie_canvas,
3178 "height", 4,
3179 NULL);
3180 gtk_grid_attach_next_to (grid, used_canvas, pie_canvas,
3181 GTK_POS_RIGHT, 1, 1);
3182 gtk_grid_attach_next_to (grid, used_label, used_canvas,
3183 GTK_POS_RIGHT, 1, 1);
3184
3185 gtk_grid_attach_next_to (grid, free_canvas, used_canvas,
3186 GTK_POS_BOTTOM, 1, 1);
3187 gtk_grid_attach_next_to (grid, free_label, free_canvas,
3188 GTK_POS_RIGHT, 1, 1);
3189
3190 gtk_grid_attach_next_to (grid, capacity_label, free_canvas,
3191 GTK_POS_BOTTOM, 2, 1);
3192 gtk_grid_attach_next_to (grid, fstype_label, capacity_label,
3193 GTK_POS_BOTTOM, 2, 1);
3194
3195 g_signal_connect (pie_canvas, "draw",
3196 G_CALLBACK (paint_pie_chart), window);
3197 g_signal_connect (used_canvas, "draw",
3198 G_CALLBACK (paint_used_legend), window);
3199 g_signal_connect (free_canvas, "draw",
3200 G_CALLBACK (paint_free_legend), window);
3201
3202 return GTK_WIDGET (grid);
3203
3204 }
3205
3206 static GtkWidget*
create_volume_usage_widget(FMPropertiesWindow * window)3207 create_volume_usage_widget (FMPropertiesWindow *window)
3208 {
3209 GtkWidget *piewidget;
3210 gchar *uri;
3211 CajaFile *file;
3212 GFile *location;
3213 GFileInfo *info;
3214
3215 file = get_original_file (window);
3216
3217 uri = caja_file_get_activation_uri (file);
3218
3219 location = g_file_new_for_uri (uri);
3220 info = g_file_query_filesystem_info (location, "filesystem::*", NULL, NULL);
3221
3222 if (info) {
3223 window->details->volume_capacity = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
3224 window->details->volume_free = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
3225
3226 g_object_unref (info);
3227 } else {
3228 window->details->volume_capacity = 0;
3229 window->details->volume_free = 0;
3230 }
3231
3232 g_object_unref (location);
3233
3234 piewidget = create_pie_widget (window);
3235
3236 gtk_widget_show_all (piewidget);
3237
3238 return piewidget;
3239 }
3240
3241 static void
create_basic_page(FMPropertiesWindow * window)3242 create_basic_page (FMPropertiesWindow *window)
3243 {
3244 GtkGrid *grid;
3245 GtkWidget *icon_pixmap_widget;
3246 GtkWidget *hbox, *vbox;
3247
3248 hbox = create_page_with_hbox (window->details->notebook, _("Basic"));
3249
3250 /* Icon pixmap */
3251
3252 icon_pixmap_widget = create_image_widget (
3253 window, should_show_custom_icon_buttons (window));
3254
3255 gtk_widget_set_halign (icon_pixmap_widget, GTK_ALIGN_END);
3256 gtk_widget_set_valign (icon_pixmap_widget, GTK_ALIGN_START);
3257 gtk_widget_show (icon_pixmap_widget);
3258
3259 gtk_box_pack_start (GTK_BOX (hbox), icon_pixmap_widget, FALSE, FALSE, 0);
3260
3261 window->details->icon_chooser = NULL;
3262
3263 vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
3264
3265 gtk_widget_show (vbox);
3266 gtk_container_add (GTK_CONTAINER (hbox), vbox);
3267
3268 grid = GTK_GRID (create_grid_with_standard_properties ());
3269 gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (grid), FALSE, FALSE, 0);
3270 window->details->basic_grid = grid;
3271
3272 /* Name label. The text will be determined in update_name_field */
3273 window->details->name_label = attach_title_field (grid, NULL);
3274
3275 /* Name field */
3276 window->details->name_field = NULL;
3277 update_name_field (window);
3278
3279 /* Start with name field selected, if it's an entry. */
3280 if (CAJA_IS_ENTRY (window->details->name_field)) {
3281 caja_entry_select_all (CAJA_ENTRY (window->details->name_field));
3282 gtk_widget_grab_focus (GTK_WIDGET (window->details->name_field));
3283 }
3284
3285 if (fm_ditem_page_should_show (window->details->target_files)) {
3286 GtkSizeGroup *label_size_group;
3287 GtkWidget *box;
3288
3289 label_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
3290 gtk_size_group_add_widget (label_size_group,
3291 GTK_WIDGET (window->details->name_label));
3292 box = fm_ditem_page_make_box (label_size_group,
3293 window->details->target_files);
3294
3295 gtk_grid_attach_next_to (window->details->basic_grid, box,
3296 GTK_WIDGET (window->details->name_label),
3297 GTK_POS_BOTTOM, 2, 1);
3298 }
3299
3300 if (should_show_file_type (window)) {
3301 append_title_value_pair (window,
3302 grid, _("Type:"),
3303 "type",
3304 INCONSISTENT_STATE_STRING,
3305 FALSE);
3306 }
3307
3308 if (should_show_link_target (window)) {
3309 append_title_and_ellipsizing_value (window, grid,
3310 _("Link target:"),
3311 "link_target",
3312 INCONSISTENT_STATE_STRING,
3313 FALSE);
3314 }
3315
3316 if (is_multi_file_window (window) ||
3317 caja_file_is_directory (get_target_file (window))) {
3318 append_directory_contents_fields (window, grid);
3319 } else {
3320 append_title_value_pair (window, grid, _("Size:"),
3321 "size_detail",
3322 INCONSISTENT_STATE_STRING,
3323 FALSE);
3324 append_title_value_pair (window, grid, _("Size on Disk:"),
3325 "size_on_disk_detail",
3326 INCONSISTENT_STATE_STRING,
3327 FALSE);
3328 }
3329
3330 append_blank_row (grid);
3331
3332 if (should_show_location_info (window)) {
3333 append_title_and_ellipsizing_value (window, grid, _("Location:"),
3334 "where",
3335 INCONSISTENT_STATE_STRING,
3336 TRUE);
3337
3338 append_title_and_ellipsizing_value (window, grid,
3339 _("Volume:"),
3340 "volume",
3341 INCONSISTENT_STATE_STRING,
3342 FALSE);
3343 }
3344
3345 if (should_show_accessed_date (window)
3346 || should_show_modified_date (window))
3347 {
3348 append_blank_row (grid);
3349 }
3350
3351 if (should_show_accessed_date (window))
3352 {
3353 append_title_value_pair (window, grid, _("Accessed:"),
3354 "date_accessed",
3355 INCONSISTENT_STATE_STRING,
3356 FALSE);
3357 }
3358
3359 if (should_show_modified_date (window))
3360 {
3361 append_title_value_pair (window, grid, _("Modified:"),
3362 "date_modified",
3363 INCONSISTENT_STATE_STRING,
3364 FALSE);
3365 append_title_value_pair (window, grid, _("Created:"),
3366 "date_created",
3367 INCONSISTENT_STATE_STRING,
3368 FALSE);
3369 }
3370
3371 if (should_show_free_space (window)) {
3372 append_blank_row (grid);
3373
3374 append_title_value_pair (window, grid, _("Free space:"),
3375 "free_space",
3376 INCONSISTENT_STATE_STRING,
3377 FALSE);
3378 }
3379
3380 if (should_show_volume_usage (window)) {
3381 GtkWidget *volume_usage;
3382
3383 volume_usage = create_volume_usage_widget (window);
3384 gtk_container_add_with_properties (GTK_CONTAINER (grid), volume_usage,
3385 "width", 2,
3386 NULL);
3387 }
3388 }
3389
3390 static GHashTable *
get_initial_emblems(GList * files)3391 get_initial_emblems (GList *files)
3392 {
3393 GHashTable *ret;
3394 GList *l;
3395
3396 ret = g_hash_table_new_full (g_direct_hash,
3397 g_direct_equal,
3398 NULL,
3399 (GDestroyNotify) eel_g_list_free_deep);
3400
3401 for (l = files; l != NULL; l = l->next) {
3402 CajaFile *file;
3403 GList *keywords;
3404
3405 file = CAJA_FILE (l->data);
3406
3407 keywords = caja_file_get_keywords (file);
3408 g_hash_table_insert (ret, file, keywords);
3409 }
3410
3411 return ret;
3412 }
3413
3414 static gboolean
files_has_directory(FMPropertiesWindow * window)3415 files_has_directory (FMPropertiesWindow *window)
3416 {
3417 GList *l;
3418
3419 for (l = window->details->target_files; l != NULL; l = l->next) {
3420 CajaFile *file;
3421 file = CAJA_FILE (l->data);
3422 if (caja_file_is_directory (file)) {
3423 return TRUE;
3424 }
3425
3426 }
3427
3428 return FALSE;
3429 }
3430
3431 static gboolean
files_has_changable_permissions_directory(FMPropertiesWindow * window)3432 files_has_changable_permissions_directory (FMPropertiesWindow *window)
3433 {
3434 GList *l;
3435
3436 for (l = window->details->target_files; l != NULL; l = l->next) {
3437 CajaFile *file;
3438 file = CAJA_FILE (l->data);
3439 if (caja_file_is_directory (file) &&
3440 caja_file_can_get_permissions (file) &&
3441 caja_file_can_set_permissions (file)) {
3442 return TRUE;
3443 }
3444
3445 }
3446
3447 return FALSE;
3448 }
3449
3450
3451 static gboolean
files_has_file(FMPropertiesWindow * window)3452 files_has_file (FMPropertiesWindow *window)
3453 {
3454 GList *l;
3455
3456 for (l = window->details->target_files; l != NULL; l = l->next) {
3457 CajaFile *file;
3458 file = CAJA_FILE (l->data);
3459 if (!caja_file_is_directory (file)) {
3460 return TRUE;
3461 }
3462
3463 }
3464
3465 return FALSE;
3466 }
3467
3468
3469 static void
create_emblems_page(FMPropertiesWindow * window)3470 create_emblems_page (FMPropertiesWindow *window)
3471 {
3472 GtkWidget *emblems_table, *button, *scroller;
3473 GdkPixbuf *pixbuf;
3474 char *label;
3475 GList *icons, *l;
3476 CajaIconInfo *info;
3477 gint scale;
3478
3479 /* The emblems wrapped table */
3480 scroller = eel_scrolled_wrap_table_new (TRUE, GTK_SHADOW_NONE, &emblems_table);
3481
3482 gtk_container_set_border_width (GTK_CONTAINER (emblems_table), 12);
3483
3484 /* stop GTK 3.22 builds from ballooning the properties dialog to full screen height */
3485 gtk_scrolled_window_set_max_content_height (GTK_SCROLLED_WINDOW (scroller), 300);
3486
3487 gtk_widget_show (scroller);
3488
3489 gtk_notebook_append_page (window->details->notebook,
3490 scroller, gtk_label_new (_("Emblems")));
3491
3492 icons = caja_emblem_list_available ();
3493 scale = gtk_widget_get_scale_factor (scroller);
3494
3495 window->details->initial_emblems = get_initial_emblems (window->details->original_files);
3496
3497 l = icons;
3498 while (l != NULL) {
3499 char *emblem_name;
3500
3501 emblem_name = l->data;
3502 l = l->next;
3503
3504 if (!caja_emblem_should_show_in_list (emblem_name)) {
3505 continue;
3506 }
3507
3508 info = caja_icon_info_lookup_from_name (emblem_name, CAJA_ICON_SIZE_SMALL, scale);
3509 pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, CAJA_ICON_SIZE_SMALL);
3510
3511 if (pixbuf == NULL) {
3512 continue;
3513 }
3514
3515 label = g_strdup (caja_icon_info_get_display_name (info));
3516 g_object_unref (info);
3517
3518 if (label == NULL) {
3519 label = caja_emblem_get_keyword_from_icon_name (emblem_name);
3520 }
3521
3522 button = eel_labeled_image_check_button_new (label, pixbuf);
3523 eel_labeled_image_set_fixed_image_height (EEL_LABELED_IMAGE (gtk_bin_get_child (GTK_BIN (button))), STANDARD_EMBLEM_HEIGHT * scale);
3524 eel_labeled_image_set_spacing (EEL_LABELED_IMAGE (gtk_bin_get_child (GTK_BIN (button))), EMBLEM_LABEL_SPACING * scale);
3525
3526 g_free (label);
3527 g_object_unref (pixbuf);
3528
3529 /* Attach parameters and signal handler. */
3530 g_object_set_data_full (G_OBJECT (button), "caja_emblem_name",
3531 caja_emblem_get_keyword_from_icon_name (emblem_name), g_free);
3532
3533 window->details->emblem_buttons =
3534 g_list_append (window->details->emblem_buttons,
3535 button);
3536
3537 g_signal_connect_object (button, "toggled",
3538 G_CALLBACK (emblem_button_toggled),
3539 G_OBJECT (window),
3540 0);
3541
3542 gtk_container_add (GTK_CONTAINER (emblems_table), button);
3543 }
3544 g_list_free_full (icons, g_free);
3545 gtk_widget_show_all (emblems_table);
3546 }
3547
3548 static void
start_long_operation(FMPropertiesWindow * window)3549 start_long_operation (FMPropertiesWindow *window)
3550 {
3551 if (window->details->long_operation_underway == 0) {
3552 /* start long operation */
3553 GdkDisplay *display;
3554 GdkCursor * cursor;
3555
3556 display = gtk_widget_get_display (GTK_WIDGET (window));
3557 cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
3558 gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), cursor);
3559 g_object_unref (cursor);
3560 }
3561 window->details->long_operation_underway ++;
3562 }
3563
3564 static void
end_long_operation(FMPropertiesWindow * window)3565 end_long_operation (FMPropertiesWindow *window)
3566 {
3567 if (gtk_widget_get_window (GTK_WIDGET (window)) != NULL &&
3568 window->details->long_operation_underway == 1) {
3569 /* finished !! */
3570 gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), NULL);
3571 }
3572 window->details->long_operation_underway--;
3573 }
3574
3575 static void
permission_change_callback(CajaFile * file,GFile * res_loc,GError * error,gpointer callback_data)3576 permission_change_callback (CajaFile *file,
3577 GFile *res_loc,
3578 GError *error,
3579 gpointer callback_data)
3580 {
3581 FMPropertiesWindow *window;
3582 g_assert (callback_data != NULL);
3583
3584 window = FM_PROPERTIES_WINDOW (callback_data);
3585 end_long_operation (window);
3586
3587 /* Report the error if it's an error. */
3588 fm_report_error_setting_permissions (file, error, NULL);
3589
3590 g_object_unref (window);
3591 }
3592
3593 static void
update_permissions(FMPropertiesWindow * window,guint32 vfs_new_perm,guint32 vfs_mask,gboolean is_folder,gboolean apply_to_both_folder_and_dir,gboolean use_original)3594 update_permissions (FMPropertiesWindow *window,
3595 guint32 vfs_new_perm,
3596 guint32 vfs_mask,
3597 gboolean is_folder,
3598 gboolean apply_to_both_folder_and_dir,
3599 gboolean use_original)
3600 {
3601 GList *l;
3602
3603 for (l = window->details->target_files; l != NULL; l = l->next) {
3604 CajaFile *file;
3605 guint32 permissions;
3606
3607 file = CAJA_FILE (l->data);
3608
3609 if (!caja_file_can_get_permissions (file)) {
3610 continue;
3611 }
3612
3613 if (!apply_to_both_folder_and_dir &&
3614 ((caja_file_is_directory (file) && !is_folder) ||
3615 (!caja_file_is_directory (file) && is_folder))) {
3616 continue;
3617 }
3618
3619 permissions = caja_file_get_permissions (file);
3620 if (use_original) {
3621 gpointer ptr;
3622 if (g_hash_table_lookup_extended (window->details->initial_permissions,
3623 file, NULL, &ptr)) {
3624 permissions = (permissions & ~vfs_mask) | (GPOINTER_TO_INT (ptr) & vfs_mask);
3625 }
3626 } else {
3627 permissions = (permissions & ~vfs_mask) | vfs_new_perm;
3628 }
3629
3630 start_long_operation (window);
3631 g_object_ref (window);
3632 caja_file_set_permissions
3633 (file, permissions,
3634 permission_change_callback,
3635 window);
3636 }
3637 }
3638
3639 static gboolean
initial_permission_state_consistent(FMPropertiesWindow * window,guint32 mask,gboolean is_folder,gboolean both_folder_and_dir)3640 initial_permission_state_consistent (FMPropertiesWindow *window,
3641 guint32 mask,
3642 gboolean is_folder,
3643 gboolean both_folder_and_dir)
3644 {
3645 GList *l;
3646 gboolean first;
3647 guint32 first_permissions;
3648
3649 first = TRUE;
3650 first_permissions = 0;
3651 for (l = window->details->target_files; l != NULL; l = l->next) {
3652 CajaFile *file;
3653 guint32 permissions;
3654
3655 file = l->data;
3656
3657 if (!both_folder_and_dir &&
3658 ((caja_file_is_directory (file) && !is_folder) ||
3659 (!caja_file_is_directory (file) && is_folder))) {
3660 continue;
3661 }
3662
3663 permissions = GPOINTER_TO_INT (g_hash_table_lookup (window->details->initial_permissions,
3664 file));
3665
3666 if (first) {
3667 if ((permissions & mask) != mask &&
3668 (permissions & mask) != 0) {
3669 /* Not fully on or off -> inconsistent */
3670 return FALSE;
3671 }
3672
3673 first_permissions = permissions;
3674 first = FALSE;
3675
3676 } else if ((permissions & mask) != (first_permissions & mask)) {
3677 /* Not same permissions as first -> inconsistent */
3678 return FALSE;
3679 }
3680 }
3681 return TRUE;
3682 }
3683
3684 static void
permission_button_toggled(GtkToggleButton * button,FMPropertiesWindow * window)3685 permission_button_toggled (GtkToggleButton *button,
3686 FMPropertiesWindow *window)
3687 {
3688 gboolean is_folder, is_special;
3689 guint32 permission_mask;
3690 gboolean inconsistent;
3691 gboolean on;
3692
3693 permission_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
3694 "permission"));
3695 is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
3696 "is-folder"));
3697 is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
3698 "is-special"));
3699
3700 if (gtk_toggle_button_get_active (button)
3701 && !gtk_toggle_button_get_inconsistent (button)) {
3702 /* Go to the initial state unless the initial state was
3703 consistent, or we support recursive apply */
3704 inconsistent = TRUE;
3705 on = TRUE;
3706
3707 if (!window->details->has_recursive_apply &&
3708 initial_permission_state_consistent (window, permission_mask, is_folder, is_special)) {
3709 inconsistent = FALSE;
3710 on = TRUE;
3711 }
3712 } else if (gtk_toggle_button_get_inconsistent (button)
3713 && !gtk_toggle_button_get_active (button)) {
3714 inconsistent = FALSE;
3715 on = TRUE;
3716 } else {
3717 inconsistent = FALSE;
3718 on = FALSE;
3719 }
3720
3721 g_signal_handlers_block_by_func (G_OBJECT (button),
3722 G_CALLBACK (permission_button_toggled),
3723 window);
3724
3725 gtk_toggle_button_set_active (button, on);
3726 gtk_toggle_button_set_inconsistent (button, inconsistent);
3727
3728 g_signal_handlers_unblock_by_func (G_OBJECT (button),
3729 G_CALLBACK (permission_button_toggled),
3730 window);
3731
3732 update_permissions (window,
3733 on?permission_mask:0,
3734 permission_mask,
3735 is_folder,
3736 is_special,
3737 inconsistent);
3738 }
3739
3740 static void
permission_button_update(FMPropertiesWindow * window,GtkToggleButton * button)3741 permission_button_update (FMPropertiesWindow *window,
3742 GtkToggleButton *button)
3743 {
3744 GList *l;
3745 gboolean all_set;
3746 gboolean all_unset;
3747 gboolean all_cannot_set;
3748 gboolean is_folder, is_special;
3749 gboolean no_match;
3750 gboolean sensitive;
3751 guint32 button_permission;
3752
3753 if (gtk_toggle_button_get_inconsistent (button) &&
3754 window->details->has_recursive_apply) {
3755 /* Never change from an inconsistent state if we have dirs, even
3756 * if the current state is now consistent, because its a useful
3757 * state for recursive apply.
3758 */
3759 return;
3760 }
3761
3762 button_permission = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
3763 "permission"));
3764 is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
3765 "is-folder"));
3766 is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
3767 "is-special"));
3768
3769 all_set = TRUE;
3770 all_unset = TRUE;
3771 all_cannot_set = TRUE;
3772 no_match = TRUE;
3773 for (l = window->details->target_files; l != NULL; l = l->next) {
3774 CajaFile *file;
3775 guint32 file_permissions;
3776
3777 file = CAJA_FILE (l->data);
3778
3779 if (!caja_file_can_get_permissions (file)) {
3780 continue;
3781 }
3782
3783 if (!is_special &&
3784 ((caja_file_is_directory (file) && !is_folder) ||
3785 (!caja_file_is_directory (file) && is_folder))) {
3786 continue;
3787 }
3788
3789 no_match = FALSE;
3790
3791 file_permissions = caja_file_get_permissions (file);
3792
3793 if ((file_permissions & button_permission) == button_permission) {
3794 all_unset = FALSE;
3795 } else if ((file_permissions & button_permission) == 0) {
3796 all_set = FALSE;
3797 } else {
3798 all_unset = FALSE;
3799 all_set = FALSE;
3800 }
3801
3802 if (caja_file_can_set_permissions (file)) {
3803 all_cannot_set = FALSE;
3804 }
3805 }
3806
3807 sensitive = !all_cannot_set;
3808 if (!is_folder) {
3809 /* Don't insitive files when we have recursive apply */
3810 sensitive |= window->details->has_recursive_apply;
3811 }
3812
3813
3814 g_signal_handlers_block_by_func (G_OBJECT (button),
3815 G_CALLBACK (permission_button_toggled),
3816 window);
3817
3818 gtk_toggle_button_set_active (button, !all_unset);
3819 /* if actually inconsistent, or default value for file buttons
3820 if no files are selected. (useful for recursive apply) */
3821 gtk_toggle_button_set_inconsistent (button,
3822 (!all_unset && !all_set) ||
3823 (!is_folder && no_match));
3824 gtk_widget_set_sensitive (GTK_WIDGET (button), sensitive);
3825
3826 g_signal_handlers_unblock_by_func (G_OBJECT (button),
3827 G_CALLBACK (permission_button_toggled),
3828 window);
3829 }
3830
3831 static void
set_up_permissions_checkbox(FMPropertiesWindow * window,GtkWidget * check_button,guint32 permission,gboolean is_folder)3832 set_up_permissions_checkbox (FMPropertiesWindow *window,
3833 GtkWidget *check_button,
3834 guint32 permission,
3835 gboolean is_folder)
3836 {
3837 /* Load up the check_button with data we'll need when updating its state. */
3838 g_object_set_data (G_OBJECT (check_button), "permission",
3839 GINT_TO_POINTER (permission));
3840 g_object_set_data (G_OBJECT (check_button), "properties_window",
3841 window);
3842 g_object_set_data (G_OBJECT (check_button), "is-folder",
3843 GINT_TO_POINTER (is_folder));
3844
3845 window->details->permission_buttons =
3846 g_list_prepend (window->details->permission_buttons,
3847 check_button);
3848
3849 g_signal_connect_object (check_button, "toggled",
3850 G_CALLBACK (permission_button_toggled),
3851 window,
3852 0);
3853 }
3854
3855 static GtkWidget *
add_permissions_checkbox_with_label(FMPropertiesWindow * window,GtkGrid * grid,GtkWidget * sibling,const char * label,guint32 permission_to_check,GtkLabel * label_for,gboolean is_folder)3856 add_permissions_checkbox_with_label (FMPropertiesWindow *window,
3857 GtkGrid *grid,
3858 GtkWidget *sibling,
3859 const char *label,
3860 guint32 permission_to_check,
3861 GtkLabel *label_for,
3862 gboolean is_folder)
3863 {
3864 GtkWidget *check_button;
3865 gboolean a11y_enabled;
3866
3867 check_button = gtk_check_button_new_with_mnemonic (label);
3868 gtk_widget_show (check_button);
3869 if (sibling) {
3870 gtk_grid_attach_next_to (grid, check_button, sibling,
3871 GTK_POS_RIGHT, 1, 1);
3872 } else {
3873 gtk_container_add (GTK_CONTAINER (grid), check_button);
3874 }
3875
3876 set_up_permissions_checkbox (window,
3877 check_button,
3878 permission_to_check,
3879 is_folder);
3880
3881 a11y_enabled = GTK_IS_ACCESSIBLE (gtk_widget_get_accessible (check_button));
3882 if (a11y_enabled && label_for != NULL) {
3883 eel_accessibility_set_up_label_widget_relation (GTK_WIDGET (label_for),
3884 check_button);
3885 }
3886
3887 return check_button;
3888 }
3889
3890 static GtkWidget *
add_permissions_checkbox(FMPropertiesWindow * window,GtkGrid * grid,GtkWidget * sibling,CheckboxType type,guint32 permission_to_check,GtkLabel * label_for,gboolean is_folder)3891 add_permissions_checkbox (FMPropertiesWindow *window,
3892 GtkGrid *grid,
3893 GtkWidget *sibling,
3894 CheckboxType type,
3895 guint32 permission_to_check,
3896 GtkLabel *label_for,
3897 gboolean is_folder)
3898 {
3899 const gchar *label;
3900
3901 if (type == PERMISSIONS_CHECKBOXES_READ) {
3902 label = _("_Read");
3903 } else if (type == PERMISSIONS_CHECKBOXES_WRITE) {
3904 label = _("_Write");
3905 } else {
3906 label = _("E_xecute");
3907 }
3908
3909 return add_permissions_checkbox_with_label (window, grid,
3910 sibling,
3911 label,
3912 permission_to_check,
3913 label_for,
3914 is_folder);
3915 }
3916
3917 enum {
3918 UNIX_PERM_SUID = S_ISUID,
3919 UNIX_PERM_SGID = S_ISGID,
3920 UNIX_PERM_STICKY = 01000, /* S_ISVTX not defined on all systems */
3921 UNIX_PERM_USER_READ = S_IRUSR,
3922 UNIX_PERM_USER_WRITE = S_IWUSR,
3923 UNIX_PERM_USER_EXEC = S_IXUSR,
3924 UNIX_PERM_USER_ALL = S_IRUSR | S_IWUSR | S_IXUSR,
3925 UNIX_PERM_GROUP_READ = S_IRGRP,
3926 UNIX_PERM_GROUP_WRITE = S_IWGRP,
3927 UNIX_PERM_GROUP_EXEC = S_IXGRP,
3928 UNIX_PERM_GROUP_ALL = S_IRGRP | S_IWGRP | S_IXGRP,
3929 UNIX_PERM_OTHER_READ = S_IROTH,
3930 UNIX_PERM_OTHER_WRITE = S_IWOTH,
3931 UNIX_PERM_OTHER_EXEC = S_IXOTH,
3932 UNIX_PERM_OTHER_ALL = S_IROTH | S_IWOTH | S_IXOTH
3933 };
3934
3935 typedef enum {
3936 PERMISSION_READ = (1<<0),
3937 PERMISSION_WRITE = (1<<1),
3938 PERMISSION_EXEC = (1<<2)
3939 } PermissionValue;
3940
3941 typedef enum {
3942 PERMISSION_USER,
3943 PERMISSION_GROUP,
3944 PERMISSION_OTHER
3945 } PermissionType;
3946
3947 static guint32 vfs_perms[3][3] = {
3948 {UNIX_PERM_USER_READ, UNIX_PERM_USER_WRITE, UNIX_PERM_USER_EXEC},
3949 {UNIX_PERM_GROUP_READ, UNIX_PERM_GROUP_WRITE, UNIX_PERM_GROUP_EXEC},
3950 {UNIX_PERM_OTHER_READ, UNIX_PERM_OTHER_WRITE, UNIX_PERM_OTHER_EXEC},
3951 };
3952
3953 static guint32
permission_to_vfs(PermissionType type,PermissionValue perm)3954 permission_to_vfs (PermissionType type, PermissionValue perm)
3955 {
3956 guint32 vfs_perm;
3957 g_assert (type >= 0 && type < 3);
3958
3959 vfs_perm = 0;
3960 if (perm & PERMISSION_READ) {
3961 vfs_perm |= vfs_perms[type][0];
3962 }
3963 if (perm & PERMISSION_WRITE) {
3964 vfs_perm |= vfs_perms[type][1];
3965 }
3966 if (perm & PERMISSION_EXEC) {
3967 vfs_perm |= vfs_perms[type][2];
3968 }
3969
3970 return vfs_perm;
3971 }
3972
3973
3974 static PermissionValue
permission_from_vfs(PermissionType type,guint32 vfs_perm)3975 permission_from_vfs (PermissionType type, guint32 vfs_perm)
3976 {
3977 PermissionValue perm;
3978 g_assert (type >= 0 && type < 3);
3979
3980 perm = 0;
3981 if (vfs_perm & vfs_perms[type][0]) {
3982 perm |= PERMISSION_READ;
3983 }
3984 if (vfs_perm & vfs_perms[type][1]) {
3985 perm |= PERMISSION_WRITE;
3986 }
3987 if (vfs_perm & vfs_perms[type][2]) {
3988 perm |= PERMISSION_EXEC;
3989 }
3990
3991 return perm;
3992 }
3993
3994 static void
permission_combo_changed(GtkWidget * combo,FMPropertiesWindow * window)3995 permission_combo_changed (GtkWidget *combo, FMPropertiesWindow *window)
3996 {
3997 GtkTreeIter iter;
3998 GtkTreeModel *model;
3999 gboolean is_folder, use_original;
4000 PermissionType type;
4001 int new_perm, mask;
4002 guint32 vfs_new_perm, vfs_mask;
4003
4004 is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder"));
4005 type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type"));
4006
4007 if (is_folder) {
4008 mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC;
4009 } else {
4010 mask = PERMISSION_READ|PERMISSION_WRITE;
4011 }
4012
4013 vfs_mask = permission_to_vfs (type, mask);
4014
4015 model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
4016
4017 if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) {
4018 return;
4019 }
4020 gtk_tree_model_get (model, &iter, 1, &new_perm, 2, &use_original, -1);
4021 vfs_new_perm = permission_to_vfs (type, new_perm);
4022
4023 update_permissions (window, vfs_new_perm, vfs_mask,
4024 is_folder, FALSE, use_original);
4025 }
4026
4027 static void
permission_combo_add_multiple_choice(GtkComboBox * combo,GtkTreeIter * iter)4028 permission_combo_add_multiple_choice (GtkComboBox *combo, GtkTreeIter *iter)
4029 {
4030 GtkTreeModel *model;
4031 GtkListStore *store;
4032 gboolean found;
4033
4034 model = gtk_combo_box_get_model (combo);
4035 store = GTK_LIST_STORE (model);
4036
4037 found = FALSE;
4038 gtk_tree_model_get_iter_first (model, iter);
4039 do {
4040 gboolean multi;
4041 gtk_tree_model_get (model, iter, 2, &multi, -1);
4042
4043 if (multi) {
4044 found = TRUE;
4045 break;
4046 }
4047 } while (gtk_tree_model_iter_next (model, iter));
4048
4049 if (!found) {
4050 gtk_list_store_append (store, iter);
4051 gtk_list_store_set (store, iter, 0, "---", 1, 0, 2, TRUE, -1);
4052 }
4053 }
4054
4055 static void
permission_combo_update(FMPropertiesWindow * window,GtkComboBox * combo)4056 permission_combo_update (FMPropertiesWindow *window,
4057 GtkComboBox *combo)
4058 {
4059 PermissionType type;
4060 PermissionValue perm, all_dir_perm, all_file_perm, all_perm;
4061 gboolean is_folder, no_files, no_dirs, all_file_same, all_dir_same, all_same;
4062 gboolean all_dir_cannot_set, all_file_cannot_set, sensitive;
4063 GtkTreeIter iter;
4064 int mask;
4065 GtkTreeModel *model;
4066 GtkListStore *store;
4067 GList *l;
4068 gboolean is_multi;
4069
4070 model = gtk_combo_box_get_model (combo);
4071
4072 is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder"));
4073 type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type"));
4074
4075 is_multi = FALSE;
4076 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) {
4077 gtk_tree_model_get (model, &iter, 2, &is_multi, -1);
4078 }
4079
4080 if (is_multi && window->details->has_recursive_apply) {
4081 /* Never change from an inconsistent state if we have dirs, even
4082 * if the current state is now consistent, because its a useful
4083 * state for recursive apply.
4084 */
4085 return;
4086 }
4087
4088 no_files = TRUE;
4089 no_dirs = TRUE;
4090 all_dir_same = TRUE;
4091 all_file_same = TRUE;
4092 all_dir_perm = 0;
4093 all_file_perm = 0;
4094 all_dir_cannot_set = TRUE;
4095 all_file_cannot_set = TRUE;
4096
4097 for (l = window->details->target_files; l != NULL; l = l->next) {
4098 CajaFile *file;
4099 guint32 file_permissions;
4100
4101 file = CAJA_FILE (l->data);
4102
4103 if (!caja_file_can_get_permissions (file)) {
4104 continue;
4105 }
4106
4107 if (caja_file_is_directory (file)) {
4108 mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC;
4109 } else {
4110 mask = PERMISSION_READ|PERMISSION_WRITE;
4111 }
4112
4113 file_permissions = caja_file_get_permissions (file);
4114
4115 perm = permission_from_vfs (type, file_permissions) & mask;
4116
4117 if (caja_file_is_directory (file)) {
4118 if (no_dirs) {
4119 all_dir_perm = perm;
4120 no_dirs = FALSE;
4121 } else if (perm != all_dir_perm) {
4122 all_dir_same = FALSE;
4123 }
4124
4125 if (caja_file_can_set_permissions (file)) {
4126 all_dir_cannot_set = FALSE;
4127 }
4128 } else {
4129 if (no_files) {
4130 all_file_perm = perm;
4131 no_files = FALSE;
4132 } else if (perm != all_file_perm) {
4133 all_file_same = FALSE;
4134 }
4135
4136 if (caja_file_can_set_permissions (file)) {
4137 all_file_cannot_set = FALSE;
4138 }
4139 }
4140 }
4141
4142 if (is_folder) {
4143 all_same = all_dir_same;
4144 all_perm = all_dir_perm;
4145 } else {
4146 all_same = all_file_same && !no_files;
4147 all_perm = all_file_perm;
4148 }
4149
4150 store = GTK_LIST_STORE (model);
4151 if (all_same) {
4152 gboolean found;
4153
4154 found = FALSE;
4155 gtk_tree_model_get_iter_first (model, &iter);
4156 do {
4157 int current_perm;
4158 gtk_tree_model_get (model, &iter, 1, ¤t_perm, -1);
4159
4160 if (current_perm == all_perm) {
4161 found = TRUE;
4162 break;
4163 }
4164 } while (gtk_tree_model_iter_next (model, &iter));
4165
4166 if (!found) {
4167 GString *str;
4168 str = g_string_new ("");
4169
4170 if (!(all_perm & PERMISSION_READ)) {
4171 /* Translators: this gets concatenated to "no read",
4172 * "no access", etc. (see following strings)
4173 */
4174 g_string_append (str, _("no "));
4175 }
4176 if (is_folder) {
4177 g_string_append (str, _("list"));
4178 } else {
4179 g_string_append (str, _("read"));
4180 }
4181
4182 g_string_append (str, ", ");
4183
4184 if (!(all_perm & PERMISSION_WRITE)) {
4185 g_string_append (str, _("no "));
4186 }
4187 if (is_folder) {
4188 g_string_append (str, _("create/delete"));
4189 } else {
4190 g_string_append (str, _("write"));
4191 }
4192
4193 if (is_folder) {
4194 g_string_append (str, ", ");
4195
4196 if (!(all_perm & PERMISSION_EXEC)) {
4197 g_string_append (str, _("no "));
4198 }
4199 g_string_append (str, _("access"));
4200 }
4201
4202 gtk_list_store_append (store, &iter);
4203 gtk_list_store_set (store, &iter,
4204 0, str->str,
4205 1, all_perm, -1);
4206
4207 g_string_free (str, TRUE);
4208 }
4209 } else {
4210 permission_combo_add_multiple_choice (combo, &iter);
4211 }
4212
4213 g_signal_handlers_block_by_func (G_OBJECT (combo),
4214 G_CALLBACK (permission_combo_changed),
4215 window);
4216
4217 gtk_combo_box_set_active_iter (combo, &iter);
4218
4219 /* Also enable if no files found (for recursive
4220 file changes when only selecting folders) */
4221 if (is_folder) {
4222 sensitive = !all_dir_cannot_set;
4223 } else {
4224 sensitive = !all_file_cannot_set ||
4225 window->details->has_recursive_apply;
4226 }
4227 gtk_widget_set_sensitive (GTK_WIDGET (combo), sensitive);
4228
4229 g_signal_handlers_unblock_by_func (G_OBJECT (combo),
4230 G_CALLBACK (permission_combo_changed),
4231 window);
4232
4233 }
4234
4235 static void
add_permissions_combo_box(FMPropertiesWindow * window,GtkGrid * grid,PermissionType type,gboolean is_folder,gboolean short_label)4236 add_permissions_combo_box (FMPropertiesWindow *window, GtkGrid *grid,
4237 PermissionType type, gboolean is_folder,
4238 gboolean short_label)
4239 {
4240 GtkWidget *combo;
4241 GtkLabel *label;
4242 GtkListStore *store;
4243 GtkCellRenderer *cell;
4244 GtkTreeIter iter;
4245
4246 if (short_label) {
4247 label = attach_title_field (grid, _("Access:"));
4248 } else if (is_folder) {
4249 label = attach_title_field (grid, _("Folder access:"));
4250 } else {
4251 label = attach_title_field (grid, _("File access:"));
4252 }
4253
4254 store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
4255 combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
4256
4257 g_object_set_data (G_OBJECT (combo), "is-folder", GINT_TO_POINTER (is_folder));
4258 g_object_set_data (G_OBJECT (combo), "permission-type", GINT_TO_POINTER (type));
4259
4260 if (is_folder) {
4261 if (type != PERMISSION_USER) {
4262 gtk_list_store_append (store, &iter);
4263 /* Translators: this is referred to the permissions
4264 * the user has in a directory.
4265 */
4266 gtk_list_store_set (store, &iter, 0, _("None"), 1, 0, -1);
4267 }
4268 gtk_list_store_append (store, &iter);
4269 gtk_list_store_set (store, &iter, 0, _("List files only"), 1, PERMISSION_READ, -1);
4270 gtk_list_store_append (store, &iter);
4271 gtk_list_store_set (store, &iter, 0, _("Access files"), 1, PERMISSION_READ|PERMISSION_EXEC, -1);
4272 gtk_list_store_append (store, &iter);
4273 gtk_list_store_set (store, &iter, 0, _("Create and delete files"), 1, PERMISSION_READ|PERMISSION_EXEC|PERMISSION_WRITE, -1);
4274 } else {
4275 if (type != PERMISSION_USER) {
4276 gtk_list_store_append (store, &iter);
4277 gtk_list_store_set (store, &iter, 0, _("None"), 1, 0, -1);
4278 }
4279 gtk_list_store_append (store, &iter);
4280 gtk_list_store_set (store, &iter, 0, _("Read-only"), 1, PERMISSION_READ, -1);
4281 gtk_list_store_append (store, &iter);
4282 gtk_list_store_set (store, &iter, 0, _("Read and write"), 1, PERMISSION_READ|PERMISSION_WRITE, -1);
4283 }
4284 if (window->details->has_recursive_apply) {
4285 permission_combo_add_multiple_choice (GTK_COMBO_BOX (combo), &iter);
4286 }
4287
4288 g_object_unref (store);
4289
4290 window->details->permission_combos =
4291 g_list_prepend (window->details->permission_combos,
4292 combo);
4293
4294 g_signal_connect (combo, "changed", G_CALLBACK (permission_combo_changed), window);
4295
4296 cell = gtk_cell_renderer_text_new ();
4297 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
4298 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell,
4299 "text", 0,
4300 NULL);
4301
4302 gtk_label_set_mnemonic_widget (label, combo);
4303 gtk_widget_show (combo);
4304
4305 gtk_grid_attach_next_to (grid, combo, GTK_WIDGET (label),
4306 GTK_POS_RIGHT, 1, 1);
4307 }
4308
4309
4310 static GtkWidget *
append_special_execution_checkbox(FMPropertiesWindow * window,GtkGrid * grid,GtkWidget * sibling,const char * label_text,guint32 permission_to_check)4311 append_special_execution_checkbox (FMPropertiesWindow *window,
4312 GtkGrid *grid,
4313 GtkWidget *sibling,
4314 const char *label_text,
4315 guint32 permission_to_check)
4316 {
4317 GtkWidget *check_button;
4318
4319 check_button = gtk_check_button_new_with_mnemonic (label_text);
4320 gtk_widget_show (check_button);
4321
4322 if (sibling != NULL) {
4323 gtk_grid_attach_next_to (grid, check_button, sibling,
4324 GTK_POS_RIGHT, 1, 1);
4325 } else {
4326 gtk_container_add_with_properties (GTK_CONTAINER (grid), check_button,
4327 "left-attach", 1,
4328 NULL);
4329 }
4330
4331 set_up_permissions_checkbox (window,
4332 check_button,
4333 permission_to_check,
4334 FALSE);
4335 g_object_set_data (G_OBJECT (check_button), "is-special",
4336 GINT_TO_POINTER (TRUE));
4337
4338 return check_button;
4339 }
4340
4341 static void
append_special_execution_flags(FMPropertiesWindow * window,GtkGrid * grid)4342 append_special_execution_flags (FMPropertiesWindow *window, GtkGrid *grid)
4343 {
4344 GtkWidget *title;
4345
4346 append_blank_slim_row (grid);
4347 title = GTK_WIDGET (attach_title_field (grid, _("Special flags:")));
4348
4349 append_special_execution_checkbox (window, grid, title, _("Set _user ID"), UNIX_PERM_SUID);
4350 append_special_execution_checkbox (window, grid, NULL, _("Set gro_up ID"), UNIX_PERM_SGID);
4351 append_special_execution_checkbox (window, grid, NULL, _("_Sticky"), UNIX_PERM_STICKY);
4352 }
4353
4354 static gboolean
all_can_get_permissions(GList * file_list)4355 all_can_get_permissions (GList *file_list)
4356 {
4357 GList *l;
4358 for (l = file_list; l != NULL; l = l->next) {
4359 CajaFile *file;
4360
4361 file = CAJA_FILE (l->data);
4362
4363 if (!caja_file_can_get_permissions (file)) {
4364 return FALSE;
4365 }
4366 }
4367
4368 return TRUE;
4369 }
4370
4371 static gboolean
all_can_set_permissions(GList * file_list)4372 all_can_set_permissions (GList *file_list)
4373 {
4374 GList *l;
4375 for (l = file_list; l != NULL; l = l->next) {
4376 CajaFile *file;
4377
4378 file = CAJA_FILE (l->data);
4379
4380 if (!caja_file_can_set_permissions (file)) {
4381 return FALSE;
4382 }
4383 }
4384
4385 return TRUE;
4386 }
4387
4388 static GHashTable *
get_initial_permissions(GList * file_list)4389 get_initial_permissions (GList *file_list)
4390 {
4391 GHashTable *ret;
4392 GList *l;
4393
4394 ret = g_hash_table_new (g_direct_hash,
4395 g_direct_equal);
4396
4397 for (l = file_list; l != NULL; l = l->next) {
4398 guint32 permissions;
4399 CajaFile *file;
4400
4401 file = CAJA_FILE (l->data);
4402
4403 permissions = caja_file_get_permissions (file);
4404 g_hash_table_insert (ret, file,
4405 GINT_TO_POINTER (permissions));
4406 }
4407
4408 return ret;
4409 }
4410
4411 static void
create_simple_permissions(FMPropertiesWindow * window,GtkGrid * page_grid)4412 create_simple_permissions (FMPropertiesWindow *window, GtkGrid *page_grid)
4413 {
4414 gboolean has_file, has_directory;
4415 GtkLabel *group_label;
4416 GtkLabel *owner_label;
4417 GtkLabel *execute_label;
4418 GtkWidget *value;
4419
4420 has_file = files_has_file (window);
4421 has_directory = files_has_directory (window);
4422
4423 if (!is_multi_file_window (window) && caja_file_can_set_owner (get_target_file (window))) {
4424 GtkComboBox *owner_combo_box;
4425
4426 owner_label = attach_title_field (page_grid, _("_Owner:"));
4427 /* Combo box in this case. */
4428 owner_combo_box = attach_owner_combo_box (page_grid,
4429 GTK_WIDGET (owner_label),
4430 get_target_file (window));
4431 gtk_label_set_mnemonic_widget (owner_label,
4432 GTK_WIDGET (owner_combo_box));
4433 } else {
4434 owner_label = attach_title_field (page_grid, _("Owner:"));
4435 /* Static text in this case. */
4436 value = attach_value_field (window,
4437 page_grid, GTK_WIDGET (owner_label),
4438 "owner",
4439 INCONSISTENT_STATE_STRING,
4440 FALSE);
4441 gtk_label_set_mnemonic_widget (owner_label, value);
4442 }
4443
4444 if (has_directory) {
4445 add_permissions_combo_box (window, page_grid,
4446 PERMISSION_USER, TRUE, FALSE);
4447 }
4448 if (has_file || window->details->has_recursive_apply) {
4449 add_permissions_combo_box (window, page_grid,
4450 PERMISSION_USER, FALSE, !has_directory);
4451 }
4452
4453 append_blank_slim_row (page_grid);
4454
4455 if (!is_multi_file_window (window) && caja_file_can_set_group (get_target_file (window))) {
4456 GtkComboBox *group_combo_box;
4457
4458 group_label = attach_title_field (page_grid, _("_Group:"));
4459
4460 /* Combo box in this case. */
4461 group_combo_box = attach_group_combo_box (page_grid, GTK_WIDGET (group_label),
4462 get_target_file (window));
4463 gtk_label_set_mnemonic_widget (group_label,
4464 GTK_WIDGET (group_combo_box));
4465 } else {
4466 group_label = attach_title_field (page_grid, _("Group:"));
4467
4468 /* Static text in this case. */
4469 value = attach_value_field (window, page_grid,
4470 GTK_WIDGET (group_label),
4471 "group",
4472 INCONSISTENT_STATE_STRING,
4473 FALSE);
4474 gtk_label_set_mnemonic_widget (group_label, value);
4475 }
4476
4477 if (has_directory) {
4478 add_permissions_combo_box (window, page_grid,
4479 PERMISSION_GROUP, TRUE,
4480 FALSE);
4481 }
4482 if (has_file || window->details->has_recursive_apply) {
4483 add_permissions_combo_box (window, page_grid,
4484 PERMISSION_GROUP, FALSE,
4485 !has_directory);
4486 }
4487
4488 append_blank_slim_row (page_grid);
4489
4490 group_label = attach_title_field (page_grid, _("Others"));
4491
4492 if (has_directory) {
4493 add_permissions_combo_box (window, page_grid,
4494 PERMISSION_OTHER, TRUE,
4495 FALSE);
4496 }
4497 if (has_file || window->details->has_recursive_apply) {
4498 add_permissions_combo_box (window, page_grid,
4499 PERMISSION_OTHER, FALSE,
4500 !has_directory);
4501 }
4502
4503 append_blank_slim_row (page_grid);
4504
4505 execute_label = attach_title_field (page_grid, _("Execute:"));
4506 add_permissions_checkbox_with_label (window, page_grid,
4507 GTK_WIDGET (execute_label),
4508 _("Allow _executing file as program"),
4509 UNIX_PERM_USER_EXEC|UNIX_PERM_GROUP_EXEC|UNIX_PERM_OTHER_EXEC,
4510 execute_label, FALSE);
4511 }
4512
4513 static void
create_permission_checkboxes(FMPropertiesWindow * window,GtkGrid * page_grid,gboolean is_folder)4514 create_permission_checkboxes (FMPropertiesWindow *window,
4515 GtkGrid *page_grid,
4516 gboolean is_folder)
4517 {
4518 GtkLabel *owner_perm_label;
4519 GtkLabel *group_perm_label;
4520 GtkLabel *other_perm_label;
4521 GtkGrid *check_button_grid;
4522 GtkWidget *w;
4523
4524 owner_perm_label = attach_title_field (page_grid, _("Owner:"));
4525 group_perm_label = attach_title_field (page_grid, _("Group:"));
4526 other_perm_label = attach_title_field (page_grid, _("Others:"));
4527
4528 check_button_grid = GTK_GRID (create_grid_with_standard_properties ());
4529 gtk_widget_show (GTK_WIDGET (check_button_grid));
4530
4531 gtk_grid_attach_next_to (page_grid, GTK_WIDGET (check_button_grid),
4532 GTK_WIDGET (owner_perm_label),
4533 GTK_POS_RIGHT, 1, 3);
4534
4535 w = add_permissions_checkbox (window,
4536 check_button_grid,
4537 NULL,
4538 PERMISSIONS_CHECKBOXES_READ,
4539 UNIX_PERM_USER_READ,
4540 owner_perm_label,
4541 is_folder);
4542
4543 w = add_permissions_checkbox (window,
4544 check_button_grid,
4545 w,
4546 PERMISSIONS_CHECKBOXES_WRITE,
4547 UNIX_PERM_USER_WRITE,
4548 owner_perm_label,
4549 is_folder);
4550
4551 w = add_permissions_checkbox (window,
4552 check_button_grid,
4553 w,
4554 PERMISSIONS_CHECKBOXES_EXECUTE,
4555 UNIX_PERM_USER_EXEC,
4556 owner_perm_label,
4557 is_folder);
4558
4559 w = add_permissions_checkbox (window,
4560 check_button_grid,
4561 NULL,
4562 PERMISSIONS_CHECKBOXES_READ,
4563 UNIX_PERM_GROUP_READ,
4564 group_perm_label,
4565 is_folder);
4566
4567 w = add_permissions_checkbox (window,
4568 check_button_grid,
4569 w,
4570 PERMISSIONS_CHECKBOXES_WRITE,
4571 UNIX_PERM_GROUP_WRITE,
4572 group_perm_label,
4573 is_folder);
4574
4575 w = add_permissions_checkbox (window,
4576 check_button_grid,
4577 w,
4578 PERMISSIONS_CHECKBOXES_EXECUTE,
4579 UNIX_PERM_GROUP_EXEC,
4580 group_perm_label,
4581 is_folder);
4582
4583 w = add_permissions_checkbox (window,
4584 check_button_grid,
4585 NULL,
4586 PERMISSIONS_CHECKBOXES_READ,
4587 UNIX_PERM_OTHER_READ,
4588 other_perm_label,
4589 is_folder);
4590
4591 w = add_permissions_checkbox (window,
4592 check_button_grid,
4593 w,
4594 PERMISSIONS_CHECKBOXES_WRITE,
4595 UNIX_PERM_OTHER_WRITE,
4596 other_perm_label,
4597 is_folder);
4598
4599 add_permissions_checkbox (window,
4600 check_button_grid,
4601 w,
4602 PERMISSIONS_CHECKBOXES_EXECUTE,
4603 UNIX_PERM_OTHER_EXEC,
4604 other_perm_label,
4605 is_folder);
4606 }
4607
4608 static void
create_advanced_permissions(FMPropertiesWindow * window,GtkGrid * page_grid)4609 create_advanced_permissions (FMPropertiesWindow *window, GtkGrid *page_grid)
4610 {
4611 GtkLabel *group_label;
4612 GtkLabel *owner_label;
4613 gboolean has_directory, has_file;
4614
4615 if (!is_multi_file_window (window) && caja_file_can_set_owner (get_target_file (window))) {
4616 GtkComboBox *owner_combo_box;
4617
4618 owner_label = attach_title_field (page_grid, _("_Owner:"));
4619 /* Combo box in this case. */
4620 owner_combo_box = attach_owner_combo_box (page_grid,
4621 GTK_WIDGET (owner_label),
4622 get_target_file (window));
4623 gtk_label_set_mnemonic_widget (owner_label,
4624 GTK_WIDGET (owner_combo_box));
4625 } else {
4626 GtkWidget *value;
4627 owner_label = attach_title_field (page_grid, _("Owner:"));
4628
4629 /* Static text in this case. */
4630 value = attach_value_field (window,
4631 page_grid,
4632 GTK_WIDGET (owner_label),
4633 "owner",
4634 INCONSISTENT_STATE_STRING,
4635 FALSE);
4636 gtk_label_set_mnemonic_widget (owner_label, value);
4637 }
4638
4639 if (!is_multi_file_window (window) && caja_file_can_set_group (get_target_file (window))) {
4640 GtkComboBox *group_combo_box;
4641
4642 group_label = attach_title_field (page_grid, _("_Group:"));
4643
4644 /* Combo box in this case. */
4645 group_combo_box = attach_group_combo_box (page_grid, GTK_WIDGET (group_label),
4646 get_target_file (window));
4647 gtk_label_set_mnemonic_widget (group_label,
4648 GTK_WIDGET (group_combo_box));
4649 } else {
4650 group_label = attach_title_field (page_grid, _("Group:"));
4651
4652 /* Static text in this case. */
4653 attach_value_field (window, page_grid, GTK_WIDGET (group_label),
4654 "group",
4655 INCONSISTENT_STATE_STRING,
4656 FALSE);
4657 }
4658
4659 append_blank_slim_row (page_grid);
4660
4661 has_directory = files_has_directory (window);
4662 has_file = files_has_file (window);
4663
4664 if (has_directory) {
4665 if (has_file || window->details->has_recursive_apply) {
4666 attach_title_field (page_grid, _("Folder Permissions:"));
4667 }
4668 create_permission_checkboxes (window, page_grid, TRUE);
4669 }
4670
4671 if (has_file || window->details->has_recursive_apply) {
4672 if (has_directory) {
4673 attach_title_field (page_grid, _("File Permissions:"));
4674 }
4675 create_permission_checkboxes (window, page_grid, FALSE);
4676 }
4677
4678 append_blank_slim_row (page_grid);
4679 append_special_execution_flags (window, page_grid);
4680
4681 append_title_value_pair
4682 (window, page_grid, _("Text view:"),
4683 "permissions", INCONSISTENT_STATE_STRING,
4684 FALSE);
4685 }
4686
4687 static void
set_recursive_permissions_done(gpointer callback_data)4688 set_recursive_permissions_done (gpointer callback_data)
4689 {
4690 FMPropertiesWindow *window;
4691
4692 window = FM_PROPERTIES_WINDOW (callback_data);
4693 end_long_operation (window);
4694
4695 g_object_unref (window);
4696 }
4697
4698
4699 static void
apply_recursive_clicked(GtkWidget * recursive_button,FMPropertiesWindow * window)4700 apply_recursive_clicked (GtkWidget *recursive_button,
4701 FMPropertiesWindow *window)
4702 {
4703 guint32 file_permission, file_permission_mask;
4704 guint32 dir_permission, dir_permission_mask;
4705 guint32 vfs_mask, vfs_new_perm, p;
4706 gboolean active, is_folder, is_special, use_original;
4707 GList *l;
4708 GtkTreeModel *model;
4709 GtkTreeIter iter;
4710 PermissionType type;
4711 int new_perm, mask;
4712 GtkWidget *button = NULL;
4713 GtkWidget *combo = NULL;
4714
4715 file_permission = 0;
4716 file_permission_mask = 0;
4717 dir_permission = 0;
4718 dir_permission_mask = 0;
4719
4720 /* Advanced mode and execute checkbox: */
4721 for (l = window->details->permission_buttons; l != NULL; l = l->next) {
4722 button = l->data;
4723
4724 if (gtk_toggle_button_get_inconsistent (GTK_TOGGLE_BUTTON (button))) {
4725 continue;
4726 }
4727
4728 active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
4729 p = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
4730 "permission"));
4731 is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
4732 "is-folder"));
4733 is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
4734 "is-special"));
4735
4736 if (is_folder || is_special) {
4737 dir_permission_mask |= p;
4738 if (active) {
4739 dir_permission |= p;
4740 }
4741 }
4742 if (!is_folder || is_special) {
4743 file_permission_mask |= p;
4744 if (active) {
4745 file_permission |= p;
4746 }
4747 }
4748 }
4749 /* Simple mode, minus exec checkbox */
4750 for (l = window->details->permission_combos; l != NULL; l = l->next) {
4751 combo = l->data;
4752
4753 if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) {
4754 continue;
4755 }
4756
4757 type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type"));
4758 is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo),
4759 "is-folder"));
4760
4761 model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
4762 gtk_tree_model_get (model, &iter, 1, &new_perm, 2, &use_original, -1);
4763 if (use_original) {
4764 continue;
4765 }
4766 vfs_new_perm = permission_to_vfs (type, new_perm);
4767
4768 if (is_folder) {
4769 mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC;
4770 } else {
4771 mask = PERMISSION_READ|PERMISSION_WRITE;
4772 }
4773 vfs_mask = permission_to_vfs (type, mask);
4774
4775 if (is_folder) {
4776 dir_permission_mask |= vfs_mask;
4777 dir_permission |= vfs_new_perm;
4778 } else {
4779 file_permission_mask |= vfs_mask;
4780 file_permission |= vfs_new_perm;
4781 }
4782 }
4783
4784 for (l = window->details->target_files; l != NULL; l = l->next) {
4785 CajaFile *file;
4786
4787 file = CAJA_FILE (l->data);
4788
4789 if (caja_file_is_directory (file) &&
4790 caja_file_can_set_permissions (file)) {
4791 char *uri;
4792
4793 uri = caja_file_get_uri (file);
4794 start_long_operation (window);
4795 g_object_ref (window);
4796 caja_file_set_permissions_recursive (uri,
4797 file_permission,
4798 file_permission_mask,
4799 dir_permission,
4800 dir_permission_mask,
4801 set_recursive_permissions_done,
4802 window);
4803 g_free (uri);
4804 }
4805 }
4806 }
4807
4808 static void
create_permissions_page(FMPropertiesWindow * window)4809 create_permissions_page (FMPropertiesWindow *window)
4810 {
4811 GtkWidget *vbox;
4812 GList *file_list;
4813
4814 vbox = create_page_with_vbox (window->details->notebook,
4815 _("Permissions"));
4816
4817 file_list = window->details->original_files;
4818
4819 window->details->initial_permissions = NULL;
4820
4821 if (all_can_get_permissions (file_list) && all_can_get_permissions (window->details->target_files)) {
4822 GtkGrid *page_grid;
4823
4824 window->details->initial_permissions = get_initial_permissions (window->details->target_files);
4825 window->details->has_recursive_apply = files_has_changable_permissions_directory (window);
4826
4827 if (!all_can_set_permissions (file_list)) {
4828 add_prompt_and_separator (
4829 vbox,
4830 _("You are not the owner, so you cannot change these permissions."));
4831 }
4832
4833 page_grid = GTK_GRID (create_grid_with_standard_properties ());
4834
4835 gtk_widget_show (GTK_WIDGET (page_grid));
4836 gtk_box_pack_start (GTK_BOX (vbox),
4837 GTK_WIDGET (page_grid),
4838 TRUE, TRUE, 0);
4839
4840 if (g_settings_get_boolean (caja_preferences, CAJA_PREFERENCES_SHOW_ADVANCED_PERMISSIONS)) {
4841 create_advanced_permissions (window, page_grid);
4842 } else {
4843 create_simple_permissions (window, page_grid);
4844 }
4845
4846 append_blank_slim_row (page_grid);
4847
4848 #ifdef HAVE_SELINUX
4849 append_title_value_pair
4850 (window, page_grid, _("SELinux context:"),
4851 "selinux_context", INCONSISTENT_STATE_STRING,
4852 FALSE);
4853 #endif
4854 append_title_value_pair
4855 (window, page_grid, _("Last changed:"),
4856 "date_permissions", INCONSISTENT_STATE_STRING,
4857 FALSE);
4858
4859 if (window->details->has_recursive_apply) {
4860 GtkWidget *button, *hbox;
4861
4862 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
4863 gtk_widget_show (hbox);
4864 gtk_container_add_with_properties (GTK_CONTAINER (page_grid), hbox,
4865 "width", 2,
4866 NULL);
4867
4868 button = gtk_button_new_with_mnemonic (_("Apply Permissions to Enclosed Files"));
4869 gtk_widget_show (button);
4870 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
4871 g_signal_connect (button, "clicked",
4872 G_CALLBACK (apply_recursive_clicked),
4873 window);
4874 }
4875 } else {
4876 char *prompt_text;
4877
4878 if (!is_multi_file_window (window)) {
4879 char *file_name;
4880
4881 file_name = caja_file_get_display_name (get_target_file (window));
4882 prompt_text = g_strdup_printf (_("The permissions of \"%s\" could not be determined."), file_name);
4883 g_free (file_name);
4884 } else {
4885 prompt_text = g_strdup (_("The permissions of the selected file could not be determined."));
4886 }
4887
4888 add_prompt (vbox, prompt_text, TRUE);
4889 g_free (prompt_text);
4890 }
4891 }
4892
4893 static void
append_extension_pages(FMPropertiesWindow * window)4894 append_extension_pages (FMPropertiesWindow *window)
4895 {
4896 GList *providers;
4897 GList *module_providers;
4898 GList *p;
4899
4900 providers = caja_extensions_get_for_type (CAJA_TYPE_PROPERTY_PAGE_PROVIDER);
4901
4902 /* FIXME: we also need the property pages from two old modules that
4903 * are not registered as proper extensions. This is going to work
4904 * this way until some generic solution is introduced.
4905 */
4906 module_providers = caja_module_get_extensions_for_type (CAJA_TYPE_PROPERTY_PAGE_PROVIDER);
4907 for (p = module_providers; p != NULL; p = p->next) {
4908 const gchar *type_name = G_OBJECT_TYPE_NAME (G_OBJECT (p->data));
4909 if (g_strcmp0 (type_name, "CajaNotesViewerProvider") == 0 ||
4910 g_strcmp0 (type_name, "CajaImagePropertiesPageProvider") == 0) {
4911 providers = g_list_prepend (providers, p->data);
4912 }
4913 }
4914
4915 for (p = providers; p != NULL; p = p->next) {
4916 CajaPropertyPageProvider *provider;
4917 GList *pages;
4918 GList *l;
4919
4920 provider = CAJA_PROPERTY_PAGE_PROVIDER (p->data);
4921
4922 pages = caja_property_page_provider_get_pages
4923 (provider, window->details->original_files);
4924
4925 for (l = pages; l != NULL; l = l->next) {
4926 CajaPropertyPage *page;
4927 GtkWidget *page_widget;
4928 GtkWidget *label;
4929
4930 page = CAJA_PROPERTY_PAGE (l->data);
4931
4932 g_object_get (G_OBJECT (page),
4933 "page", &page_widget, "label", &label,
4934 NULL);
4935
4936 gtk_notebook_append_page (window->details->notebook,
4937 page_widget, label);
4938
4939 g_object_set_data (G_OBJECT (page_widget),
4940 "is-extension-page",
4941 page);
4942
4943 g_object_unref (page_widget);
4944 g_object_unref (label);
4945
4946 g_object_unref (page);
4947 }
4948
4949 g_list_free (pages);
4950 }
4951
4952 caja_module_extension_list_free (providers);
4953 }
4954
4955 static gboolean
should_show_emblems(FMPropertiesWindow * window)4956 should_show_emblems (FMPropertiesWindow *window)
4957 {
4958 /* FIXME bugzilla.gnome.org 45643:
4959 * Emblems aren't displayed on the the desktop Trash icon, so
4960 * we shouldn't pretend that they work by showing them here.
4961 * When bug 5643 is fixed we can remove this case.
4962 */
4963 if (!is_multi_file_window (window)
4964 && is_merged_trash_directory (get_target_file (window))) {
4965 return FALSE;
4966 }
4967
4968 return TRUE;
4969 }
4970
4971 static gboolean
should_show_permissions(FMPropertiesWindow * window)4972 should_show_permissions (FMPropertiesWindow *window)
4973 {
4974 CajaFile *file;
4975
4976 file = get_target_file (window);
4977
4978 /* Don't show permissions for Trash and Computer since they're not
4979 * really file system objects.
4980 */
4981 if (!is_multi_file_window (window)
4982 && (is_merged_trash_directory (file) ||
4983 is_computer_directory (file))) {
4984 return FALSE;
4985 }
4986
4987 return TRUE;
4988 }
4989
4990 static char *
get_pending_key(GList * file_list)4991 get_pending_key (GList *file_list)
4992 {
4993 GList *l;
4994 GList *uris;
4995 GString *key;
4996 char *ret;
4997
4998 uris = NULL;
4999 for (l = file_list; l != NULL; l = l->next) {
5000 uris = g_list_prepend (uris, caja_file_get_uri (CAJA_FILE (l->data)));
5001 }
5002 uris = g_list_sort (uris, (GCompareFunc)strcmp);
5003
5004 key = g_string_new ("");
5005 for (l = uris; l != NULL; l = l->next) {
5006 g_string_append (key, l->data);
5007 g_string_append (key, ";");
5008 }
5009
5010 g_list_free_full (uris, g_free);
5011
5012 ret = key->str;
5013 g_string_free (key, FALSE);
5014
5015 return ret;
5016 }
5017
5018 static StartupData *
startup_data_new(GList * original_files,GList * target_files,const char * pending_key,GtkWidget * parent_widget)5019 startup_data_new (GList *original_files,
5020 GList *target_files,
5021 const char *pending_key,
5022 GtkWidget *parent_widget)
5023 {
5024 StartupData *data;
5025 GList *l;
5026
5027 data = g_new0 (StartupData, 1);
5028 data->original_files = caja_file_list_copy (original_files);
5029 data->target_files = caja_file_list_copy (target_files);
5030 data->parent_widget = parent_widget;
5031 data->pending_key = g_strdup (pending_key);
5032 data->pending_files = g_hash_table_new (g_direct_hash,
5033 g_direct_equal);
5034
5035 for (l = data->target_files; l != NULL; l = l->next) {
5036 g_hash_table_insert (data->pending_files, l->data, l->data);
5037 }
5038
5039 return data;
5040 }
5041
5042 static void
startup_data_free(StartupData * data)5043 startup_data_free (StartupData *data)
5044 {
5045 caja_file_list_free (data->original_files);
5046 caja_file_list_free (data->target_files);
5047 g_hash_table_destroy (data->pending_files);
5048 g_free (data->pending_key);
5049 g_free (data);
5050 }
5051
5052 static void
file_changed_callback(CajaFile * file,gpointer user_data)5053 file_changed_callback (CajaFile *file, gpointer user_data)
5054 {
5055 FMPropertiesWindow *window = FM_PROPERTIES_WINDOW (user_data);
5056
5057 if (!g_list_find (window->details->changed_files, file)) {
5058 caja_file_ref (file);
5059 window->details->changed_files = g_list_prepend (window->details->changed_files, file);
5060
5061 schedule_files_update (window);
5062 }
5063 }
5064
5065 static gboolean
is_a_special_file(CajaFile * file)5066 is_a_special_file (CajaFile *file)
5067 {
5068 if (file == NULL ||
5069 CAJA_IS_DESKTOP_ICON_FILE (file) ||
5070 is_merged_trash_directory (file) ||
5071 is_computer_directory (file)) {
5072 return TRUE;
5073 }
5074 return FALSE;
5075 }
5076
5077 static gboolean
should_show_open_with(FMPropertiesWindow * window)5078 should_show_open_with (FMPropertiesWindow *window)
5079 {
5080 CajaFile *file;
5081
5082 /* Don't show open with tab for desktop special icons (trash, etc)
5083 * We don't get the open-with menu for these anyway.
5084 *
5085 * Also don't show it for folders. Changing the default app for folders
5086 * leads to all sort of hard to understand errors.
5087 */
5088
5089 if (is_multi_file_window (window)) {
5090 if (!file_list_attributes_identical (window->details->original_files,
5091 "mime_type")) {
5092 return FALSE;
5093 } else {
5094
5095 GList *l;
5096
5097 for (l = window->details->original_files; l; l = l->next) {
5098 file = CAJA_FILE (l->data);
5099 if (caja_file_is_directory (file) ||
5100 is_a_special_file (file)) {
5101 return FALSE;
5102 }
5103 }
5104 }
5105 } else {
5106 file = get_original_file (window);
5107 if (caja_file_is_directory (file) ||
5108 is_a_special_file (file)) {
5109 return FALSE;
5110 }
5111 }
5112 return TRUE;
5113 }
5114
5115 static void
create_open_with_page(FMPropertiesWindow * window)5116 create_open_with_page (FMPropertiesWindow *window)
5117 {
5118 GtkWidget *vbox;
5119 char *mime_type;
5120
5121 mime_type = caja_file_get_mime_type (get_target_file (window));
5122
5123 if (!is_multi_file_window (window)) {
5124 char *uri;
5125
5126 uri = caja_file_get_uri (get_target_file (window));
5127
5128 if (uri == NULL) {
5129 return;
5130 }
5131
5132 vbox = caja_mime_application_chooser_new (uri, mime_type);
5133
5134 g_free (uri);
5135 } else {
5136 GList *uris;
5137
5138 uris = window->details->original_files;
5139 if (uris == NULL) {
5140 return;
5141 }
5142 vbox = caja_mime_application_chooser_new_for_multiple_files (uris, mime_type);
5143 }
5144
5145 gtk_widget_show (vbox);
5146 g_free (mime_type);
5147
5148 gtk_notebook_append_page (window->details->notebook,
5149 vbox, gtk_label_new (_("Open With")));
5150 }
5151
5152
5153 static FMPropertiesWindow *
create_properties_window(StartupData * startup_data)5154 create_properties_window (StartupData *startup_data)
5155 {
5156 FMPropertiesWindow *window;
5157 GList *l;
5158 GtkWidget *action_area;
5159
5160 window = FM_PROPERTIES_WINDOW (gtk_widget_new (fm_properties_window_get_type (), NULL));
5161
5162 window->details->original_files = caja_file_list_copy (startup_data->original_files);
5163
5164 window->details->target_files = caja_file_list_copy (startup_data->target_files);
5165
5166 gtk_window_set_screen (GTK_WINDOW (window),
5167 gtk_widget_get_screen (startup_data->parent_widget));
5168
5169 gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_DIALOG);
5170
5171 /* Set initial window title */
5172 update_properties_window_title (window);
5173
5174 /* Start monitoring the file attributes we display. Note that some
5175 * of the attributes are for the original file, and some for the
5176 * target files.
5177 */
5178
5179 for (l = window->details->original_files; l != NULL; l = l->next) {
5180 CajaFile *file;
5181 CajaFileAttributes attributes;
5182
5183 file = CAJA_FILE (l->data);
5184
5185 attributes =
5186 CAJA_FILE_ATTRIBUTES_FOR_ICON |
5187 CAJA_FILE_ATTRIBUTE_INFO |
5188 CAJA_FILE_ATTRIBUTE_LINK_INFO;
5189
5190 caja_file_monitor_add (file,
5191 &window->details->original_files,
5192 attributes);
5193 }
5194
5195 for (l = window->details->target_files; l != NULL; l = l->next) {
5196 CajaFile *file;
5197 CajaFileAttributes attributes;
5198
5199 file = CAJA_FILE (l->data);
5200
5201 attributes = 0;
5202 if (caja_file_is_directory (file)) {
5203 attributes |= CAJA_FILE_ATTRIBUTE_DEEP_COUNTS;
5204 }
5205
5206 attributes |= CAJA_FILE_ATTRIBUTE_INFO;
5207 caja_file_monitor_add (file, &window->details->target_files, attributes);
5208 }
5209
5210 for (l = window->details->target_files; l != NULL; l = l->next) {
5211 g_signal_connect_object (CAJA_FILE (l->data),
5212 "changed",
5213 G_CALLBACK (file_changed_callback),
5214 G_OBJECT (window),
5215 0);
5216 }
5217
5218 for (l = window->details->original_files; l != NULL; l = l->next) {
5219 g_signal_connect_object (CAJA_FILE (l->data),
5220 "changed",
5221 G_CALLBACK (file_changed_callback),
5222 G_OBJECT (window),
5223 0);
5224 }
5225
5226 /* Create the notebook tabs. */
5227 window->details->notebook = GTK_NOTEBOOK (gtk_notebook_new ());
5228
5229 gtk_widget_add_events (GTK_WIDGET (window->details->notebook), GDK_SCROLL_MASK);
5230 g_signal_connect (window->details->notebook,
5231 "scroll-event",
5232 G_CALLBACK (eel_notebook_scroll_event_cb),
5233 NULL);
5234
5235 gtk_widget_show (GTK_WIDGET (window->details->notebook));
5236 gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))),
5237 GTK_WIDGET (window->details->notebook),
5238 TRUE, TRUE, 0);
5239
5240 /* Create the pages. */
5241 create_basic_page (window);
5242
5243 if (should_show_emblems (window)) {
5244 create_emblems_page (window);
5245 }
5246
5247 if (should_show_permissions (window)) {
5248 create_permissions_page (window);
5249 }
5250
5251 if (should_show_open_with (window)) {
5252 create_open_with_page (window);
5253 }
5254
5255 /* append pages from available views */
5256 append_extension_pages (window);
5257
5258 eel_dialog_add_button (GTK_DIALOG (window),
5259 _("_Help"),
5260 "help-browser",
5261 GTK_RESPONSE_HELP);
5262
5263 action_area = gtk_widget_get_parent (eel_dialog_add_button (GTK_DIALOG (window),
5264 _("_Close"),
5265 "window-close",
5266 GTK_RESPONSE_CLOSE));
5267
5268 /* FIXME - HIGificiation, should be done inside GTK+ */
5269 gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (window))), 12);
5270 gtk_container_set_border_width (GTK_CONTAINER (action_area), 0);
5271 gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))), 12);
5272
5273 /* Update from initial state */
5274 properties_window_update (window, NULL);
5275
5276 return window;
5277 }
5278
5279 static GList *
get_target_file_list(GList * original_files)5280 get_target_file_list (GList *original_files)
5281 {
5282 GList *ret;
5283 GList *l;
5284
5285 ret = NULL;
5286
5287 for (l = original_files; l != NULL; l = l->next) {
5288 CajaFile *target;
5289
5290 target = get_target_file_for_original_file (CAJA_FILE (l->data));
5291
5292 ret = g_list_prepend (ret, target);
5293 }
5294
5295 ret = g_list_reverse (ret);
5296
5297 return ret;
5298 }
5299
5300 static void
add_window(FMPropertiesWindow * window)5301 add_window (FMPropertiesWindow *window)
5302 {
5303 if (!is_multi_file_window (window)) {
5304 g_hash_table_insert (windows,
5305 get_original_file (window),
5306 window);
5307 g_object_set_data (G_OBJECT (window), "window_key",
5308 get_original_file (window));
5309 }
5310 }
5311
5312 static void
remove_window(FMPropertiesWindow * window)5313 remove_window (FMPropertiesWindow *window)
5314 {
5315 gpointer key;
5316
5317 key = g_object_get_data (G_OBJECT (window), "window_key");
5318 if (key) {
5319 g_hash_table_remove (windows, key);
5320 }
5321 }
5322
5323 static GtkWindow *
get_existing_window(GList * file_list)5324 get_existing_window (GList *file_list)
5325 {
5326 if (!file_list->next) {
5327 return g_hash_table_lookup (windows, file_list->data);
5328 }
5329
5330 return NULL;
5331 }
5332
5333 static void
cancel_create_properties_window_callback(gpointer callback_data)5334 cancel_create_properties_window_callback (gpointer callback_data)
5335 {
5336 remove_pending ((StartupData *)callback_data, TRUE, FALSE, TRUE);
5337 }
5338
5339 static void
parent_widget_destroyed_callback(GtkWidget * widget,gpointer callback_data)5340 parent_widget_destroyed_callback (GtkWidget *widget, gpointer callback_data)
5341 {
5342 g_assert (widget == ((StartupData *)callback_data)->parent_widget);
5343
5344 remove_pending ((StartupData *)callback_data, TRUE, TRUE, FALSE);
5345 }
5346
5347 static void
cancel_call_when_ready_callback(gpointer key,gpointer value,gpointer user_data)5348 cancel_call_when_ready_callback (gpointer key,
5349 gpointer value,
5350 gpointer user_data)
5351 {
5352 caja_file_cancel_call_when_ready
5353 (CAJA_FILE (key),
5354 is_directory_ready_callback,
5355 user_data);
5356 }
5357
5358 static void
remove_pending(StartupData * startup_data,gboolean cancel_call_when_ready,gboolean cancel_timed_wait,gboolean cancel_destroy_handler)5359 remove_pending (StartupData *startup_data,
5360 gboolean cancel_call_when_ready,
5361 gboolean cancel_timed_wait,
5362 gboolean cancel_destroy_handler)
5363 {
5364 if (cancel_call_when_ready) {
5365 g_hash_table_foreach (startup_data->pending_files,
5366 cancel_call_when_ready_callback,
5367 startup_data);
5368
5369 }
5370 if (cancel_timed_wait) {
5371 eel_timed_wait_stop
5372 (cancel_create_properties_window_callback, startup_data);
5373 }
5374 if (cancel_destroy_handler) {
5375 g_signal_handlers_disconnect_by_func (startup_data->parent_widget,
5376 G_CALLBACK (parent_widget_destroyed_callback),
5377 startup_data);
5378 }
5379
5380 g_hash_table_remove (pending_lists, startup_data->pending_key);
5381
5382 startup_data_free (startup_data);
5383 }
5384
5385 static void
is_directory_ready_callback(CajaFile * file,gpointer data)5386 is_directory_ready_callback (CajaFile *file,
5387 gpointer data)
5388 {
5389 StartupData *startup_data;
5390
5391 startup_data = data;
5392
5393 g_hash_table_remove (startup_data->pending_files, file);
5394
5395 if (g_hash_table_size (startup_data->pending_files) == 0) {
5396 FMPropertiesWindow *new_window;
5397
5398 new_window = create_properties_window (startup_data);
5399
5400 add_window (new_window);
5401
5402 remove_pending (startup_data, FALSE, TRUE, TRUE);
5403
5404 gtk_window_present (GTK_WINDOW (new_window));
5405 }
5406 }
5407
5408
5409 void
fm_properties_window_present(GList * original_files,GtkWidget * parent_widget)5410 fm_properties_window_present (GList *original_files,
5411 GtkWidget *parent_widget)
5412 {
5413 GList *l, *next;
5414 GtkWidget *parent_window;
5415 StartupData *startup_data;
5416 GList *target_files;
5417 GtkWindow *existing_window;
5418 char *pending_key;
5419
5420 g_return_if_fail (original_files != NULL);
5421 g_return_if_fail (GTK_IS_WIDGET (parent_widget));
5422
5423 /* Create the hash tables first time through. */
5424 if (windows == NULL) {
5425 windows = eel_g_hash_table_new_free_at_exit
5426 (NULL, NULL, "property windows");
5427 }
5428
5429 if (pending_lists == NULL) {
5430 pending_lists = eel_g_hash_table_new_free_at_exit
5431 (g_str_hash, g_str_equal, "pending property window files");
5432 }
5433
5434 /* Look to see if there's already a window for this file. */
5435 existing_window = get_existing_window (original_files);
5436 if (existing_window != NULL) {
5437 gtk_window_set_screen (existing_window,
5438 gtk_widget_get_screen (parent_widget));
5439 gtk_window_present (existing_window);
5440 return;
5441 }
5442
5443
5444 pending_key = get_pending_key (original_files);
5445
5446 /* Look to see if we're already waiting for a window for this file. */
5447 if (g_hash_table_lookup (pending_lists, pending_key) != NULL) {
5448 return;
5449 }
5450
5451 target_files = get_target_file_list (original_files);
5452
5453 startup_data = startup_data_new (original_files,
5454 target_files,
5455 pending_key,
5456 parent_widget);
5457
5458 caja_file_list_free (target_files);
5459 g_free(pending_key);
5460
5461 /* Wait until we can tell whether it's a directory before showing, since
5462 * some one-time layout decisions depend on that info.
5463 */
5464
5465 g_hash_table_insert (pending_lists, startup_data->pending_key, startup_data->pending_key);
5466 g_signal_connect (parent_widget, "destroy",
5467 G_CALLBACK (parent_widget_destroyed_callback), startup_data);
5468
5469 parent_window = gtk_widget_get_ancestor (parent_widget, GTK_TYPE_WINDOW);
5470
5471 eel_timed_wait_start
5472 (cancel_create_properties_window_callback,
5473 startup_data,
5474 _("Creating Properties window."),
5475 parent_window == NULL ? NULL : GTK_WINDOW (parent_window));
5476
5477
5478 for (l = startup_data->target_files; l != NULL; l = next) {
5479 next = l->next;
5480 caja_file_call_when_ready
5481 (CAJA_FILE (l->data),
5482 CAJA_FILE_ATTRIBUTE_INFO,
5483 is_directory_ready_callback,
5484 startup_data);
5485 }
5486 }
5487
5488 static void
real_response(GtkDialog * dialog,int response)5489 real_response (GtkDialog *dialog,
5490 int response)
5491 {
5492 GError *error = NULL;
5493
5494 switch (response) {
5495 case GTK_RESPONSE_HELP:
5496 gtk_show_uri_on_window (GTK_WINDOW (dialog),
5497 "help:mate-user-guide/goscaja-51",
5498 gtk_get_current_event_time (),
5499 &error);
5500 if (error != NULL) {
5501 eel_show_error_dialog (_("There was an error displaying help."), error->message,
5502 GTK_WINDOW (dialog));
5503 g_error_free (error);
5504 }
5505 break;
5506
5507 case GTK_RESPONSE_NONE:
5508 case GTK_RESPONSE_CLOSE:
5509 case GTK_RESPONSE_DELETE_EVENT:
5510 gtk_widget_destroy (GTK_WIDGET (dialog));
5511 break;
5512
5513 default:
5514 g_assert_not_reached ();
5515 break;
5516 }
5517 }
5518
5519 static void
real_destroy(GtkWidget * object)5520 real_destroy (GtkWidget *object)
5521 {
5522 FMPropertiesWindow *window;
5523 GList *l;
5524
5525 window = FM_PROPERTIES_WINDOW (object);
5526
5527 remove_window (window);
5528
5529 for (l = window->details->original_files; l != NULL; l = l->next) {
5530 caja_file_monitor_remove (CAJA_FILE (l->data), &window->details->original_files);
5531 }
5532 caja_file_list_free (window->details->original_files);
5533 window->details->original_files = NULL;
5534
5535 for (l = window->details->target_files; l != NULL; l = l->next) {
5536 caja_file_monitor_remove (CAJA_FILE (l->data), &window->details->target_files);
5537 }
5538 caja_file_list_free (window->details->target_files);
5539 window->details->target_files = NULL;
5540
5541 caja_file_list_free (window->details->changed_files);
5542 window->details->changed_files = NULL;
5543
5544 window->details->name_field = NULL;
5545
5546 g_list_free (window->details->emblem_buttons);
5547 window->details->emblem_buttons = NULL;
5548
5549 if (window->details->initial_emblems) {
5550 g_hash_table_destroy (window->details->initial_emblems);
5551 window->details->initial_emblems = NULL;
5552 }
5553
5554 g_list_free (window->details->permission_buttons);
5555 window->details->permission_buttons = NULL;
5556
5557 g_list_free (window->details->permission_combos);
5558 window->details->permission_combos = NULL;
5559
5560 if (window->details->initial_permissions) {
5561 g_hash_table_destroy (window->details->initial_permissions);
5562 window->details->initial_permissions = NULL;
5563 }
5564
5565 g_list_free (window->details->value_fields);
5566 window->details->value_fields = NULL;
5567
5568 if (window->details->update_directory_contents_timeout_id != 0) {
5569 g_source_remove (window->details->update_directory_contents_timeout_id);
5570 window->details->update_directory_contents_timeout_id = 0;
5571 }
5572
5573 if (window->details->update_files_timeout_id != 0) {
5574 g_source_remove (window->details->update_files_timeout_id);
5575 window->details->update_files_timeout_id = 0;
5576 }
5577
5578 GTK_WIDGET_CLASS (fm_properties_window_parent_class)->destroy (object);
5579 }
5580
5581 static void
real_finalize(GObject * object)5582 real_finalize (GObject *object)
5583 {
5584 FMPropertiesWindow *window;
5585
5586 window = FM_PROPERTIES_WINDOW (object);
5587
5588 g_list_free_full (window->details->mime_list, g_free);
5589
5590 g_free (window->details->pending_name);
5591
5592 G_OBJECT_CLASS (fm_properties_window_parent_class)->finalize (object);
5593 }
5594
5595 /* converts
5596 * file://foo/foobar/foofoo/bar
5597 * to
5598 * foofoo/bar
5599 * if
5600 * file://foo/foobar
5601 * is the parent
5602 *
5603 * It does not resolve any symlinks.
5604 * */
5605 static char *
make_relative_uri_from_full(const char * uri,const char * base_uri)5606 make_relative_uri_from_full (const char *uri,
5607 const char *base_uri)
5608 {
5609 g_assert (uri != NULL);
5610 g_assert (base_uri != NULL);
5611
5612 if (g_str_has_prefix (uri, base_uri)) {
5613 uri += strlen (base_uri);
5614 if (*uri != '/') {
5615 return NULL;
5616 }
5617
5618 while (*uri == '/') {
5619 uri++;
5620 }
5621
5622 if (*uri != '\0') {
5623 return g_strdup (uri);
5624 }
5625 }
5626
5627 return NULL;
5628 }
5629
5630 /* icon selection callback to set the image of the file object to the selected file */
5631 static void
set_icon(const char * icon_uri,FMPropertiesWindow * properties_window)5632 set_icon (const char* icon_uri, FMPropertiesWindow *properties_window)
5633 {
5634 char *icon_path;
5635
5636 g_assert (icon_uri != NULL);
5637 g_assert (FM_IS_PROPERTIES_WINDOW (properties_window));
5638
5639 icon_path = g_filename_from_uri (icon_uri, NULL, NULL);
5640 /* we don't allow remote URIs */
5641 if (icon_path != NULL) {
5642 GList *l;
5643 CajaFile *file = NULL;
5644
5645 for (l = properties_window->details->original_files; l != NULL; l = l->next) {
5646 char *file_uri;
5647
5648 file = CAJA_FILE (l->data);
5649
5650 file_uri = caja_file_get_uri (file);
5651
5652 if (caja_file_is_mime_type (file, "application/x-desktop")) {
5653 if (caja_link_local_set_icon (file_uri, icon_path)) {
5654 caja_file_invalidate_attributes (file,
5655 CAJA_FILE_ATTRIBUTE_INFO |
5656 CAJA_FILE_ATTRIBUTE_LINK_INFO);
5657 }
5658 } else {
5659 char *real_icon_uri;
5660
5661 real_icon_uri = make_relative_uri_from_full (icon_uri, file_uri);
5662
5663 if (real_icon_uri == NULL) {
5664 real_icon_uri = g_strdup (icon_uri);
5665 }
5666
5667 caja_file_set_metadata (file, CAJA_METADATA_KEY_CUSTOM_ICON, NULL, real_icon_uri);
5668 caja_file_set_metadata (file, CAJA_METADATA_KEY_ICON_SCALE, NULL, NULL);
5669
5670 g_free (real_icon_uri);
5671 }
5672
5673 g_free (file_uri);
5674 }
5675
5676 g_free (icon_path);
5677 }
5678 }
5679
5680 static void
update_preview_callback(GtkFileChooser * icon_chooser,FMPropertiesWindow * window)5681 update_preview_callback (GtkFileChooser *icon_chooser,
5682 FMPropertiesWindow *window)
5683 {
5684 GdkPixbuf *pixbuf, *scaled_pixbuf;
5685 char *filename;
5686
5687 pixbuf = NULL;
5688
5689 filename = gtk_file_chooser_get_filename (icon_chooser);
5690 if (filename != NULL) {
5691 pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
5692 }
5693
5694 if (pixbuf != NULL) {
5695 GtkWidget *preview_widget;
5696
5697 preview_widget = gtk_file_chooser_get_preview_widget (icon_chooser);
5698 gtk_file_chooser_set_preview_widget_active (icon_chooser, TRUE);
5699
5700 if (gdk_pixbuf_get_width (pixbuf) > PREVIEW_IMAGE_WIDTH) {
5701 double scale;
5702
5703 scale = (double)gdk_pixbuf_get_height (pixbuf) /
5704 gdk_pixbuf_get_width (pixbuf);
5705
5706 scaled_pixbuf = gdk_pixbuf_scale_simple
5707 (pixbuf,
5708 PREVIEW_IMAGE_WIDTH,
5709 scale * PREVIEW_IMAGE_WIDTH,
5710 GDK_INTERP_HYPER);
5711 g_object_unref (pixbuf);
5712 pixbuf = scaled_pixbuf;
5713 }
5714
5715 gtk_image_set_from_pixbuf (GTK_IMAGE (preview_widget), pixbuf);
5716 } else {
5717 gtk_file_chooser_set_preview_widget_active (icon_chooser, FALSE);
5718 }
5719
5720 g_free (filename);
5721
5722 if (pixbuf != NULL) {
5723 g_object_unref (pixbuf);
5724 }
5725 }
5726
5727 static void
custom_icon_file_chooser_response_cb(GtkDialog * dialog,gint response,FMPropertiesWindow * window)5728 custom_icon_file_chooser_response_cb (GtkDialog *dialog,
5729 gint response,
5730 FMPropertiesWindow *window)
5731 {
5732 char *uri;
5733
5734 switch (response) {
5735 case GTK_RESPONSE_NO:
5736 reset_icon (window);
5737 break;
5738
5739 case GTK_RESPONSE_OK:
5740 uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
5741 set_icon (uri, window);
5742 g_free (uri);
5743 break;
5744
5745 default:
5746 break;
5747 }
5748
5749 gtk_widget_hide (GTK_WIDGET (dialog));
5750 }
5751
5752 static void
select_image_button_callback(GtkWidget * widget,FMPropertiesWindow * window)5753 select_image_button_callback (GtkWidget *widget,
5754 FMPropertiesWindow *window)
5755 {
5756 GtkWidget *dialog;
5757 GList *l;
5758 CajaFile *file;
5759 char *image_path;
5760 gboolean revert_is_sensitive;
5761
5762 g_assert (FM_IS_PROPERTIES_WINDOW (window));
5763
5764 dialog = window->details->icon_chooser;
5765
5766 if (dialog == NULL) {
5767 GtkWidget *preview;
5768 GtkFileFilter *filter;
5769
5770 dialog = eel_file_chooser_dialog_new (_("Select Custom Icon"), GTK_WINDOW (window),
5771 GTK_FILE_CHOOSER_ACTION_OPEN,
5772 "document-revert", GTK_RESPONSE_NO,
5773 "process-stop", GTK_RESPONSE_CANCEL,
5774 "document-open", GTK_RESPONSE_OK,
5775 NULL);
5776 gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), ICONDIR, NULL);
5777 gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), PIXMAPDIR, NULL);
5778 gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
5779
5780 filter = gtk_file_filter_new ();
5781 gtk_file_filter_add_pixbuf_formats (filter);
5782 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter);
5783
5784 preview = gtk_image_new ();
5785 gtk_widget_set_size_request (preview, PREVIEW_IMAGE_WIDTH, -1);
5786 gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (dialog), preview);
5787 gtk_file_chooser_set_use_preview_label (GTK_FILE_CHOOSER (dialog), FALSE);
5788 gtk_file_chooser_set_preview_widget_active (GTK_FILE_CHOOSER (dialog), FALSE);
5789
5790 g_signal_connect (dialog, "update-preview",
5791 G_CALLBACK (update_preview_callback), window);
5792
5793 window->details->icon_chooser = dialog;
5794
5795 g_object_add_weak_pointer (G_OBJECT (dialog),
5796 (gpointer *) &window->details->icon_chooser);
5797 }
5798
5799 /* it's likely that the user wants to pick an icon that is inside a local directory */
5800 if (g_list_length (window->details->original_files) == 1) {
5801 file = CAJA_FILE (window->details->original_files->data);
5802
5803 if (caja_file_is_directory (file)) {
5804 char *uri;
5805
5806 uri = caja_file_get_uri (file);
5807
5808 image_path = g_filename_from_uri (uri, NULL, NULL);
5809 if (image_path != NULL) {
5810 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), image_path);
5811 g_free (image_path);
5812 }
5813
5814 g_free (uri);
5815 }
5816 }
5817
5818 revert_is_sensitive = FALSE;
5819 for (l = window->details->original_files; l != NULL; l = l->next) {
5820 file = CAJA_FILE (l->data);
5821 image_path = caja_file_get_metadata (file, CAJA_METADATA_KEY_CUSTOM_ICON, NULL);
5822 revert_is_sensitive = (image_path != NULL);
5823 g_free (image_path);
5824
5825 if (revert_is_sensitive) {
5826 break;
5827 }
5828 }
5829 gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_NO, revert_is_sensitive);
5830
5831 g_signal_connect (dialog, "response",
5832 G_CALLBACK (custom_icon_file_chooser_response_cb), window);
5833 gtk_widget_show (dialog);
5834 }
5835
5836 static void
fm_properties_window_class_init(FMPropertiesWindowClass * class)5837 fm_properties_window_class_init (FMPropertiesWindowClass *class)
5838 {
5839 GtkBindingSet *binding_set;
5840
5841 G_OBJECT_CLASS (class)->finalize = real_finalize;
5842
5843 GTK_WIDGET_CLASS (class)->destroy = real_destroy;
5844
5845 GTK_DIALOG_CLASS (class)->response = real_response;
5846
5847 binding_set = gtk_binding_set_by_class (class);
5848 gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0,
5849 "close", 0);
5850 }
5851
5852 static void
fm_properties_window_init(FMPropertiesWindow * window)5853 fm_properties_window_init (FMPropertiesWindow *window)
5854 {
5855 window->details = fm_properties_window_get_instance_private (window);
5856 }
5857