nautilus-object-window.c 52.1 KB
Newer Older
Darin Adler's avatar
Darin Adler committed
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3
4
5

/*
 *  Nautilus
 *
Elliot Lee's avatar
Elliot Lee committed
6
 *  Copyright (C) 1999, 2000 Red Hat, Inc.
7
 *  Copyright (C) 1999, 2000 Eazel, Inc.
8
 *
9
 *  Nautilus is free software; you can redistribute it and/or
10
11
12
13
 *  modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
14
 *  Nautilus is distributed in the hope that it will be useful,
15
16
17
18
19
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public
20
 *  License along with this program; if not, write to the Free
21
22
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
23
24
 *  Authors: Elliot Lee <sopwith@redhat.com>
 *  	     John Sullivan <sullivan@eazel.com>
25
26
 *
 */
27

28
/* nautilus-window.c: Implementation of the main window object */
29

30
#include <config.h>
31
#include "nautilus-window-private.h"
32

33
#include "nautilus-main.h"
34
#include "nautilus-application.h"
35
#include "nautilus-bookmarks-window.h"
36
#include "nautilus-sidebar.h"
37
#include "nautilus-signaller.h"
38
#include "nautilus-switchable-navigation-bar.h"
39
#include "nautilus-window-manage-views.h"
Darin Adler's avatar
Darin Adler committed
40
#include "nautilus-window-service-ui.h"
41
#include "nautilus-zoom-control.h"
42
#include <bonobo/bonobo-ui-util.h>
43
#include <bonobo/bonobo-exception.h>
44
#include <ctype.h>
45
#include <gdk-pixbuf/gdk-pixbuf.h>
46
47
#include <gtk/gtkmain.h>
#include <gtk/gtkmenuitem.h>
Mike Engber's avatar
Mike Engber committed
48
#include <gtk/gtkmenubar.h>
49
50
51
52
53
54
55
#include <gtk/gtkoptionmenu.h>
#include <gtk/gtktogglebutton.h>
#include <gtk/gtkvbox.h>
#include <libgnome/gnome-i18n.h>
#include <libgnomeui/gnome-geometry.h>
#include <libgnomeui/gnome-messagebox.h>
#include <libgnomeui/gnome-uidefs.h>
56
#include <libgnomevfs/gnome-vfs-uri.h>
Darin Adler's avatar
Darin Adler committed
57
#include <libgnomevfs/gnome-vfs-utils.h>
Darin Adler's avatar
Darin Adler committed
58
#include <libnautilus-extensions/nautilus-bonobo-extensions.h>
59
#include <libnautilus-extensions/nautilus-drag-window.h>
60
#include <libnautilus-extensions/nautilus-file-utilities.h>
61
#include <libnautilus-extensions/nautilus-gdk-extensions.h>
62
#include <libnautilus-extensions/nautilus-gdk-pixbuf-extensions.h>
63
#include <libnautilus-extensions/nautilus-generous-bin.h>
64
#include <libnautilus-extensions/nautilus-global-preferences.h>
65
#include <libnautilus-extensions/nautilus-gtk-extensions.h>
66
67
#include <libnautilus-extensions/nautilus-gtk-macros.h>
#include <libnautilus-extensions/nautilus-horizontal-splitter.h>
68
#include <libnautilus-extensions/nautilus-icon-factory.h>
69
#include <libnautilus-extensions/nautilus-metadata.h>
70
#include <libnautilus-extensions/nautilus-mime-actions.h>
71
#include <libnautilus-extensions/nautilus-program-choosing.h>
72
#include <libnautilus-extensions/nautilus-string.h>
73
#include <libnautilus/nautilus-bonobo-ui.h>
74
#include <libnautilus/nautilus-clipboard.h>
75
#include <libnautilus/nautilus-undo.h>
76
#include <math.h>
77
78
79
#include <sys/time.h>
#include <X11/Xatom.h>
#include <gdk/gdkx.h>
80

81
82
/* FIXME bugzilla.eazel.com 1243: 
 * We should use inheritance instead of these special cases
Darin Adler's avatar
Darin Adler committed
83
84
85
 * for the desktop window.
 */
#include "nautilus-desktop-window.h"
86

87
/* Milliseconds */
88
#define STATUS_BAR_CLEAR_TIMEOUT 10000
89

90
/* dock items */
91
#define LOCATION_BAR_PATH	"/Location Bar"
92
#define TOOLBAR_PATH           "/Toolbar"
93
#define STATUS_BAR_PATH         "/status"
94
#define MENU_BAR_PATH           "/menu"
95

96
97
98
99
/* FIXME: bugzilla.eazel.com 3590
 * This shouldn't need to exist. See bug report for details.
 */
#define NAUTILUS_COMMAND_TOGGLE_FIND_MODE_WITH_STATE	"/commands/Toggle Find Mode With State"
100

Elliot Lee's avatar
Elliot Lee committed
101
enum {
Darin Adler's avatar
Darin Adler committed
102
103
	ARG_0,
	ARG_APP_ID,
104
	ARG_APP
Elliot Lee's avatar
Elliot Lee committed
105
106
};

107
static GList *history_list = NULL;
108

109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
static void nautilus_window_initialize_class       (NautilusWindowClass *klass);
static void nautilus_window_initialize             (NautilusWindow      *window);
static void nautilus_window_destroy                (GtkObject           *object);
static void nautilus_window_set_arg                (GtkObject           *object,
						    GtkArg              *arg,
						    guint                arg_id);
static void nautilus_window_get_arg                (GtkObject           *object,
						    GtkArg              *arg,
						    guint                arg_id);
static void nautilus_window_size_request           (GtkWidget           *widget,
						    GtkRequisition      *requisition);
static void nautilus_window_realize                (GtkWidget           *widget);
static void update_sidebar_panels_from_preferences (NautilusWindow      *window);
static void sidebar_panels_changed_callback        (gpointer             user_data);
static void nautilus_window_show                   (GtkWidget           *widget);
static void cancel_view_as_callback                (NautilusWindow      *window);
125
static void real_add_current_location_to_history_list (NautilusWindow   *window);
126
127
128

NAUTILUS_DEFINE_CLASS_BOILERPLATE (NautilusWindow,
				   nautilus_window,
129
				   BONOBO_TYPE_WINDOW)
130

Elliot Lee's avatar
Elliot Lee committed
131
static void
132
nautilus_window_initialize_class (NautilusWindowClass *klass)
Elliot Lee's avatar
Elliot Lee committed
133
{
Darin Adler's avatar
Darin Adler committed
134
135
136
	GtkObjectClass *object_class;
	GtkWidgetClass *widget_class;
	
137
138
139
	object_class = (GtkObjectClass *) klass;
	widget_class = (GtkWidgetClass *) klass;

140
	object_class->destroy = nautilus_window_destroy;
Darin Adler's avatar
Darin Adler committed
141
142
143
	object_class->get_arg = nautilus_window_get_arg;
	object_class->set_arg = nautilus_window_set_arg;
	
144
	widget_class->show = nautilus_window_show;
Darin Adler's avatar
Darin Adler committed
145
146
147
	
	gtk_object_add_arg_type ("NautilusWindow::app_id",
				 GTK_TYPE_STRING,
148
				 GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
Darin Adler's avatar
Darin Adler committed
149
150
151
				 ARG_APP_ID);
	gtk_object_add_arg_type ("NautilusWindow::app",
				 GTK_TYPE_OBJECT,
152
				 GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
Darin Adler's avatar
Darin Adler committed
153
154
155
				 ARG_APP);
	
	widget_class->realize = nautilus_window_realize;
156
157
158
159
	widget_class->size_request = nautilus_window_size_request;

	klass->add_current_location_to_history_list
		= real_add_current_location_to_history_list;
Elliot Lee's avatar
Elliot Lee committed
160
161
}

162
static void
163
nautilus_window_initialize (NautilusWindow *window)
164
{
165
166
	window->details = g_new0 (NautilusWindowDetails, 1);

Darin Adler's avatar
Darin Adler committed
167
168
	gtk_quit_add_destroy (1, GTK_OBJECT (window));
	
169
	/* Keep track of any sidebar panel changes */
Darin Adler's avatar
Darin Adler committed
170
171
172
	nautilus_preferences_add_callback (NAUTILUS_PREFERENCES_SIDEBAR_PANELS_NAMESPACE,
					   sidebar_panels_changed_callback,
					   window);
173
174
175

	/* Keep the main event loop alive as long as the window exists */
	nautilus_main_event_loop_register (GTK_OBJECT (window));
176
177
}

178
static gboolean
179
nautilus_window_clear_status (gpointer callback_data)
180
{
181
182
183
	NautilusWindow *window;

	window = NAUTILUS_WINDOW (callback_data);
184
185
186
187
188
189

	/* FIXME bugzilla.eazel.com 3597:
	 * Should pass "" or NULL here. This didn't work, then did, now doesn't again.
	 * When this is fixed in Bonobo we should change this line.
	 */
	bonobo_ui_component_set_status (window->details->shell_ui, " ", NULL);
190
	window->status_bar_clear_id = 0;
Darin Adler's avatar
Darin Adler committed
191
	return FALSE;
192
193
}

194
void
195
nautilus_window_set_status (NautilusWindow *window, const char *text)
196
{
197
198
199
	if (window->status_bar_clear_id != 0) {
		g_source_remove (window->status_bar_clear_id);
	}
Darin Adler's avatar
Darin Adler committed
200
	
201
202
	if (text != NULL && text[0] != '\0') {
		bonobo_ui_component_set_status (window->details->shell_ui, text, NULL);
Darin Adler's avatar
Darin Adler committed
203
		window->status_bar_clear_id = g_timeout_add
204
			(STATUS_BAR_CLEAR_TIMEOUT, nautilus_window_clear_status, window);
205
	} else {
206
		nautilus_window_clear_status (window);
207
208
		window->status_bar_clear_id = 0;
	}
209
210
}

211
void
212
nautilus_window_go_to (NautilusWindow *window, const char *uri)
213
{
214
	nautilus_window_open_location (window, uri);
215
216
}

217
218
219
220
221
222
223
224
char *
nautilus_window_get_location (NautilusWindow *window)
{
	g_return_val_if_fail (NAUTILUS_IS_WINDOW (window), NULL);

	return g_strdup (window->details->location);
}

225
static void
226
227
228
go_to_callback (GtkWidget *widget,
		const char *uri,
		GtkWidget *window)
229
{
230
	nautilus_window_go_to (NAUTILUS_WINDOW (window), uri);
231
232
}

233
static void
234
navigation_bar_mode_changed_callback (GtkWidget *widget,
235
				      NautilusSwitchableNavigationBarMode mode,
236
				      NautilusWindow *window)
237
{
238
	nautilus_window_update_find_menu_item (window);
239
	
240
	window->details->updating_bonobo_state = TRUE;
241

242
	g_assert (mode == NAUTILUS_SWITCHABLE_NAVIGATION_BAR_MODE_LOCATION 
243
		  || mode == NAUTILUS_SWITCHABLE_NAVIGATION_BAR_MODE_SEARCH);
244

245
246
247
248
249
250
	/* FIXME: bugzilla.eazel.com 3590:
	 * We shouldn't need a separate command for the toggle button and menu item.
	 * This is a Bonobo design flaw, explained in the bug report.
	 */
	nautilus_bonobo_set_toggle_state (window->details->shell_ui,
					  NAUTILUS_COMMAND_TOGGLE_FIND_MODE_WITH_STATE,
251
					  mode == NAUTILUS_SWITCHABLE_NAVIGATION_BAR_MODE_SEARCH);
252
	
253
	window->details->updating_bonobo_state = FALSE;
254
255
}

256
257
void
nautilus_window_zoom_in (NautilusWindow *window)
258
{
Darin Adler's avatar
Darin Adler committed
259
260
261
	if (window->content_view != NULL) {
		nautilus_view_frame_zoom_in (window->content_view);
	}
262
263
}

264
265
void
nautilus_window_zoom_to_level (NautilusWindow *window, double level)
266
267
{
	if (window->content_view != NULL) {
268
		nautilus_view_frame_set_zoom_level (window->content_view, level);
269
270
271
	}
}

272
273
void
nautilus_window_zoom_out (NautilusWindow *window)
274
{
Darin Adler's avatar
Darin Adler committed
275
276
277
	if (window->content_view != NULL) {
		nautilus_view_frame_zoom_out (window->content_view);
	}
278
279
}

280
281
void
nautilus_window_zoom_to_fit (NautilusWindow *window)
282
283
{
	if (window->content_view != NULL) {
284
		nautilus_view_frame_zoom_to_fit (window->content_view);
285
286
287
	}
}

288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
/* Code should never force the window taller than this size.
 * (The user can still stretch the window taller if desired).
 */
static guint
get_max_forced_height (void)
{
	return (gdk_screen_height () * 90) / 100;
}

/* Code should never force the window wider than this size.
 * (The user can still stretch the window wider if desired).
 */
static guint
get_max_forced_width (void)
{
	return (gdk_screen_width () * 90) / 100;
}

static void
set_initial_window_geometry (NautilusWindow *window)
{
	guint max_width_for_screen, max_height_for_screen;

	/* Don't let GTK determine the minimum size
	 * automatically. It will insist that the window be
	 * really wide based on some misguided notion about
	 * the content view area. Also, it might start the
	 * window wider (or taller) than the screen, which
	 * is evil. So we choose semi-arbitrary initial and
	 * minimum widths instead of letting GTK decide.
	 */

	max_width_for_screen = get_max_forced_width ();
	max_height_for_screen = get_max_forced_height ();

	gtk_widget_set_usize (GTK_WIDGET (window), 
			      MIN (NAUTILUS_WINDOW_MIN_WIDTH, 
			           max_width_for_screen),
			      MIN (NAUTILUS_WINDOW_MIN_HEIGHT, 
			           max_height_for_screen));

	gtk_window_set_default_size (GTK_WINDOW (window), 
				     MIN (NAUTILUS_WINDOW_DEFAULT_WIDTH, 
				          max_width_for_screen), 
				     MIN (NAUTILUS_WINDOW_DEFAULT_HEIGHT, 
				          max_height_for_screen));

	gtk_window_set_policy (GTK_WINDOW (window), 
			       FALSE,  /* don't let window be stretched 
			                  smaller than usize */
			       TRUE,   /* do let the window be stretched 
			                  larger than default size */
			       FALSE); /* don't shrink the window 
			                  automatically to fit contents */

}

Mike Engber's avatar
Mike Engber committed
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
static void
menu_bar_no_resize_hack_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
{
	/* do nothing */
}

static void
menu_bar_no_resize_hack_menu_bar_finder (GtkWidget *child, gpointer data)
{
	if (GTK_IS_MENU_BAR (child)) {
		* (GtkObject **) data = GTK_OBJECT (child);
	}
}

/* Since there's only one desktop at a time, we can keep track
360
 * of our faux-class with a single static.
Mike Engber's avatar
Mike Engber committed
361
 */
362
static GtkObjectClass *menu_bar_no_resize_hack_class;
Mike Engber's avatar
Mike Engber committed
363
364

static void
365
menu_bar_no_resize_hack_class_free (void)
Mike Engber's avatar
Mike Engber committed
366
{
367
	g_free (menu_bar_no_resize_hack_class);
Mike Engber's avatar
Mike Engber committed
368
369
370
371
372
373
374
375
376
377
378
379
380
381
}

/* This fn is used to keep the desktop menu bar from resizing.
 * It patches out its class with one where the size_allocate
 * method has been replaced with a no-op.
 */
static void
menu_bar_no_resize_hack (NautilusWindow *window)
{
	GtkObject *menu_bar;
	GtkTypeQuery *type_query;
	
	menu_bar = NULL;
	
382
383
384
	nautilus_gtk_container_foreach_deep (GTK_CONTAINER (window),
					     menu_bar_no_resize_hack_menu_bar_finder,
					     &menu_bar);
Mike Engber's avatar
Mike Engber committed
385
386
387

	g_return_if_fail (menu_bar != NULL);

388
389
	if (menu_bar_no_resize_hack_class == NULL) {
		g_atexit (menu_bar_no_resize_hack_class_free);
Mike Engber's avatar
Mike Engber committed
390
391
392
	}
	
	type_query = gtk_type_query (menu_bar->klass->type);
393
394
	g_free (menu_bar_no_resize_hack_class);
	menu_bar_no_resize_hack_class = g_memdup (menu_bar->klass, type_query->class_size);
Mike Engber's avatar
Mike Engber committed
395
396
	g_free (type_query);
	
397
398
	((GtkWidgetClass *) menu_bar_no_resize_hack_class)->size_allocate
		= menu_bar_no_resize_hack_size_allocate;
Mike Engber's avatar
Mike Engber committed
399

400
	menu_bar->klass = menu_bar_no_resize_hack_class;
Mike Engber's avatar
Mike Engber committed
401
402
}

403
404
static gboolean
location_change_at_idle_callback (gpointer callback_data)
405
{
406
407
408
409
410
411
412
413
	NautilusWindow *window;
	char *location;

	window = NAUTILUS_WINDOW (callback_data);

	location = window->details->location_to_change_to_at_idle;
	window->details->location_to_change_to_at_idle = NULL;
	window->details->location_change_at_idle_id = 0;
414

415
	nautilus_window_go_to (window, location);
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
	g_free (location);

	return FALSE;
}

/* handle bonobo events from the throbber -- since they can come in at
   any time right in the middle of things, defer until idle */
static void 
throbber_location_change_request_callback (BonoboListener *listener,
					   char *event_name, 
					   CORBA_any *arg,
					   CORBA_Environment *ev,
					   gpointer callback_data)
{
	NautilusWindow *window;

	window = NAUTILUS_WINDOW (callback_data);

	g_free (window->details->location_to_change_to_at_idle);
	window->details->location_to_change_to_at_idle = g_strdup (BONOBO_ARG_GET_STRING (arg));

	if (window->details->location_change_at_idle_id == 0) {
		window->details->location_change_at_idle_id =
			gtk_idle_add (location_change_at_idle_callback, window);
	}
441
442
}

Elliot Lee's avatar
Elliot Lee committed
443
static void
Gene Z. Ragan's avatar
CVS:    
Gene Z. Ragan committed
444
nautilus_window_constructed (NautilusWindow *window)
Elliot Lee's avatar
Elliot Lee committed
445
{
Darin Adler's avatar
Darin Adler committed
446
	GtkWidget *location_bar_box;
447
	GtkWidget *view_as_menu_vbox;
Gene Z. Ragan's avatar
CVS:    
Gene Z. Ragan committed
448
  	int sidebar_width;
449
	BonoboControl *location_bar_wrapper;
450
	EPaned *panel;
451
452
453
	CORBA_Environment ev;
	Bonobo_PropertyBag property_bag;
	
454
455
456
	/* CORBA and Bonobo setup, which must be done before the location bar setup */
	window->details->ui_container = bonobo_ui_container_new ();
	bonobo_ui_container_set_win (window->details->ui_container,
457
				     BONOBO_WINDOW (window));
458
459
460
461
462

	/* Load the user interface from the XML file. */
	window->details->shell_ui = bonobo_ui_component_new ("Nautilus Shell");
	bonobo_ui_component_set_container
		(window->details->shell_ui,
Darin Adler's avatar
Darin Adler committed
463
		 nautilus_window_get_ui_container (window));
464
465
466
467
468
469
	bonobo_ui_component_freeze (window->details->shell_ui, NULL);
	bonobo_ui_util_set_ui (window->details->shell_ui,
			       DATADIR,
			       "nautilus-shell-ui.xml",
			       "nautilus");
	bonobo_ui_component_thaw (window->details->shell_ui, NULL);
470
	
Darin Adler's avatar
Darin Adler committed
471
	/* Load the services part of the user interface too if desired. */
472
#ifdef EAZEL_SERVICES
Darin Adler's avatar
Darin Adler committed
473
	nautilus_window_install_service_ui (window);
474
#endif
475

Gene Z. Ragan's avatar
CVS:    
Gene Z. Ragan committed
476
	/* set up location bar */
Darin Adler's avatar
Darin Adler committed
477
	location_bar_box = gtk_hbox_new (FALSE, GNOME_PAD);
478
	gtk_container_set_border_width (GTK_CONTAINER (location_bar_box), GNOME_PAD_SMALL);
Darin Adler's avatar
Darin Adler committed
479
	
480
	window->navigation_bar = nautilus_switchable_navigation_bar_new (window);
481
482
483
	gtk_widget_show (GTK_WIDGET (window->navigation_bar));

	gtk_signal_connect (GTK_OBJECT (window->navigation_bar), "location_changed",
484
			    go_to_callback, window);
485
486

	gtk_signal_connect (GTK_OBJECT (window->navigation_bar), "mode_changed",
487
			    navigation_bar_mode_changed_callback, window);
488
489

	gtk_box_pack_start (GTK_BOX (location_bar_box), window->navigation_bar,
Darin Adler's avatar
Darin Adler committed
490
			    TRUE, TRUE, GNOME_PAD_SMALL);
Gene Z. Ragan's avatar
CVS:    
Gene Z. Ragan committed
491

492
493
494
495
496
497
498
	/* Option menu for content view types; it's empty here, filled in when a uri is set.
	 * Pack it into vbox so it doesn't grow vertically when location bar does. 
	 */
	view_as_menu_vbox = gtk_vbox_new (FALSE, GNOME_PAD_SMALL);
	gtk_widget_show (view_as_menu_vbox);
	gtk_box_pack_end (GTK_BOX (location_bar_box), view_as_menu_vbox, FALSE, FALSE, 0);
	
499
	window->view_as_option_menu = gtk_option_menu_new ();
500
501
	gtk_box_pack_end (GTK_BOX (view_as_menu_vbox), window->view_as_option_menu, TRUE, FALSE, 0);
	gtk_widget_show (window->view_as_option_menu);
Darin Adler's avatar
Darin Adler committed
502
	
503
504
505
	/* Allocate the zoom control and place on the right next to the menu.
	 * It gets shown later, if the view-frame contains something zoomable.
	 */
Gene Z. Ragan's avatar
CVS:    
Gene Z. Ragan committed
506
	window->zoom_control = nautilus_zoom_control_new ();
507
508
509
510
511
512
513
514
	gtk_signal_connect_object (GTK_OBJECT (window->zoom_control), "zoom_in",
				   nautilus_window_zoom_in, GTK_OBJECT (window));
	gtk_signal_connect_object (GTK_OBJECT (window->zoom_control), "zoom_out",
				   nautilus_window_zoom_out, GTK_OBJECT (window));
	gtk_signal_connect_object (GTK_OBJECT (window->zoom_control), "zoom_to_level",
				   nautilus_window_zoom_to_level, GTK_OBJECT (window));
	gtk_signal_connect_object (GTK_OBJECT (window->zoom_control), "zoom_to_fit",
				   nautilus_window_zoom_to_fit, GTK_OBJECT (window));
Gene Z. Ragan's avatar
CVS:    
Gene Z. Ragan committed
515
516
	gtk_box_pack_end (GTK_BOX (location_bar_box), window->zoom_control, FALSE, FALSE, 0);
	
517
	gtk_widget_show (location_bar_box);
Darin Adler's avatar
Darin Adler committed
518
	
519
520
	/* FIXME bugzilla.eazel.com 1243: 
	 * We should use inheritance instead of these special cases
Darin Adler's avatar
Darin Adler committed
521
522
523
	 * for the desktop window.
	 */
        if (NAUTILUS_IS_DESKTOP_WINDOW (window)) {
524
		window->content_hbox = gtk_widget_new (NAUTILUS_TYPE_GENEROUS_BIN, NULL);
Darin Adler's avatar
Darin Adler committed
525
	} else {
526
527
		set_initial_window_geometry (window);
	
528
		window->content_hbox = nautilus_horizontal_splitter_new ();
529
530
		panel = E_PANED (window->content_hbox);
		
531
532
		/* FIXME bugzilla.eazel.com 1245: Saved in pixels instead of in %? */
		/* FIXME bugzilla.eazel.com 1245: No reality check on the value? */
533
		sidebar_width = nautilus_preferences_get_integer (NAUTILUS_PREFERENCES_SIDEBAR_WIDTH);
534
		e_paned_set_position (E_PANED (window->content_hbox), sidebar_width);
Darin Adler's avatar
Darin Adler committed
535
	}
536
	gtk_widget_show (window->content_hbox);
537
	bonobo_window_set_contents (BONOBO_WINDOW (window), window->content_hbox);
Darin Adler's avatar
Darin Adler committed
538
	
539
	/* set up the sidebar */
Darin Adler's avatar
Darin Adler committed
540
541
	window->sidebar = nautilus_sidebar_new ();
	
542
543
	/* FIXME bugzilla.eazel.com 1243: 
	 * We should use inheritance instead of these special cases
Darin Adler's avatar
Darin Adler committed
544
545
546
	 * for the desktop window.
	 */
        if (!NAUTILUS_IS_DESKTOP_WINDOW (window)) {
547
548
		gtk_widget_show (GTK_WIDGET (window->sidebar));
		gtk_signal_connect (GTK_OBJECT (window->sidebar), "location_changed",
549
				    go_to_callback, window);
550
551
552
		e_paned_pack1 (E_PANED (window->content_hbox),
			       GTK_WIDGET (window->sidebar),
			       FALSE, FALSE);
Darin Adler's avatar
Darin Adler committed
553
554
	}
	
555
556
	bonobo_ui_component_freeze (window->details->shell_ui, NULL);

557
558
559
560
	/* FIXME bugzilla.eazel.com 1243: 
	 * We should use inheritance instead of these special cases
	 * for the desktop window.
	 */
561
562
563
564
	if (NAUTILUS_IS_DESKTOP_WINDOW (window)) {
		nautilus_bonobo_set_hidden (window->details->shell_ui,
					    LOCATION_BAR_PATH, TRUE);
		nautilus_bonobo_set_hidden (window->details->shell_ui,
565
					    TOOLBAR_PATH, TRUE);
566
567
568
569
		nautilus_bonobo_set_hidden (window->details->shell_ui,
					    STATUS_BAR_PATH, TRUE);
		nautilus_bonobo_set_hidden (window->details->shell_ui,
					    MENU_BAR_PATH, TRUE);
Mike Engber's avatar
Mike Engber committed
570
571
572
573
574
575

		/* FIXME bugzilla.eazel.com 4752:
		 * If we ever get the unsigned math errors in
		 * gtk_menu_item_size_allocate fixed this can be removed.
		 */
		menu_bar_no_resize_hack (window);
576
	}
577
578
579
580
581
582
583

	/* Wrap the location bar in a control and set it up. */
	location_bar_wrapper = bonobo_control_new (location_bar_box);
	bonobo_ui_component_object_set (window->details->shell_ui,
					"/Location Bar/Wrapper",
					bonobo_object_corba_objref (BONOBO_OBJECT (location_bar_wrapper)),
					NULL);
584
	bonobo_ui_component_thaw (window->details->shell_ui, NULL);
585
	bonobo_object_unref (BONOBO_OBJECT (location_bar_wrapper));
586

587
	/* initalize the menus and toolbars */
Gene Z. Ragan's avatar
CVS:    
Gene Z. Ragan committed
588
589
	nautilus_window_initialize_menus (window);
	nautilus_window_initialize_toolbars (window);
590

591
	/* watch for throbber location changes, too */
592
593
594
	if (window->throbber != NULL) {
		CORBA_exception_init (&ev);
		property_bag = Bonobo_Control_getProperties (window->throbber, &ev);
595
596
597
598
599
		if (!BONOBO_EX (&ev) && property_bag != CORBA_OBJECT_NIL) {
			window->details->throbber_location_change_request_listener_id =
				bonobo_event_source_client_add_listener
				(property_bag, throbber_location_change_request_callback, 
				 "Bonobo/Property:change:location", NULL, window); 
600
601
602
603
			bonobo_object_release_unref (property_bag, &ev);	
		}
		CORBA_exception_free (&ev);
	}
Gene Z. Ragan's avatar
CVS:    
Gene Z. Ragan committed
604
605
	
	/* Set initial sensitivity of some buttons & menu items 
Darin Adler's avatar
Darin Adler committed
606
607
608
609
610
	 * now that they're all created.
	 */
	nautilus_window_allow_back (window, FALSE);
	nautilus_window_allow_forward (window, FALSE);
	nautilus_window_allow_stop (window, FALSE);
611

Darin Adler's avatar
Darin Adler committed
612
	/* Set up undo manager */
613
	nautilus_undo_manager_attach (window->application->undo_manager, GTK_OBJECT (window));	
614
615
616

	/* Set up the sidebar panels. */
	update_sidebar_panels_from_preferences (window);
617
618
619

	/* Register that things may be dragged from this window */
	nautilus_drag_window_register (GTK_WINDOW (window));
Elliot Lee's avatar
Elliot Lee committed
620
621
622
}

static void
Darin Adler's avatar
Darin Adler committed
623
624
625
nautilus_window_set_arg (GtkObject *object,
			 GtkArg *arg,
			 guint arg_id)
Elliot Lee's avatar
Elliot Lee committed
626
{
627
	char *old_name;
628
629
630
	NautilusWindow *window;

	window = NAUTILUS_WINDOW (object);
Darin Adler's avatar
Darin Adler committed
631
	
632
	switch (arg_id) {
Darin Adler's avatar
Darin Adler committed
633
	case ARG_APP_ID:
634
		if (GTK_VALUE_STRING (*arg) == NULL) {
Darin Adler's avatar
Darin Adler committed
635
636
			return;
		}
637
638
		old_name = bonobo_window_get_name (BONOBO_WINDOW (window));
		bonobo_window_set_name (BONOBO_WINDOW (window), GTK_VALUE_STRING (*arg));
639
640
641
642
643
644
		/* This hack of using the time when the name first
		 * goes non-NULL to be window-constructed time is
		 * completely lame. But it works, so for now we leave
		 * it alone.
		 */
		if (old_name == NULL) {
645
			nautilus_window_constructed (window);
646
647
		}
		g_free (old_name);
Darin Adler's avatar
Darin Adler committed
648
649
		break;
	case ARG_APP:
650
		window->application = NAUTILUS_APPLICATION (GTK_VALUE_OBJECT (*arg));
Darin Adler's avatar
Darin Adler committed
651
652
		break;
	}
Elliot Lee's avatar
Elliot Lee committed
653
654
655
}

static void
Darin Adler's avatar
Darin Adler committed
656
657
658
nautilus_window_get_arg (GtkObject *object,
			 GtkArg *arg,
			 guint arg_id)
Elliot Lee's avatar
Elliot Lee committed
659
{
660
	switch (arg_id) {
Darin Adler's avatar
Darin Adler committed
661
	case ARG_APP_ID:
662
		GTK_VALUE_STRING (*arg) = bonobo_window_get_name (BONOBO_WINDOW (object));
Darin Adler's avatar
Darin Adler committed
663
664
		break;
	case ARG_APP:
665
		GTK_VALUE_OBJECT (*arg) = GTK_OBJECT (NAUTILUS_WINDOW (object)->application);
Darin Adler's avatar
Darin Adler committed
666
667
		break;
	}
Elliot Lee's avatar
Elliot Lee committed
668
669
}

670
static void 
671
nautilus_window_destroy (GtkObject *object)
672
{
673
	NautilusWindow *window;
674
675
676
	CORBA_Environment ev;
	Bonobo_PropertyBag property_bag;
	
677
678
	window = NAUTILUS_WINDOW (object);

679
680
681
682
	/* Handle the part of destroy that's private to the view
	 * management.
	 */
	nautilus_window_manage_views_destroy (window);
683

684
	/* Get rid of all callbacks. */
685

686
	cancel_view_as_callback (window);
Darin Adler's avatar
Darin Adler committed
687
688
	nautilus_preferences_remove_callback (NAUTILUS_PREFERENCES_SIDEBAR_PANELS_NAMESPACE,
					      sidebar_panels_changed_callback,
689
					      window);
690
691
692
	nautilus_preferences_remove_callback (NAUTILUS_PREFERENCES_HIDE_BUILT_IN_BOOKMARKS,
					      nautilus_window_bookmarks_preference_changed_callback,
					      window);
693
694
	nautilus_window_remove_bookmarks_menu_callback (window);
	nautilus_window_remove_go_menu_callback (window);
695
	nautilus_window_toolbar_remove_theme_callback (window);
696

697
	/* Get rid of all owned objects. */
698

699
700
701
702
703
704
705
706
	if (window->details->shell_ui != NULL) {
		bonobo_ui_component_unset_container (window->details->shell_ui);
		bonobo_object_unref (BONOBO_OBJECT (window->details->shell_ui));
	}

	nautilus_file_unref (window->details->viewed_file);

	g_list_free (window->sidebar_panels);
707

708
	nautilus_view_identifier_free (window->content_view_id);
Darin Adler's avatar
Darin Adler committed
709
	
710
711
712
	g_free (window->details->location);
	nautilus_g_list_free_deep (window->details->selection);
	nautilus_g_list_free_deep (window->details->pending_selection);
713
714
715
716

	nautilus_window_clear_back_list (window);
	nautilus_window_clear_forward_list (window);

717
718
719
720
721
722
	if (window->current_location_bookmark != NULL) {
		gtk_object_unref (GTK_OBJECT (window->current_location_bookmark));
	}
	if (window->last_location_bookmark != NULL) {
		gtk_object_unref (GTK_OBJECT (window->last_location_bookmark));
	}
Darin Adler's avatar
Darin Adler committed
723
	
724
725
	if (window->status_bar_clear_id != 0) {
		g_source_remove (window->status_bar_clear_id);
726
	}
727

728
729
	if (window->details->ui_container != NULL) {
		bonobo_object_unref (BONOBO_OBJECT (window->details->ui_container));
730
	}
731

732
733
734
	if (window->throbber != NULL) {
		CORBA_exception_init (&ev);
		property_bag = Bonobo_Control_getProperties (window->throbber, &ev);
735
736
737
738
739
		if (!BONOBO_EX (&ev) && property_bag != CORBA_OBJECT_NIL) {	
			bonobo_event_source_client_remove_listener
				(property_bag,
				 window->details->throbber_location_change_request_listener_id,
				 &ev);
740
741
742
743
744
745
			bonobo_object_release_unref (property_bag, &ev);	
		}

		bonobo_object_release_unref (window->throbber, &ev);		
		CORBA_exception_free (&ev);
	}
746
747
748
749
	if (window->details->location_change_at_idle_id != 0) {
		gtk_idle_remove (window->details->location_change_at_idle_id);
	}

750
751
	g_free (window->details);

752
	NAUTILUS_CALL_PARENT (GTK_OBJECT_CLASS, destroy, (GTK_OBJECT (window)));
753
754
}

755
756
757
758
759
760
761
762
763
static void
nautilus_window_save_geometry (NautilusWindow *window)
{
	char *geometry_string;

        g_return_if_fail (NAUTILUS_IS_WINDOW (window));
	g_return_if_fail (GTK_WIDGET_VISIBLE (window));

        geometry_string = gnome_geometry_string (GTK_WIDGET (window)->window);
764
765
766
767
	nautilus_file_set_metadata (window->details->viewed_file,
				    NAUTILUS_METADATA_KEY_WINDOW_GEOMETRY,
				    NULL,
				    geometry_string);
768
769
770
	g_free (geometry_string);
}

771
772
773
774
void
nautilus_window_close (NautilusWindow *window)
{
        g_return_if_fail (NAUTILUS_IS_WINDOW (window));
775
776
777
778
779

	/* Save the window position in the directory's metadata only if
	 * we're in every-location-in-its-own-window mode. Otherwise it
	 * would be too apparently random when the stored positions change.
	 */
780
	if (nautilus_preferences_get_boolean (NAUTILUS_PREFERENCES_WINDOW_ALWAYS_NEW)) {
781
782
783
	        nautilus_window_save_geometry (window);
	}

784
785
786
	gtk_widget_destroy (GTK_WIDGET (window));
}

787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
static void
nautilus_window_update_launcher (GdkWindow *window)
{
	struct timeval tmp;
	
	gettimeofday (&tmp, NULL);

	/* Set a property on the root window to the time of day in seconds.
	 * The launcher will monitor the root window for this property change
	 * to update its launching state */
	gdk_property_change (GDK_ROOT_PARENT (),
			     gdk_atom_intern ("_NAUTILUS_LAST_WINDOW_REALIZE_TIME", FALSE),
			     XA_CARDINAL,
			     32,
			     PropModeReplace,
			     (guchar *) &tmp.tv_sec,
			     1);
}

806
807
808
static void
nautilus_window_realize (GtkWidget *widget)
{
809
810
811
812
        char *filename;
	GdkPixbuf *pixbuf;
        GdkPixmap *pixmap;
        GdkBitmap *mask;
813
814
        
        /* Create our GdkWindow */
815
	NAUTILUS_CALL_PARENT (GTK_WIDGET_CLASS, realize, (widget));
816

817
        /* Set the mini icon */
818
        filename = nautilus_pixmap_file ("nautilus-mini-logo.png");
819
820
821
        if (filename != NULL) {
                pixbuf = gdk_pixbuf_new_from_file(filename);
                if (pixbuf != NULL) {
822
                        gdk_pixbuf_render_pixmap_and_mask
823
				(pixbuf, &pixmap, &mask, NAUTILUS_STANDARD_ALPHA_THRESHHOLD);				   
824
			gdk_pixbuf_unref (pixbuf);
825
826
827
828
829
830
			nautilus_set_mini_icon
				(widget->window, pixmap, mask);
			/* FIXME bugzilla.eazel.com 610: It seems we are
			 * leaking the pixmap and mask here, but if we unref
			 * them here, the task bar crashes.
			 */
831
832
833
		}
        	g_free (filename);
	}
834
835
836

	/* Notify the launcher that our window has been realized */
	nautilus_window_update_launcher (widget->window);
837
838
}

839
840
841
842
843
844
845
846
847
848
static void
nautilus_window_size_request (GtkWidget		*widget,
			      GtkRequisition	*requisition)
{
	guint max_width;
	guint max_height;

	g_return_if_fail (NAUTILUS_IS_WINDOW (widget));
	g_return_if_fail (requisition != NULL);

849
	NAUTILUS_CALL_PARENT (GTK_WIDGET_CLASS, size_request, (widget, requisition));
850
851
852
853
854
855
856
857
858
859
860
861
862
863

	/* Limit the requisition to be within 90% of the available screen 
	 * real state.
	 *
	 * This way the user will have a fighting chance of getting
	 * control of their window back if for whatever reason one of the
	 * window's descendants decide they want to be 4000 pixels wide.
	 *
	 * Note that the user can still make the window really huge by hand.
	 *
	 * Bugs in components or other widgets that cause such huge geometries
	 * to be requested, should still be fixed.  This code is here only to 
	 * prevent the extremely frustrating consequence of such bugs.
	 */
864
865
	max_width = get_max_forced_width ();
	max_height = get_max_forced_height ();
866

867
	if (requisition->width > (int) max_width) {
868
869
870
		requisition->width = max_width;
	}
	
871
	if (requisition->height > (int) max_height) {
872
873
874
875
		requisition->height = max_height;
	}
}

876

877
878
879
880
/*
 * Main API
 */

881
static void
882
view_as_menu_switch_views_callback (GtkWidget *widget, gpointer data)
883
884
885
886
887
888
889
890
{
        NautilusWindow *window;
        NautilusViewIdentifier *identifier;
        
        g_return_if_fail (GTK_IS_MENU_ITEM (widget));
        g_return_if_fail (NAUTILUS_IS_WINDOW (gtk_object_get_data (GTK_OBJECT (widget), "window")));
        
        window = NAUTILUS_WINDOW (gtk_object_get_data (GTK_OBJECT (widget), "window"));
891
        identifier = (NautilusViewIdentifier *) gtk_object_get_data (GTK_OBJECT (widget), "identifier");
892
        
893
        nautilus_window_set_content_view (window, identifier);
894
895
}

896
/* Note: The identifier parameter ownership is handed off to the menu item. */
897
static GtkWidget *
898
create_view_as_menu_item (NautilusWindow *window, NautilusViewIdentifier *identifier)
899
900
901
902
903
904
905
906
{
	GtkWidget *menu_item;
        char *menu_label;

	menu_label = g_strdup_printf (_("View as %s"), identifier->name);
	menu_item = gtk_menu_item_new_with_label (menu_label);
	g_free (menu_label);

907
908
	gtk_signal_connect (GTK_OBJECT (menu_item),
			    "activate",
909
			    view_as_menu_switch_views_callback, 
910
			    NULL);
911
912
913
914

	/* Store copy of iid in item; free when item destroyed. */
	gtk_object_set_data_full (GTK_OBJECT (menu_item),
				  "identifier",
915
				  identifier,
916
917
918
919
920
921
922
923
924
				  (GtkDestroyNotify) nautilus_view_identifier_free);

	/* Store reference to window in item; no need to free this. */
	gtk_object_set_data (GTK_OBJECT (menu_item), "window", window);
	gtk_widget_show (menu_item);

	return menu_item;
}

925
926
927
928
929
930
931
932
933
934
935
936
static GtkWidget *
new_gtk_separator (void)
{
	GtkWidget *result;
	
	result = gtk_menu_item_new ();
	gtk_widget_show (result);
	gtk_widget_set_sensitive (result, FALSE);

	return result;
}

937
938
939
940
941
/* Make a special first item in the "View as" option menu that represents
 * the current content view. This should only be called if the current
 * content view isn't already in the "View as" option menu.
 */
static void
942
replace_special_current_view_in_view_as_menu (NautilusWindow *window)
943
944
945
946
947
948
949
950
951
952
953
954
955
{
	GtkWidget *menu;
	GtkWidget *first_menu_item;
	GtkWidget *new_menu_item;
	
	menu = gtk_option_menu_get_menu (GTK_OPTION_MENU (window->view_as_option_menu));

	/* Remove menu before changing contents so it is resized properly
	 * when reattached later in this function.
	 */
	gtk_widget_ref (menu);
	gtk_option_menu_remove_menu (GTK_OPTION_MENU (window->view_as_option_menu));

Darin Adler's avatar
Darin Adler committed
956
957
	first_menu_item = nautilus_gtk_container_get_first_child (GTK_CONTAINER (menu));
	g_assert (first_menu_item != NULL);
958

Darin Adler's avatar
Darin Adler committed
959
	if (GPOINTER_TO_INT (gtk_object_get_data (GTK_OBJECT (first_menu_item), "current content view"))) {
960
961
962
		gtk_container_remove (GTK_CONTAINER (menu), first_menu_item);
	} else {
		/* Prepend separator. */
963
		gtk_menu_prepend (GTK_MENU (menu), new_gtk_separator ());
964
965
	}

966
	new_menu_item = create_view_as_menu_item (window, nautilus_view_identifier_copy (window->content_view_id));
967
968
969
970
971
972
973
	gtk_object_set_data (GTK_OBJECT (new_menu_item), "current content view", GINT_TO_POINTER (TRUE));
	gtk_menu_prepend (GTK_MENU (menu), new_menu_item);

	gtk_option_menu_set_menu (GTK_OPTION_MENU (window->view_as_option_menu), menu);
	gtk_widget_unref (menu);
}

974
/**
975
 * nautilus_window_synch_view_as_menu:
976
977
978
979
980
981
 * 
 * Set the visible item of the "View as" option menu to
 * match the current content view.
 * 
 * @window: The NautilusWindow whose "View as" option menu should be synched.
 */
982
void
983
nautilus_window_synch_view_as_menu (NautilusWindow *window)
984
985
986
{
	GList *children, *child;
	GtkWidget *menu;
987
	const char *content_view_iid;
988
	NautilusViewIdentifier *item_id;
989
990
991
992
993
994
995
996
	int index, matching_index;

	g_return_if_fail (NAUTILUS_IS_WINDOW (window));

	menu = gtk_option_menu_get_menu (GTK_OPTION_MENU (window->view_as_option_menu));
	if (menu == NULL) {
		return;
	}
997
	
998
999
1000
	children = gtk_container_children (GTK_CONTAINER (menu));
	matching_index = -1;

1001
1002
1003
1004
	if (window->content_view != NULL) {
		content_view_iid = nautilus_view_frame_get_view_iid (window->content_view);

		for (child = children, index = 0; child != NULL; child = child->next, ++index) {
1005
1006
			item_id = (NautilusViewIdentifier *) gtk_object_get_data
				(GTK_OBJECT (child->data), "identifier");
1007
1008
1009
1010
			if (item_id != NULL && strcmp (content_view_iid, item_id->iid) == 0) {
				matching_index = index;
				break;
			}
1011
1012
1013
		}
	}

1014
	if (matching_index == -1) {
1015
		replace_special_current_view_in_view_as_menu (window);
1016
		matching_index = 0;
1017
1018
	}

1019
1020
1021
	gtk_option_menu_set_history (GTK_OPTION_MENU (window->view_as_option_menu), 
				     matching_index);

1022
1023
1024
1025
1026
1027
	g_list_free (children);
}

static void
chose_component_callback (NautilusViewIdentifier *identifier, gpointer callback_data)
{
1028
	NautilusWindow *window;
1029

1030
	window = NAUTILUS_WINDOW (callback_data);
1031
	if (identifier != NULL) {
1032
		nautilus_window_set_content_view (window, identifier);
1033
	}
1034
1035
1036
1037
1038
1039
	
	/* FIXME bugzilla.eazel.com 1334: There should be some global
	 * way to signal that the file type associations have changed,
	 * so that the places that display these lists can react. For
	 * now, hardwire this case, which is the most obvious one by
	 * far.
1040
	 */
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
	nautilus_window_load_view_as_menu (window);
}

static void
cancel_chose_component_callback (NautilusWindow *window)
{
	if (window->details->viewed_file != NULL) {
		nautilus_cancel_choose_component_for_file (window->details->viewed_file,
							   chose_component_callback, 
							   window);
1051
	}
1052
1053
1054
}

static void
1055
view_as_menu_choose_view_callback (GtkWidget