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