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