fm-properties-window.c 138 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */

/* fm-properties-window.c - window that lets user modify file properties

   Copyright (C) 2000 Eazel, Inc.

   The Gnome Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   The Gnome Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with the Gnome Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

22
   Authors: Darin Adler <darin@bentspoon.com>
23 24 25 26 27
*/

#include <config.h>
#include "fm-properties-window.h"

28
#include "fm-error-reporting.h"
Alexander Larsson's avatar
Alexander Larsson committed
29
#include "libnautilus-private/nautilus-mime-application-chooser.h"
30
#include <eel/eel-accessibility.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
31 32 33 34 35 36 37 38 39
#include <eel/eel-gdk-pixbuf-extensions.h>
#include <eel/eel-glib-extensions.h>
#include <eel/eel-gnome-extensions.h>
#include <eel/eel-gtk-extensions.h>
#include <eel/eel-labeled-image.h>
#include <eel/eel-stock-dialogs.h>
#include <eel/eel-string.h>
#include <eel/eel-vfs-extensions.h>
#include <eel/eel-wrap-table.h>
40
#include <gtk/gtkalignment.h>
41
#include <gtk/gtkcheckbutton.h>
42
#include <gtk/gtkdnd.h>
43
#include <gtk/gtkeditable.h>
44
#include <gtk/gtkentry.h>
45
#include <gtk/gtkfilesel.h>
46
#include <gtk/gtkhbox.h>
Alexander Larsson's avatar
Alexander Larsson committed
47
#include <gtk/gtkhbbox.h>
48
#include <gtk/gtkhseparator.h>
Darin Adler's avatar
Darin Adler committed
49
#include <gtk/gtkimage.h>
50
#include <gtk/gtklabel.h>
51
#include <gtk/gtkmain.h>
52
#include <gtk/gtknotebook.h>
53
#include <gtk/gtkcombobox.h>
54
#include <gtk/gtkscrolledwindow.h>
55
#include <gtk/gtksignal.h>
Alexander Larsson's avatar
Alexander Larsson committed
56
#include <gtk/gtkstock.h>
57
#include <gtk/gtktable.h>
58
#include <gtk/gtkvbox.h>
59 60
#include <gtk/gtktreemodel.h>
#include <gtk/gtkliststore.h>
61
#include <glib/gi18n.h>
62
#include <libgnome/gnome-macros.h>
63
#include <libgnomeui/gnome-dialog.h>
64
#include <libgnomeui/gnome-help.h>
65
#include <libgnomeui/gnome-thumbnail.h>
Darin Adler's avatar
Darin Adler committed
66
#include <libgnomeui/gnome-uidefs.h>
67
#include <libnautilus-extension/nautilus-property-page-provider.h>
68 69 70
#include <libnautilus-private/nautilus-customization-data.h>
#include <libnautilus-private/nautilus-entry.h>
#include <libnautilus-private/nautilus-file-attributes.h>
71
#include <libnautilus-private/nautilus-file-operations.h>
Alexander Larsson's avatar
Alexander Larsson committed
72
#include <libnautilus-private/nautilus-desktop-icon-file.h>
73
#include <libnautilus-private/nautilus-global-preferences.h>
74
#include <libnautilus-private/nautilus-emblem-utils.h>
75 76
#include <libnautilus-private/nautilus-link.h>
#include <libnautilus-private/nautilus-metadata.h>
77
#include <libnautilus-private/nautilus-module.h>
78
#include <libnautilus-private/nautilus-undo-signal-handlers.h>
79
#include <libnautilus-private/nautilus-mime-actions.h>
Alexander Larsson's avatar
Alexander Larsson committed
80
#include <libnautilus-private/nautilus-undo.h>
Darin Adler's avatar
Darin Adler committed
81
#include <string.h>
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
#include <cairo.h>

#if HAVE_SYS_STATVFS_H
#include <sys/statvfs.h>
#endif
#if HAVE_SYS_VFS_H
#include <sys/vfs.h>
#elif HAVE_SYS_MOUNT_H
#if HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#include <sys/mount.h>
#endif

#define USED_FILL_R  0.988235294
#define USED_FILL_G  0.91372549
#define USED_FILL_B  0.309803922

#define USED_STROKE_R  0.929411765
#define USED_STROKE_G  0.831372549
#define USED_STROKE_B  0.0

#define FREE_FILL_R  0.447058824
#define FREE_FILL_G  0.623529412
#define FREE_FILL_B  0.811764706

#define FREE_STROKE_R  0.203921569
#define FREE_STROKE_G  0.396078431
#define FREE_STROKE_B  0.643137255
111

112 113
#define PREVIEW_IMAGE_WIDTH 96

114 115
#define ROW_PAD 6

116
static GHashTable *windows;
117 118 119 120 121
static GHashTable *pending_lists;

struct FMPropertiesWindowDetails {	
	GList *original_files;
	GList *target_files;
122
	
123
	GtkNotebook *notebook;
124
	
125 126
	GtkTable *basic_table;
	GtkTable *permissions_table;
127
	gboolean advanced_permissions;
128

129
	GtkWidget *icon_button;
130
	GtkWidget *icon_image;
131
	GtkWidget *icon_chooser;
132

133 134
	GtkWidget *name_label;
	GtkWidget *name_field;
135
	char *pending_name;
136

137 138 139
	GtkLabel *directory_contents_title_field;
	GtkLabel *directory_contents_value_field;
	guint update_directory_contents_timeout_id;
140
	guint update_files_timeout_id;
141

142 143
	GList *emblem_buttons;
	GHashTable *initial_emblems;
144 145 146 147 148 149 150

	NautilusFile *group_change_file;
	char         *group_change_group;
	unsigned int  group_change_timeout;
	NautilusFile *owner_change_file;
	char         *owner_change_owner;
	unsigned int  owner_change_timeout;
151 152

	GList *permission_buttons;
153
	GList *permission_combos;
154
	GHashTable *initial_permissions;
155
	gboolean has_recursive_apply;
156 157 158 159 160

	GList *value_fields;

	GList *mime_list;

161
	gboolean deep_count_finished;
162 163

	guint total_count;
Alexander Larsson's avatar
Alexander Larsson committed
164
	goffset total_size;
165 166

	guint long_operation_underway;
167 168

 	GList *changed_files;
169 170 171
 	
 	guint64 volume_capacity;
 	guint64 volume_free;
172 173
};

174 175 176 177 178 179 180
enum {
	PERMISSIONS_CHECKBOXES_OWNER_ROW,
	PERMISSIONS_CHECKBOXES_GROUP_ROW,
	PERMISSIONS_CHECKBOXES_OTHERS_ROW,
	PERMISSIONS_CHECKBOXES_ROW_COUNT
};

181 182 183 184 185
enum {
	PERMISSIONS_CHECKBOXES_READ_COLUMN,
	PERMISSIONS_CHECKBOXES_WRITE_COLUMN,
	PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN,
	PERMISSIONS_CHECKBOXES_COLUMN_COUNT
John Sullivan's avatar
John Sullivan committed
186 187
};

188 189 190 191
enum {
	TITLE_COLUMN,
	VALUE_COLUMN,
	COLUMN_COUNT
192 193
};

194
typedef struct {
195 196
	GList *original_files;
	GList *target_files;
197
	GtkWidget *parent_widget;
198 199
	char *pending_key;
	GHashTable *pending_files;
200 201
} StartupData;

202 203 204 205
/* drag and drop definitions */

enum {
	TARGET_URI_LIST,
206 207
	TARGET_GNOME_URI_LIST,
	TARGET_RESET_BACKGROUND
208 209
};

210
static const GtkTargetEntry target_table[] = {
211
	{ "text/uri-list",  0, TARGET_URI_LIST },
212 213
	{ "x-special/gnome-icon-list",  0, TARGET_GNOME_URI_LIST },
	{ "x-special/gnome-reset-background", 0, TARGET_RESET_BACKGROUND }
214 215
};

216
#define DIRECTORY_CONTENTS_UPDATE_INTERVAL	200 /* milliseconds */
217
#define FILES_UPDATE_INTERVAL			200 /* milliseconds */
218 219
#define STANDARD_EMBLEM_HEIGHT			52
#define EMBLEM_LABEL_SPACING			2
220

221 222 223 224 225 226 227 228 229 230
/*
 * A timeout before changes through the user/group combo box will be applied.
 * When quickly changing owner/groups (i.e. by keyboard or scroll wheel),
 * this ensures that the GUI doesn't end up unresponsive.
 *
 * Both combos react on changes by scheduling a new change and unscheduling
 * or cancelling old pending changes.
 */
#define CHOWN_CHGRP_TIMEOUT			300 /* milliseconds */

231 232 233 234 235
static void directory_contents_value_field_update (FMPropertiesWindow *window);
static void file_changed_callback                 (NautilusFile       *file,
						   gpointer            user_data);
static void permission_button_update              (FMPropertiesWindow *window,
						   GtkToggleButton    *button);
236 237
static void permission_combo_update               (FMPropertiesWindow *window,
						   GtkComboBox        *combo);
238 239 240 241 242 243
static void value_field_update                    (FMPropertiesWindow *window,
						   GtkLabel           *field);
static void properties_window_update              (FMPropertiesWindow *window,
						   GList              *files);
static void is_directory_ready_callback           (NautilusFile       *file,
						   gpointer            data);
244 245
static void cancel_group_change_callback          (FMPropertiesWindow *window);
static void cancel_owner_change_callback          (FMPropertiesWindow *window);
246 247 248 249
static void parent_widget_destroyed_callback      (GtkWidget          *widget,
						   gpointer            callback_data);
static void select_image_button_callback          (GtkWidget          *widget,
						   FMPropertiesWindow *properties_window);
250
static void set_icon                              (const char         *icon_path,
251
						   FMPropertiesWindow *properties_window);
252 253 254 255
static void remove_pending                        (StartupData        *data,
						   gboolean            cancel_call_when_ready,
						   gboolean            cancel_timed_wait,
						   gboolean            cancel_destroy_handler);
256
static void append_extension_pages                (FMPropertiesWindow *window);
257

258 259 260 261 262 263 264 265 266
static gboolean name_field_focus_out              (NautilusEntry *name_field,
						   GdkEventFocus *event,
						   gpointer callback_data);
static void name_field_activate                   (NautilusEntry *name_field,
						   gpointer callback_data);
static GtkLabel *attach_ellipsizing_value_label   (GtkTable *table,
						   int row,
						   int column,
						   const char *initial_text);
267 268
						   
static GtkWidget* create_pie_widget 		  (FMPropertiesWindow *window);
269

270 271
G_DEFINE_TYPE (FMPropertiesWindow, fm_properties_window, GTK_TYPE_WINDOW);
#define parent_class fm_properties_window_parent_class 
Darin Adler's avatar
Darin Adler committed
272

273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
static gboolean
is_multi_file_window (FMPropertiesWindow *window)
{
	GList *l;
	int count;
	
	count = 0;
	
	for (l = window->details->original_files; l != NULL; l = l->next) {
		if (!nautilus_file_is_gone (NAUTILUS_FILE (l->data))) {
			count++;
			if (count > 1) {
				return TRUE;
			}	
		}
	}

	return FALSE;
}

293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
static int
get_not_gone_original_file_count (FMPropertiesWindow *window)
{
	GList *l;
	int count;

	count = 0;

	for (l = window->details->original_files; l != NULL; l = l->next) {
		if (!nautilus_file_is_gone (NAUTILUS_FILE (l->data))) {
			count++;
		}
	}

	return count;
}

310 311 312 313 314
static NautilusFile *
get_original_file (FMPropertiesWindow *window) 
{
	g_return_val_if_fail (!is_multi_file_window (window), NULL);

315 316 317 318
	if (window->details->original_files == NULL) {
		return NULL;
	}

319 320 321 322 323 324 325
	return NAUTILUS_FILE (window->details->original_files->data);
}

static NautilusFile *
get_target_file_for_original_file (NautilusFile *file)
{
	NautilusFile *target_file;
Alexander Larsson's avatar
Alexander Larsson committed
326
	GFile *location;
327
	char *uri_to_display;
Alexander Larsson's avatar
Alexander Larsson committed
328
	NautilusDesktopLink *link;
329 330

	target_file = NULL;
Alexander Larsson's avatar
Alexander Larsson committed
331
	if (NAUTILUS_IS_DESKTOP_ICON_FILE (file)) {
Alexander Larsson's avatar
Alexander Larsson committed
332
		link = nautilus_desktop_icon_file_get_link (NAUTILUS_DESKTOP_ICON_FILE (file));
333 334 335

		if (link != NULL) {
			/* map to linked URI for these types of links */
Alexander Larsson's avatar
Alexander Larsson committed
336 337 338 339
			location = nautilus_desktop_link_get_activation_location (link);
			if (location) {
				target_file = nautilus_file_get (location);
				g_object_unref (location);
340 341 342 343
			}
			
			g_object_unref (link);
		}
Alexander Larsson's avatar
Alexander Larsson committed
344 345 346 347 348 349 350 351
        } else {
		uri_to_display = nautilus_file_get_activation_uri (file);
		if (uri_to_display != NULL) {
			target_file = nautilus_file_get_by_uri (uri_to_display);
			g_free (uri_to_display);
		}
	}
	
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
	if (target_file != NULL) {
		return target_file;
	}

	/* Ref passed-in file here since we've decided to use it. */
	nautilus_file_ref (file);
	return file;
}

static NautilusFile *
get_target_file (FMPropertiesWindow *window)
{
	return NAUTILUS_FILE (window->details->target_files->data);
}

367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
static void
add_prompt (GtkVBox *vbox, const char *prompt_text, gboolean pack_at_start)
{
	GtkWidget *prompt;

	prompt = gtk_label_new (prompt_text);
   	gtk_label_set_justify (GTK_LABEL (prompt), GTK_JUSTIFY_LEFT);
	gtk_label_set_line_wrap (GTK_LABEL (prompt), TRUE);
	gtk_widget_show (prompt);
	if (pack_at_start) {
		gtk_box_pack_start (GTK_BOX (vbox), prompt, FALSE, FALSE, 0);
	} else {
		gtk_box_pack_end (GTK_BOX (vbox), prompt, FALSE, FALSE, 0);
	}
}

static void
add_prompt_and_separator (GtkVBox *vbox, const char *prompt_text)
{
	GtkWidget *separator_line;

	add_prompt (vbox, prompt_text, FALSE);

John Sullivan's avatar
John Sullivan committed
390
 	separator_line = gtk_hseparator_new ();
391
  	gtk_widget_show (separator_line);
392
  	gtk_box_pack_end (GTK_BOX (vbox), separator_line, TRUE, TRUE, 2*ROW_PAD);
393
}
394

395
static GdkPixbuf *
396
get_pixbuf_for_properties_window (FMPropertiesWindow *window)
397
{
398
	GdkPixbuf *pixbuf;
Alexander Larsson's avatar
Alexander Larsson committed
399
	NautilusIconInfo *icon, *new_icon;
400
	GList *l;
401
	
402 403 404 405 406 407 408
	icon = NULL;
	for (l = window->details->original_files; l != NULL; l = l->next) {
		NautilusFile *file;
		
		file = NAUTILUS_FILE (l->data);
		
		if (!icon) {
Alexander Larsson's avatar
Alexander Larsson committed
409
			icon = nautilus_file_get_icon (file, NAUTILUS_ICON_SIZE_STANDARD, NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS | NAUTILUS_FILE_ICON_FLAGS_IGNORE_VISITING);
410
		} else {
Alexander Larsson's avatar
Alexander Larsson committed
411 412 413 414
			new_icon = nautilus_file_get_icon (file, NAUTILUS_ICON_SIZE_STANDARD, NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS | NAUTILUS_FILE_ICON_FLAGS_IGNORE_VISITING);
			if (!new_icon || new_icon != icon) {
				g_object_unref (icon);
				g_object_unref (new_icon);
415 416 417
				icon = NULL;
				break;
			}
Alexander Larsson's avatar
Alexander Larsson committed
418
			g_object_unref (new_icon);
419
		}
420
	}
421 422

	if (!icon) {
Alexander Larsson's avatar
Alexander Larsson committed
423
		icon = nautilus_icon_info_lookup_from_name ("text-x-generic", NAUTILUS_ICON_SIZE_STANDARD);
424 425
	}
	
Alexander Larsson's avatar
Alexander Larsson committed
426 427
	pixbuf = nautilus_icon_info_get_pixbuf_at_size (icon, NAUTILUS_ICON_SIZE_STANDARD);
	g_object_unref (icon);
428 429

	return pixbuf;
430 431
}

432

433
static void
Darin Adler's avatar
Darin Adler committed
434
update_properties_window_icon (GtkImage *image)
435
{
436
	GdkPixbuf	*pixbuf;
437
	FMPropertiesWindow *window;
438

439
	window = g_object_get_data (G_OBJECT (image), "properties_window");
440
	
441
	pixbuf = get_pixbuf_for_properties_window (window);
442

Darin Adler's avatar
Darin Adler committed
443
	gtk_image_set_from_pixbuf (image, pixbuf);
444 445

	gtk_window_set_icon (GTK_WINDOW (window), pixbuf);
446
	
447
	g_object_unref (pixbuf);
448 449
}

450 451 452 453 454 455 456
/* utility to test if a uri refers to a local image */
static gboolean
uri_is_local_image (const char *uri)
{
	GdkPixbuf *pixbuf;
	char *image_path;
	
Alexander Larsson's avatar
Alexander Larsson committed
457
	image_path = g_filename_from_uri (uri, NULL, NULL);
458 459 460 461
	if (image_path == NULL) {
		return FALSE;
	}

462
	pixbuf = gdk_pixbuf_new_from_file (image_path, NULL);
463 464 465 466 467
	g_free (image_path);
	
	if (pixbuf == NULL) {
		return FALSE;
	}
468
	g_object_unref (pixbuf);
469 470 471
	return TRUE;
}

472

473 474 475
static void
reset_icon (FMPropertiesWindow *properties_window)
{
476 477 478 479 480 481 482 483 484 485 486 487 488 489
	GList *l;

	for (l = properties_window->details->original_files; l != NULL; l = l->next) {
		NautilusFile *file;
		
		file = NAUTILUS_FILE (l->data);
		
		nautilus_file_set_metadata (file,
					    NAUTILUS_METADATA_KEY_ICON_SCALE,
					    NULL, NULL);
		nautilus_file_set_metadata (file,
					    NAUTILUS_METADATA_KEY_CUSTOM_ICON,
					    NULL, NULL);
	}
490 491
}

492

493 494 495 496 497 498 499 500
static void  
fm_properties_window_drag_data_received (GtkWidget *widget, GdkDragContext *context,
					 int x, int y,
					 GtkSelectionData *selection_data,
					 guint info, guint time)
{
	char **uris;
	gboolean exactly_one;
Darin Adler's avatar
Darin Adler committed
501
	GtkImage *image;
502 503
 	GtkWindow *window; 

Darin Adler's avatar
Darin Adler committed
504
	image = GTK_IMAGE (widget);
505 506
 	window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (image)));

507 508 509 510 511 512 513 514 515
	if (info == TARGET_RESET_BACKGROUND) {
		reset_icon (FM_PROPERTIES_WINDOW (window));
		
		return;
	}
	
	uris = g_strsplit (selection_data->data, "\r\n", 0);
	exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0');

516 517

	if (!exactly_one) {
Darin Adler's avatar
Darin Adler committed
518
		eel_show_error_dialog
519 520
			(_("You can't assign more than one custom icon at a time!"),
			 _("Please drag just one image to set a custom icon."), 
Darin Adler's avatar
Darin Adler committed
521
			 window);
522 523
	} else {		
		if (uri_is_local_image (uris[0])) {			
524
			set_icon (uris[0], FM_PROPERTIES_WINDOW (window));
Alexander Larsson's avatar
Alexander Larsson committed
525 526 527 528 529
		} else {
			GFile *f;

			f = g_file_new_for_uri (uris[0]);
			if (!g_file_is_native (f)) {
Darin Adler's avatar
Darin Adler committed
530
				eel_show_error_dialog
531 532
					(_("The file that you dropped is not local."),
					 _("You can only use local images as custom icons."), 
Darin Adler's avatar
Darin Adler committed
533
					 window);
534 535
				
			} else {
Darin Adler's avatar
Darin Adler committed
536
				eel_show_error_dialog
537 538
					(_("The file that you dropped is not an image."),
					 _("You can only use local images as custom icons."),
Darin Adler's avatar
Darin Adler committed
539
					 window);
540
			}
Alexander Larsson's avatar
Alexander Larsson committed
541
			g_object_unref (f);
542 543 544 545 546
		}		
	}
	g_strfreev (uris);
}

547
static GtkWidget *
548
create_image_widget (FMPropertiesWindow *window,
549
		     gboolean is_customizable)
550
{
551 552
 	GtkWidget *button;
	GtkWidget *image;
553
	GdkPixbuf *pixbuf;
554
	
555
	pixbuf = get_pixbuf_for_properties_window (window);
556
	
557

Darin Adler's avatar
Darin Adler committed
558
	image = gtk_image_new ();
559 560 561 562 563 564
	gtk_widget_show (image);

	button = NULL;
	if (is_customizable) {
		button = gtk_button_new ();
		gtk_container_add (GTK_CONTAINER (button), image);
565

566 567 568 569 570
		/* prepare the image to receive dropped objects to assign custom images */
		gtk_drag_dest_set (GTK_WIDGET (image),
				   GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, 
				   target_table, G_N_ELEMENTS (target_table),
				   GDK_ACTION_COPY | GDK_ACTION_MOVE);
571

572 573
		g_signal_connect (image, "drag_data_received",
				  G_CALLBACK (fm_properties_window_drag_data_received), NULL);
574 575
		g_signal_connect (button, "clicked",
				  G_CALLBACK (select_image_button_callback), window);
576
	}
577

Darin Adler's avatar
Darin Adler committed
578
	gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf);
579

580
	g_object_unref (pixbuf);
581

582
	g_object_set_data (G_OBJECT (image), "properties_window", window);
583

584 585 586 587
	window->details->icon_image = image;
	window->details->icon_button = button;

	return button != NULL ? button : image;
588 589
}

590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666
static void
set_name_field (FMPropertiesWindow *window, const gchar *original_name,
					 const gchar *name)
{
	gboolean new_widget;
	gboolean use_label;

	/* There are four cases here:
	 * 1) Changing the text of a label
	 * 2) Changing the text of an entry
	 * 3) Creating label (potentially replacing entry)
	 * 4) Creating entry (potentially replacing label)
	 */
	use_label = is_multi_file_window (window) || !nautilus_file_can_rename (get_original_file (window));
	new_widget = !window->details->name_field || (use_label ? NAUTILUS_IS_ENTRY (window->details->name_field) : GTK_IS_LABEL (window->details->name_field));

	if (new_widget) {
		if (window->details->name_field) {
			gtk_widget_destroy (window->details->name_field);
		}

		if (use_label) {
			window->details->name_field = GTK_WIDGET (attach_ellipsizing_value_label (window->details->basic_table, 0, VALUE_COLUMN, name));		
		} else {
			window->details->name_field = nautilus_entry_new ();
			gtk_entry_set_text (GTK_ENTRY (window->details->name_field), name);
			gtk_widget_show (window->details->name_field);
			gtk_table_attach (window->details->basic_table,
					  window->details->name_field,
					  VALUE_COLUMN, 
					  VALUE_COLUMN + 1,
					  0, 1,
					  GTK_FILL, 0,
					  0, 0);
			gtk_label_set_mnemonic_widget (GTK_LABEL (window->details->name_label), window->details->name_field);
			
			/* FIXME bugzilla.gnome.org 42151:
			 * With this (and one place elsewhere in this file, not sure which is the
			 * trouble-causer) code in place, bug 2151 happens (crash on quit). Since
			 * we've removed Undo from Nautilus for now, I'm just ifdeffing out this
			 * code rather than trying to fix 2151 now. Note that it might be possible
			 * to fix 2151 without making Undo actually work, it's just not worth the
			 * trouble.
			 */
#ifdef UNDO_ENABLED
			/* Set up name field for undo */
			nautilus_undo_set_up_nautilus_entry_for_undo ( NAUTILUS_ENTRY (window->details->name_field));
			nautilus_undo_editable_set_undo_key (GTK_EDITABLE (window->details->name_field), TRUE);
#endif

			g_signal_connect_object (window->details->name_field, "focus_out_event",
						 G_CALLBACK (name_field_focus_out), window, 0);
			g_signal_connect_object (window->details->name_field, "activate",
						 G_CALLBACK (name_field_activate), window, 0);
		}

		gtk_widget_show (window->details->name_field);
	}
	/* Only replace text if the file's name has changed. */ 
	else if (original_name == NULL || strcmp (original_name, name) != 0) {
		
		if (use_label) {
			gtk_label_set_text (GTK_LABEL (window->details->name_field), name);
		} else {
			/* Only reset the text if it's different from what is
			 * currently showing. This causes minimal ripples (e.g.
			 * selection change).
			 */
			gchar *displayed_name = gtk_editable_get_chars (GTK_EDITABLE (window->details->name_field), 0, -1);
			if (strcmp (displayed_name, name) != 0) {
				gtk_entry_set_text (GTK_ENTRY (window->details->name_field), name);
			}
			g_free (displayed_name);
		}
	}
}

667
static void
668
update_name_field (FMPropertiesWindow *window)
669 670
{
	NautilusFile *file;
671
	
672 673 674 675 676 677 678 679
	if (is_multi_file_window (window)) {
		/* Multifile property dialog, show all names */
		GString *str;
		char *name;
		gboolean first;
		GList *l;
		
		str = g_string_new ("");
680

681
		first = TRUE;
682

683
		for (l = window->details->target_files; l != NULL; l = l->next) {
684
			file = NAUTILUS_FILE (l->data);
685

686 687 688 689 690 691 692 693 694 695
			if (!nautilus_file_is_gone (file)) {
				if (!first) {
					g_string_append (str, ", ");
				} 
				first = FALSE;
				
				name = nautilus_file_get_display_name (file);
				g_string_append (str, name);
				g_free (name);
			}
696
		}
697
		set_name_field (window, NULL, str->str);
698
		g_string_free (str, TRUE);
699
	} else {
700 701
		const char *original_name = NULL;
		char *current_name;
702

703
		file = get_original_file (window);
704

705
		if (file == NULL || nautilus_file_is_gone (file)) {
706 707 708
			current_name = g_strdup ("");
		} else {
			current_name = nautilus_file_get_display_name (file);
709 710 711 712 713 714 715
		}

		/* If the file name has changed since the original name was stored,
		 * update the text in the text field, possibly (deliberately) clobbering
		 * an edit in progress. If the name hasn't changed (but some other
		 * aspect of the file might have), then don't clobber changes.
		 */
716 717 718 719 720 721
		if (window->details->name_field) {
			original_name = (const char *) g_object_get_data (G_OBJECT (window->details->name_field), "original_name");
		}

		set_name_field (window, original_name, current_name);

722 723 724 725 726 727 728 729 730 731
		if (original_name == NULL || 
		    eel_strcmp (original_name, current_name) != 0) {
			g_object_set_data_full (G_OBJECT (window->details->name_field),
						"original_name",
						current_name,
						g_free);
		} else {
			g_free (current_name);
		}
	}
732 733
}

734 735 736 737 738 739
static void
name_field_restore_original_name (NautilusEntry *name_field)
{
	const char *original_name;
	char *displayed_name;

740
	original_name = (const char *) g_object_get_data (G_OBJECT (name_field),
Michael Meeks's avatar
Michael Meeks committed
741
							  "original_name");
742 743 744 745 746

	if (!original_name) {
		return;
	}

747 748 749 750 751
	displayed_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1);

	if (strcmp (original_name, displayed_name) != 0) {
		gtk_entry_set_text (GTK_ENTRY (name_field), original_name);
	}
752
	nautilus_entry_select_all (name_field);
753 754 755 756

	g_free (displayed_name);
}

757
static void
Alexander Larsson's avatar
Alexander Larsson committed
758
rename_callback (NautilusFile *file, GFile *res_loc, GError *error, gpointer callback_data)
759
{
760
	FMPropertiesWindow *window;
761 762
	char *new_name;

763
	window = FM_PROPERTIES_WINDOW (callback_data);
764 765

	/* Complain to user if rename failed. */
Alexander Larsson's avatar
Alexander Larsson committed
766
	if (error != NULL) {
767 768 769
		new_name = window->details->pending_name;
		fm_report_error_renaming_file (file, 
					       window->details->pending_name, 
Alexander Larsson's avatar
Alexander Larsson committed
770
					       error,
771
					       GTK_WINDOW (window));
Michael Meeks's avatar
Michael Meeks committed
772
		if (window->details->name_field != NULL) {
773
			name_field_restore_original_name (NAUTILUS_ENTRY (window->details->name_field));
Michael Meeks's avatar
Michael Meeks committed
774
		}
775 776
	}

777
	g_object_unref (window);
778 779 780 781 782 783 784
}

static void
set_pending_name (FMPropertiesWindow *window, const char *name)
{
	g_free (window->details->pending_name);
	window->details->pending_name = g_strdup (name);
785 786
}

787
static void
788
name_field_done_editing (NautilusEntry *name_field, FMPropertiesWindow *window)
789 790
{
	NautilusFile *file;
791
	char *new_name;
792
	const char *original_name;
793
	
794
	g_return_if_fail (NAUTILUS_IS_ENTRY (name_field));
795

796 797 798 799
	/* Don't apply if the dialog has more than one file */
	if (is_multi_file_window (window)) {
		return;
	}	
800

801
	file = get_original_file (window);
802

803 804 805
	/* This gets called when the window is closed, which might be
	 * caused by the file having been deleted.
	 */
806
	if (file == NULL || nautilus_file_is_gone  (file)) {
807 808 809
		return;
	}

810
	new_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1);
811

812 813
	/* Special case: silently revert text if new text is empty. */
	if (strlen (new_name) == 0) {
814
		name_field_restore_original_name (NAUTILUS_ENTRY (name_field));
815
	} else {
816 817 818 819 820 821 822 823 824 825 826
		original_name = (const char *) g_object_get_data (G_OBJECT (window->details->name_field),
								  "original_name");
		/* Don't rename if not changed since we read the display name.
		   This is needed so that we don't save the display name to the
		   file when nothing is changed */
		if (strcmp (new_name, original_name) != 0) {		
			set_pending_name (window, new_name);
			g_object_ref (window);
			nautilus_file_rename (file, new_name,
					      rename_callback, window);
		}
827
	}
828 829

	g_free (new_name);
830 831 832 833 834
}

static gboolean
name_field_focus_out (NautilusEntry *name_field,
		      GdkEventFocus *event,
835
		      gpointer callback_data)
836
{
837 838
	g_assert (FM_IS_PROPERTIES_WINDOW (callback_data));

839
	if (GTK_WIDGET_SENSITIVE (name_field)) {
840
		name_field_done_editing (name_field, FM_PROPERTIES_WINDOW (callback_data));
841
	}
842

843
	return FALSE;
844 845 846
}

static void
847
name_field_activate (NautilusEntry *name_field, gpointer callback_data)
848 849
{
	g_assert (NAUTILUS_IS_ENTRY (name_field));
850
	g_assert (FM_IS_PROPERTIES_WINDOW (callback_data));
851 852

	/* Accept changes. */
853
	name_field_done_editing (name_field, FM_PROPERTIES_WINDOW (callback_data));
854 855 856 857

	nautilus_entry_select_all_at_idle (name_field);
}

858 859
static gboolean
file_has_keyword (NautilusFile *file, const char *keyword)
860 861 862
{
	GList *keywords, *word;

863 864 865 866 867 868
	keywords = nautilus_file_get_keywords (file);
	word = g_list_find_custom (keywords, keyword, (GCompareFunc) strcmp);
	eel_g_list_free_deep (keywords);
	
	return (word != NULL);
}
869

870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891
static void
get_initial_emblem_state (FMPropertiesWindow *window,
			  const char *name,
			  GList **on,
			  GList **off)
{
	GList *l;
	
	*on = NULL;
	*off = NULL;
	
	for (l = window->details->original_files; l != NULL; l = l->next) {
		GList *initial_emblems;
		
		initial_emblems = g_hash_table_lookup (window->details->initial_emblems,
						       l->data);
		
		if (g_list_find_custom (initial_emblems, name, (GCompareFunc) strcmp)) {
			*on = g_list_prepend (*on, l->data);
		} else {
			*off = g_list_prepend (*off, l->data);
		}
892 893 894 895
	}
}

static void
896 897
emblem_button_toggled (GtkToggleButton *button,
		       FMPropertiesWindow *window)
898
{
899 900 901
	GList *l;
	GList *keywords;
	GList *word;
902
	char *name;
903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
	GList *files_on;
	GList *files_off;

	name = g_object_get_data (G_OBJECT (button), "nautilus_emblem_name");

	files_on = NULL;
	files_off = NULL;
	if (gtk_toggle_button_get_active (button)
	    && !gtk_toggle_button_get_inconsistent (button)) {
		/* Go to the initial state unless the initial state was 
		   consistent */
		get_initial_emblem_state (window, name, 
					  &files_on, &files_off);
		
		if (!(files_on && files_off)) {
			g_list_free (files_on);
			g_list_free (files_off);
			files_on = g_list_copy (window->details->original_files);
			files_off = NULL;
		}
	} else if (gtk_toggle_button_get_inconsistent (button)
		   && !gtk_toggle_button_get_active (button)) {
		files_on = g_list_copy (window->details->original_files);
		files_off = NULL;
	} else {
		files_off = g_list_copy (window->details->original_files);
		files_on = NULL;
930
	}
931 932 933 934 935 936 937
	
	g_signal_handlers_block_by_func (G_OBJECT (button), 
					 G_CALLBACK (emblem_button_toggled),
					 window);
	
	gtk_toggle_button_set_active (button, files_on != NULL);
	gtk_toggle_button_set_inconsistent (button, files_on && files_off);
938

939 940 941 942 943 944 945 946 947 948 949 950 951
	g_signal_handlers_unblock_by_func (G_OBJECT (button), 
					   G_CALLBACK (emblem_button_toggled),
					   window);

	for (l = files_on; l != NULL; l = l->next) {
		NautilusFile *file;
		
		file = NAUTILUS_FILE (l->data);
		
		keywords = nautilus_file_get_keywords (file);
		
		word = g_list_find_custom (keywords, name,  (GCompareFunc)strcmp);
		if (!word) {
952
			keywords = g_list_prepend (keywords, g_strdup (name));
953
		}
954 955 956 957 958 959 960 961 962 963 964 965 966
		nautilus_file_set_keywords (file, keywords);
		eel_g_list_free_deep (keywords);
	}
	
	for (l = files_off; l != NULL; l = l->next) {
		NautilusFile *file;
		
		file = NAUTILUS_FILE (l->data);

		keywords = nautilus_file_get_keywords (file);
		
		word = g_list_find_custom (keywords, name,  (GCompareFunc)strcmp);
		if (word) {
967
			keywords = g_list_remove_link (keywords, word);
Ramiro Estrugo's avatar
Ramiro Estrugo committed
968
			eel_g_list_free_deep (word);
969
		}
970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003
		nautilus_file_set_keywords (file, keywords);
		eel_g_list_free_deep (keywords);
	}	

	g_list_free (files_on);
	g_list_free (files_off);	
}

static void
emblem_button_update (FMPropertiesWindow *window,
			GtkToggleButton *button)
{
	GList *l;
	char *name;
	gboolean all_set;
	gboolean all_unset;

	name = g_object_get_data (G_OBJECT (button), "nautilus_emblem_name");

	all_set = TRUE;
	all_unset = TRUE;
	for (l = window->details->original_files; l != NULL; l = l->next) {
		gboolean has_keyword;
		NautilusFile *file;

		file = NAUTILUS_FILE (l->data);
		
		has_keyword = file_has_keyword (file, name);

		if (has_keyword) {
			all_unset = FALSE;
		} else {
			all_set = FALSE;
		}
1004
	}
1005 1006