GitLab repository storage has been migrated to hashed layout. Please contact Infrastructure team if you notice any issues with repositories or hooks.

fm-directory-view.c 32.4 KB
Newer Older
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2
/* fm-directory-view.c
Ettore Perazzoli's avatar
Ettore Perazzoli committed
3
 *
Elliot Lee's avatar
Elliot Lee committed
4
 * Copyright (C) 1999, 2000  Free Software Foundaton
5
 * Copyright (C) 2000  Eazel, Inc.
Ettore Perazzoli's avatar
Ettore Perazzoli committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 *
 * This program is free software; you can redistribute it and/or
 * 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.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Author: Ettore Perazzoli
 */

25
#include <config.h>
26
#include "fm-directory-view.h"
Ettore Perazzoli's avatar
Ettore Perazzoli committed
27

28 29
#include <gtk/gtksignal.h>
#include <gtk/gtkmain.h>
30 31
#include <gtk/gtkmenu.h>
#include <gtk/gtkmenuitem.h>
32 33 34
#include <libgnome/gnome-i18n.h>
#include <libgnomevfs/gnome-vfs-async-ops.h>
#include <libgnomevfs/gnome-vfs-directory-list.h>
35
#include <libgnomevfs/gnome-vfs-file-info.h>
36
#include <libgnomevfs/gnome-vfs-uri.h>
37
#include <libgnomevfs/gnome-vfs-utils.h>
38
#include <libnautilus/nautilus-alloc.h>
39 40
#include <libnautilus/nautilus-gtk-macros.h>
#include <libnautilus/nautilus-gtk-extensions.h>
41

42
#define DISPLAY_TIMEOUT_INTERVAL_MSECS 500
43

44 45
enum 
{
46 47
	ADD_ENTRY,
	BEGIN_ADDING_ENTRIES,
48
	CLEAR,
49 50
	DONE_ADDING_ENTRIES,
	BEGIN_LOADING,
51
	APPEND_ITEM_CONTEXT_MENU_ITEMS,
52
	APPEND_BACKGROUND_CONTEXT_MENU_ITEMS,
53 54 55
	LAST_SIGNAL
};

56
static guint fm_directory_view_signals[LAST_SIGNAL];
Ettore Perazzoli's avatar
Ettore Perazzoli committed
57

58
struct _FMDirectoryViewDetails
Ettore Perazzoli's avatar
Ettore Perazzoli committed
59
{
60
	NautilusContentViewFrame *view_frame;
61
	NautilusDirectory *model;
62 63 64 65 66 67 68 69 70
	
	guint display_selection_idle_id;
	
	guint display_pending_timeout_id;
	guint display_pending_idle_id;
	
	guint add_files_handler_id;
	
	NautilusFileList *pending_list;
71 72

	gboolean loading;
73
};
Ettore Perazzoli's avatar
Ettore Perazzoli committed
74

75
/* forward declarations */
76
static int display_selection_info_idle_cb 	(gpointer data);
77
static void display_selection_info 		(FMDirectoryView *view);
78 79
static void fm_directory_view_initialize_class	(FMDirectoryViewClass *klass);
static void fm_directory_view_initialize 	(FMDirectoryView *view);
80
static void fm_directory_view_destroy 		(GtkObject *object);
81 82 83 84
static void fm_directory_view_append_background_context_menu_items 
						(FMDirectoryView *view,
						 GtkMenu *menu);
static void fm_directory_view_real_append_background_context_menu_items 		
85 86 87 88 89
						(FMDirectoryView *view,
						 GtkMenu *menu);
static void fm_directory_view_real_append_item_context_menu_items 		
						(FMDirectoryView *view,
						 GtkMenu *menu,
90
						 NautilusFile *file);
91 92 93
static GtkMenu *create_item_context_menu        (FMDirectoryView *view,
						 NautilusFile *file);
static GtkMenu *create_background_context_menu  (FMDirectoryView *view);
94 95 96 97 98
static void stop_location_change_cb 		(NautilusViewFrame *view_frame, 
						 FMDirectoryView *directory_view);
static void notify_location_change_cb 		(NautilusViewFrame *view_frame, 
						 Nautilus_NavigationInfo *nav_context, 
						 FMDirectoryView *directory_view);
99 100
static void open_cb 				(GtkMenuItem *item, NautilusFile *file);
static void open_in_new_window_cb 		(GtkMenuItem *item, NautilusFile *file);
101
static void select_all_cb                       (GtkMenuItem *item, FMDirectoryView *directory_view);
102 103
static void zoom_in_cb                          (GtkMenuItem *item, FMDirectoryView *directory_view);
static void zoom_out_cb                         (GtkMenuItem *item, FMDirectoryView *directory_view);
104

105 106 107 108 109 110 111 112
static void schedule_idle_display_of_pending_files      (FMDirectoryView *view);
static void unschedule_idle_display_of_pending_files    (FMDirectoryView *view);
static void schedule_timeout_display_of_pending_files   (FMDirectoryView *view);
static void unschedule_timeout_display_of_pending_files (FMDirectoryView *view);
static void unschedule_display_of_pending_files         (FMDirectoryView *view);

static void disconnect_model_handlers                   (FMDirectoryView *view);

113
NAUTILUS_DEFINE_CLASS_BOILERPLATE (FMDirectoryView, fm_directory_view, GTK_TYPE_SCROLLED_WINDOW)
114 115 116
NAUTILUS_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, add_entry)
NAUTILUS_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, clear)
NAUTILUS_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_selection)
117
NAUTILUS_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, select_all)
118
NAUTILUS_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, bump_zoom_level)
119 120
NAUTILUS_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, can_zoom_in)
NAUTILUS_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, can_zoom_out)
121

Ettore Perazzoli's avatar
Ettore Perazzoli committed
122
static void
123
fm_directory_view_initialize_class (FMDirectoryViewClass *klass)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
124 125 126
{
	GtkObjectClass *object_class;

127
	object_class = GTK_OBJECT_CLASS (klass);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
128

129
	object_class->destroy = fm_directory_view_destroy;
130 131 132 133 134 135 136 137

	fm_directory_view_signals[CLEAR] =
		gtk_signal_new ("clear",
       				GTK_RUN_LAST,
                    		object_class->type,
                    		GTK_SIGNAL_OFFSET (FMDirectoryViewClass, clear),
		    		gtk_marshal_NONE__NONE,
		    		GTK_TYPE_NONE, 0);
138 139 140 141 142 143 144 145 146 147 148 149
	fm_directory_view_signals[BEGIN_ADDING_ENTRIES] =
		gtk_signal_new ("begin_adding_entries",
       				GTK_RUN_LAST,
                    		object_class->type,
                    		GTK_SIGNAL_OFFSET (FMDirectoryViewClass, begin_adding_entries),
		    		gtk_marshal_NONE__NONE,
		    		GTK_TYPE_NONE, 0);
	fm_directory_view_signals[ADD_ENTRY] =
		gtk_signal_new ("add_entry",
       				GTK_RUN_LAST,
                    		object_class->type,
                    		GTK_SIGNAL_OFFSET (FMDirectoryViewClass, add_entry),
150 151
		    		gtk_marshal_NONE__BOXED,
		    		GTK_TYPE_NONE, 1, GTK_TYPE_BOXED);
152 153 154 155 156 157 158 159 160 161 162 163 164 165
	fm_directory_view_signals[DONE_ADDING_ENTRIES] =
		gtk_signal_new ("done_adding_entries",
       				GTK_RUN_LAST,
                    		object_class->type,
                    		GTK_SIGNAL_OFFSET (FMDirectoryViewClass, done_adding_entries),
		    		gtk_marshal_NONE__NONE,
		    		GTK_TYPE_NONE, 0);
	fm_directory_view_signals[BEGIN_LOADING] =
		gtk_signal_new ("begin_loading",
       				GTK_RUN_LAST,
                    		object_class->type,
                    		GTK_SIGNAL_OFFSET (FMDirectoryViewClass, begin_loading),
		    		gtk_marshal_NONE__NONE,
		    		GTK_TYPE_NONE, 0);
166 167 168 169 170 171 172
	fm_directory_view_signals[APPEND_ITEM_CONTEXT_MENU_ITEMS] =
		gtk_signal_new ("append_item_context_menu_items",
       				GTK_RUN_FIRST,
                    		object_class->type,
                    		GTK_SIGNAL_OFFSET (FMDirectoryViewClass, append_item_context_menu_items),
		    		gtk_marshal_NONE__BOXED_BOXED,
		    		GTK_TYPE_NONE, 2, GTK_TYPE_BOXED, GTK_TYPE_BOXED);
173 174 175 176 177
	fm_directory_view_signals[APPEND_BACKGROUND_CONTEXT_MENU_ITEMS] =
		gtk_signal_new ("append_background_context_menu_items",
       				GTK_RUN_FIRST,
                    		object_class->type,
                    		GTK_SIGNAL_OFFSET (FMDirectoryViewClass, append_background_context_menu_items),
178 179
		    		gtk_marshal_NONE__BOXED,
		    		GTK_TYPE_NONE, 1, GTK_TYPE_BOXED);
180

181
	klass->append_item_context_menu_items = fm_directory_view_real_append_item_context_menu_items;
182
	klass->append_background_context_menu_items = fm_directory_view_real_append_background_context_menu_items;
183

184 185
	/* Function pointers that subclasses must override */

186 187 188
	NAUTILUS_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, add_entry);
	NAUTILUS_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, clear);
	NAUTILUS_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_selection);
189
	NAUTILUS_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, select_all);
190
	NAUTILUS_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, bump_zoom_level);
191 192
	NAUTILUS_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, can_zoom_in);
	NAUTILUS_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, can_zoom_out);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
193 194 195
}

static void
196
fm_directory_view_initialize (FMDirectoryView *directory_view)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
197
{
198
	directory_view->details = g_new0 (FMDirectoryViewDetails, 1);
199
	
200
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (directory_view),
201 202
					GTK_POLICY_AUTOMATIC,
					GTK_POLICY_AUTOMATIC);
203 204
	gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (directory_view), NULL);
	gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (directory_view), NULL);
205

206
	directory_view->details->view_frame = NAUTILUS_CONTENT_VIEW_FRAME
207
		(gtk_widget_new (nautilus_content_view_frame_get_type (), NULL));
208

209
	gtk_signal_connect (GTK_OBJECT (directory_view->details->view_frame), 
210 211 212
			    "stop_location_change",
			    GTK_SIGNAL_FUNC (stop_location_change_cb),
			    directory_view);
213
	gtk_signal_connect (GTK_OBJECT (directory_view->details->view_frame), 
214 215 216 217
			    "notify_location_change",
			    GTK_SIGNAL_FUNC (notify_location_change_cb), 
			    directory_view);

218
	gtk_widget_show (GTK_WIDGET (directory_view));
219

220 221
	gtk_container_add (GTK_CONTAINER (directory_view->details->view_frame),
			   GTK_WIDGET (directory_view));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
222 223
}

224 225 226 227 228 229 230
static void
fm_directory_view_destroy (GtkObject *object)
{
	FMDirectoryView *view;

	view = FM_DIRECTORY_VIEW (object);

231 232
	if (view->details->model != NULL) {
		disconnect_model_handlers (view);
233
		gtk_object_unref (GTK_OBJECT (view->details->model));
234
	}
235

236 237 238 239
	if (view->details->display_selection_idle_id != 0)
		gtk_idle_remove (view->details->display_selection_idle_id);

	unschedule_display_of_pending_files (view);
240

241 242
	g_free (view->details);

243 244 245 246 247 248 249 250
	NAUTILUS_CALL_PARENT_CLASS (GTK_OBJECT_CLASS, destroy, (object));
}



/**
 * display_selection_info:
 *
251
 * Display information about the current selection, and notify the view frame of the changed selection.
252 253 254 255 256 257
 * @view: FMDirectoryView for which to display selection info.
 * 
 **/
static void
display_selection_info (FMDirectoryView *view)
{
258
	NautilusFileList *selection;
259 260
	GnomeVFSFileSize size;
	guint count;
261 262
	NautilusFileList *p;
	char *first_item_name;
263 264 265 266 267 268 269 270
	Nautilus_StatusRequestInfo sri;

	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));

	selection = fm_directory_view_get_selection (view);
	
	count = 0;
	size = 0;
271
	first_item_name = NULL;
272
	for (p = selection; p != NULL; p = p->next) {
273
		NautilusFile *file;
274

275
		file = p->data;
276
		count++;
277
		size += nautilus_file_get_size (file);
278
		if (first_item_name == NULL)
279
			first_item_name = nautilus_file_get_name (file);
280
	}
281
		
282
	g_list_free (selection);
283
	
284 285 286 287
	memset(&sri, 0, sizeof(sri));

	if (count == 0) 
	{
288 289
	        sri.status_string = "";
	}
290
	else
291
	{
292
		char *size_string;
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310

		size_string = gnome_vfs_file_size_to_string (size);
		if (count == 1)
		{
			g_assert (first_item_name != NULL && strlen (first_item_name) > 0);
			
			sri.status_string = g_strdup_printf (_("\"%s\" selected -- %s"), 
							     first_item_name, 
							     size_string);
		}
		else
		{
			sri.status_string = g_strdup_printf (_("%d items selected -- %s"), 
							     count, 
							     size_string);
		}
		g_free (size_string);
	}
311

312 313
	g_free (first_item_name);

314 315
	nautilus_view_frame_request_status_change
		(NAUTILUS_VIEW_FRAME (view->details->view_frame), &sri);
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
}

static void
fm_directory_view_send_selection_change (FMDirectoryView *view)
{
	Nautilus_SelectionRequestInfo request;
	NautilusFileList *selection;
	NautilusFileList *p;
	int i;

	memset (&request, 0, sizeof (request));

	/* Collect a list of URIs. */
	selection = fm_directory_view_get_selection (view);
	request.selected_uris._buffer = g_alloca (g_list_length (selection) * sizeof (char *));
	for (p = selection; p != NULL; p = p->next)
		request.selected_uris._buffer[request.selected_uris._length++]
			= nautilus_file_get_uri (p->data);
	g_list_free (selection);

	/* Send the selection change. */
337
	nautilus_view_frame_request_selection_change
338 339 340 341 342
		(NAUTILUS_VIEW_FRAME (view->details->view_frame), &request);

	/* Free the URIs. */
	for (i = 0; i < request.selected_uris._length; i++)
		g_free (request.selected_uris._buffer[i]);
343 344 345 346 347
}



static void
348 349 350
notify_location_change_cb (NautilusViewFrame *view_frame,
			   Nautilus_NavigationInfo *navigation_context,
			   FMDirectoryView *directory_view)
351
{
352
	fm_directory_view_load_uri (directory_view, navigation_context->requested_uri);
353 354 355
}

static void
356 357
stop_location_change_cb (NautilusViewFrame *view_frame,
			 FMDirectoryView *directory_view)
358
{
359
	fm_directory_view_stop (directory_view);
360 361
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
362 363 364


static void
365
stop_load (FMDirectoryView *view, gboolean error)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
366
{
367
	Nautilus_ProgressRequestInfo progress;
368
	
369 370 371 372 373 374
	if (!view->details->loading) {
		g_assert (!error);
		return;
	}

	nautilus_directory_stop_monitoring (view->details->model);
375 376 377 378
	
	memset(&progress, 0, sizeof(progress));
	progress.amount = 100.0;
	progress.type = error ? Nautilus_PROGRESS_DONE_ERROR : Nautilus_PROGRESS_DONE_OK;
379
	nautilus_view_frame_request_progress_change
380
		(NAUTILUS_VIEW_FRAME (view->details->view_frame), &progress);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
381 382 383
}


384

385 386 387 388 389 390 391 392
/* handle the "select all" menu command */

static void
select_all_cb(GtkMenuItem *item, FMDirectoryView *directory_view)
{
	fm_directory_view_select_all (directory_view);
}

393 394 395
/* handle the zoom in/out menu items */

static void
396
zoom_in_cb (GtkMenuItem *item, FMDirectoryView *directory_view)
397
{
398
	fm_directory_view_bump_zoom_level (directory_view, 1);
399 400 401
}

static void
402
zoom_out_cb (GtkMenuItem *item, FMDirectoryView *directory_view)
403
{
404
	fm_directory_view_bump_zoom_level (directory_view, -1);
405
}
406

407 408 409 410 411 412
static void
use_eazel_theme_icons_cb (GtkMenuItem *item, FMDirectoryView *directory_view)
{
	/* FIXME: This isn't implemented yet. */
}

413 414
static gboolean
display_pending_files (FMDirectoryView *view)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
415
{
416 417
	NautilusFileList *pending_list;
	NautilusFileList *p;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
418

419 420 421
	if (view->details->model != NULL
	    && nautilus_directory_are_all_files_seen (view->details->model))
		stop_load (view, FALSE);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
422

423 424 425 426
	pending_list = view->details->pending_list;
	if (pending_list == NULL)
		return FALSE;
	view->details->pending_list = NULL;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
427

428
	fm_directory_view_begin_adding_entries (view);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
429

430 431 432
	for (p = pending_list; p != NULL; p = p->next) {
		fm_directory_view_add_entry (view, p->data);
		nautilus_file_unref (p->data);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
433 434
	}

435
	fm_directory_view_done_adding_entries (view);
436 437 438 439

	g_list_free (pending_list);

	return TRUE;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
440 441
}

442
static gboolean
443
display_selection_info_idle_cb (gpointer data)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
444
{
445
	FMDirectoryView *view;
446
	
447
	view = FM_DIRECTORY_VIEW (data);
448

449 450
	view->details->display_selection_idle_id = 0;

451
	display_selection_info (view);
452 453
	fm_directory_view_send_selection_change (view);

454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
	return FALSE;
}

static gboolean
display_pending_idle_cb (gpointer data)
{
	/* Don't do another idle until we receive more files. */

	FMDirectoryView *view;

	view = FM_DIRECTORY_VIEW (data);

	view->details->display_pending_idle_id = 0;

	display_pending_files (view);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
469

470 471
	return FALSE;
}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
472

473
static gboolean
474
display_pending_timeout_cb (gpointer data)
475
{
476 477 478 479
	/* Do another timeout if we displayed some files.
	 * Once we get all the files, we'll start using
	 * idle instead.
	 */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
480

481 482 483 484 485 486 487 488 489 490 491
	FMDirectoryView *view;
	gboolean displayed_some;

	view = FM_DIRECTORY_VIEW (data);

	displayed_some = display_pending_files (view);
	if (displayed_some)
		return TRUE;

	view->details->display_pending_timeout_id = 0;
	return FALSE;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
492 493 494
}


495

Ettore Perazzoli's avatar
Ettore Perazzoli committed
496
static void
497
schedule_idle_display_of_pending_files (FMDirectoryView *view)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
498
{
499 500 501
	/* No need to schedule an idle if there's already one pending. */
	if (view->details->display_pending_idle_id != 0)
		return;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
502

503 504
	/* An idle takes precedence over a timeout. */
	unschedule_timeout_display_of_pending_files (view);
Elliot Lee's avatar
Elliot Lee committed
505

506 507 508
	view->details->display_pending_idle_id =
		gtk_idle_add (display_pending_idle_cb, view);
}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
509

510 511 512 513 514 515
static void
schedule_timeout_display_of_pending_files (FMDirectoryView *view)
{
	/* No need to schedule a timeout if there's already one pending. */
	if (view->details->display_pending_timeout_id != 0)
		return;
516

517 518 519
	/* An idle takes precedence over a timeout. */
	if (view->details->display_pending_idle_id != 0)
		return;
520

521 522 523 524
	view->details->display_pending_timeout_id =
		gtk_timeout_add (DISPLAY_TIMEOUT_INTERVAL_MSECS,
				 display_pending_timeout_cb, view);
}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
525

526 527 528 529 530 531 532 533 534 535
static void
unschedule_idle_display_of_pending_files (FMDirectoryView *view)
{
	/* Get rid of idle if it's active. */
	if (view->details->display_pending_idle_id != 0) {
		g_assert (view->details->display_pending_timeout_id == 0);
		gtk_idle_remove (view->details->display_pending_idle_id);
		view->details->display_pending_idle_id = 0;
	}
}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
536

537 538 539 540 541 542 543 544
static void
unschedule_timeout_display_of_pending_files (FMDirectoryView *view)
{
	/* Get rid of timeout if it's active. */
	if (view->details->display_pending_timeout_id != 0) {
		g_assert (view->details->display_pending_idle_id == 0);
		gtk_timeout_remove (view->details->display_pending_timeout_id);
		view->details->display_pending_timeout_id = 0;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
545
	}
546
}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
547

548 549 550 551 552 553
static void
unschedule_display_of_pending_files (FMDirectoryView *view)
{
	unschedule_idle_display_of_pending_files (view);
	unschedule_timeout_display_of_pending_files (view);
}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
554

555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
static void
add_files_cb (NautilusDirectory *directory,
	      NautilusFileList *files,
	      gpointer callback_data)
{
	FMDirectoryView *view;
	NautilusFileList *p;

	g_assert (NAUTILUS_IS_DIRECTORY (directory));
	g_assert (files != NULL);

	view = FM_DIRECTORY_VIEW (callback_data);

	g_assert (directory == view->details->model);

	/* Put the files on the pending list. */
	for (p = files; p != NULL; p = p->next)
		nautilus_file_ref (p->data);
	view->details->pending_list = g_list_concat
		(view->details->pending_list, g_list_copy (files));
	
	/* If we haven't see all the files yet, then we'll wait for the
	   timeout to fire. If we have seen all the files, then we'll use
	   an idle instead.
	*/
	if (nautilus_directory_are_all_files_seen (view->details->model))
		schedule_idle_display_of_pending_files (view);
	else
		schedule_timeout_display_of_pending_files (view);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
584 585
}

586

587 588 589 590 591 592 593 594 595
/**
 * fm_directory_view_clear:
 *
 * Emit the signal to clear the contents of the view. Subclasses must
 * override the signal handler for this signal. This is normally called
 * only by FMDirectoryView.
 * @view: FMDirectoryView to empty.
 * 
 **/
596 597
void
fm_directory_view_clear (FMDirectoryView *view)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
598
{
599
	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
600

601
	gtk_signal_emit (GTK_OBJECT (view), fm_directory_view_signals[CLEAR]);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
602 603
}

604 605 606 607 608 609 610 611 612
/**
 * fm_directory_view_begin_adding_entries:
 *
 * Emit the signal to prepare for adding a set of entries to the view. 
 * Subclasses might want to override the signal handler for this signal. 
 * This is normally called only by FMDirectoryView.
 * @view: FMDirectoryView that will soon have new entries added.
 * 
 **/
Ettore Perazzoli's avatar
Ettore Perazzoli committed
613
void
614
fm_directory_view_begin_adding_entries (FMDirectoryView *view)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
615
{
616
	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
617

618 619 620
	gtk_signal_emit (GTK_OBJECT (view), fm_directory_view_signals[BEGIN_ADDING_ENTRIES]);
}

621 622 623 624 625 626 627
/**
 * fm_directory_view_add_entry:
 *
 * Emit the signal to add one entry to the view. Subclasses must
 * override the signal handler for this signal. This is normally called
 * only by FMDirectoryView.
 * @view: FMDirectoryView to add entry to.
628
 * @file: NautilusFile describing entry to add.
629 630
 * 
 **/
631
void
632
fm_directory_view_add_entry (FMDirectoryView *view, NautilusFile *file)
633 634
{
	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
635
	g_return_if_fail (NAUTILUS_IS_FILE (file));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
636

637
	gtk_signal_emit (GTK_OBJECT (view), fm_directory_view_signals[ADD_ENTRY], file);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
638 639
}

640 641 642 643 644 645 646 647 648
/**
 * fm_directory_view_done_adding_entries:
 *
 * Emit the signal to clean up after adding a set of entries to the view. 
 * Subclasses might want to override the signal handler for this signal. 
 * This is normally called only by FMDirectoryView.
 * @view: FMDirectoryView that has just had new entries added.
 * 
 **/
649
void
650
fm_directory_view_done_adding_entries (FMDirectoryView *view)
651 652 653
{
	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));

654 655 656
	gtk_signal_emit (GTK_OBJECT (view), fm_directory_view_signals[DONE_ADDING_ENTRIES]);
}

657 658 659 660 661 662 663 664 665
/**
 * fm_directory_view_begin_loading:
 *
 * Emit the signal to prepare for loading the contents of a new location. 
 * Subclasses might want to override the signal handler for this signal. 
 * This is normally called only by FMDirectoryView.
 * @view: FMDirectoryView that is switching to view a new location.
 * 
 **/
666
void
667
fm_directory_view_begin_loading (FMDirectoryView *view)
668 669 670
{
	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));

671
	gtk_signal_emit (GTK_OBJECT (view), fm_directory_view_signals[BEGIN_LOADING]);
672 673
}

674 675 676 677 678 679
/**
 * fm_directory_view_bump_zoom_level:
 *
 * bump the current zoom level by invoking the relevant subclass through the slot
 * 
 **/
680
void
681
fm_directory_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment)
682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698
{
	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));

	(* FM_DIRECTORY_VIEW_CLASS (GTK_OBJECT (view)->klass)->bump_zoom_level) (view, zoom_increment);
}

/**
 * fm_directory_view_can_zoom_in:
 *
 * Determine whether the view can be zoomed any closer.
 * @view: The zoomable FMDirectoryView.
 * 
 * Return value: TRUE if @view can be zoomed any closer, FALSE otherwise.
 * 
 **/
gboolean
fm_directory_view_can_zoom_in (FMDirectoryView *view)
699
{
700
	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
701

702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719
	return (* FM_DIRECTORY_VIEW_CLASS (GTK_OBJECT (view)->klass)->can_zoom_in) (view);
}

/**
 * fm_directory_view_can_zoom_out:
 *
 * Determine whether the view can be zoomed any further away.
 * @view: The zoomable FMDirectoryView.
 * 
 * Return value: TRUE if @view can be zoomed any further away, FALSE otherwise.
 * 
 **/
gboolean
fm_directory_view_can_zoom_out (FMDirectoryView *view)
{
	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);

	return (* FM_DIRECTORY_VIEW_CLASS (GTK_OBJECT (view)->klass)->can_zoom_out) (view);
720 721
}

722 723 724
/**
 * fm_directory_view_get_selection:
 *
725
 * Get a list of NautilusFile pointers that represents the
726 727 728 729 730
 * currently-selected items in this view. Subclasses must override
 * the signal handler for the 'get_selection' signal. Callers are
 * responsible for g_free-ing the list (but not its data).
 * @view: FMDirectoryView whose selected items are of interest.
 * 
731
 * Return value: GList of NautilusFile pointers representing the selection.
732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760
 * 
 **/
GList *
fm_directory_view_get_selection (FMDirectoryView *view)
{
	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL);

	return (* FM_DIRECTORY_VIEW_CLASS (GTK_OBJECT (view)->klass)->get_selection) (view);
}

/**
 * fm_directory_view_get_view_frame:
 *
 * Get the NautilusContentViewFrame for this FMDirectoryView.
 * This is normally called only by the embedding framework.
 * @view: FMDirectoryView of interest.
 * 
 * Return value: NautilusContentViewFrame for this view.
 * 
 **/
NautilusContentViewFrame *
fm_directory_view_get_view_frame (FMDirectoryView *view)
{
	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL);

	return view->details->view_frame;
}

/**
761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776
 * fm_directory_view_get_model:
 *
 * Get the model for this FMDirectoryView.
 * @view: FMDirectoryView of interest.
 * 
 * Return value: NautilusDirectory for this view.
 * 
 **/
NautilusDirectory *
fm_directory_view_get_model (FMDirectoryView *view)
{
	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL);

	return view->details->model;
}

777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796
static void
open_cb (GtkMenuItem *item, NautilusFile *file)
{
	FMDirectoryView *directory_view;

	directory_view = FM_DIRECTORY_VIEW (gtk_object_get_data (GTK_OBJECT (item), "directory_view"));

	fm_directory_view_activate_entry (directory_view, file, FALSE);
}

static void
open_in_new_window_cb (GtkMenuItem *item, NautilusFile *file)
{
	FMDirectoryView *directory_view;

	directory_view = FM_DIRECTORY_VIEW (gtk_object_get_data (GTK_OBJECT (item), "directory_view"));

	fm_directory_view_activate_entry (directory_view, file, TRUE);
}

797
static void
798 799 800
add_menu_item (FMDirectoryView *view, GtkMenu *menu, const char *label,
	       void (*activate_handler) (GtkMenuItem *, FMDirectoryView *),
	       gboolean sensitive)
801 802 803
{
	GtkWidget *menu_item;

804 805 806 807
	menu_item = gtk_menu_item_new_with_label (label);
	gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
			    GTK_SIGNAL_FUNC (activate_handler), view);
	gtk_widget_set_sensitive (menu_item, sensitive);
808 809
	gtk_widget_show (menu_item);
	gtk_menu_append (menu, menu_item);
810
}
811

812 813 814 815 816 817 818 819
static void
fm_directory_view_real_append_background_context_menu_items (FMDirectoryView *view, 
							     GtkMenu *menu)
{
	add_menu_item (view, menu, _("Select All"), select_all_cb, TRUE);
	add_menu_item (view, menu, _("Zoom In"), zoom_in_cb, fm_directory_view_can_zoom_in (view));
	add_menu_item (view, menu, _("Zoom Out"), zoom_out_cb, fm_directory_view_can_zoom_out (view));
	add_menu_item (view, menu, _("Use Eazel Theme Icons"), use_eazel_theme_icons_cb, FALSE);
820 821
}

822
static void
823 824 825
fm_directory_view_real_append_item_context_menu_items (FMDirectoryView *view,
						       GtkMenu *menu,
						       NautilusFile *file)
826 827 828 829
{
	GtkWidget *menu_item;

	menu_item = gtk_menu_item_new_with_label ("Open");
830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845
	/* Store directory view in menu item so callback can access it. */
	gtk_object_set_data_full (GTK_OBJECT (menu_item), "directory_view",
				  view, (GtkDestroyNotify) gtk_object_unref);
	gtk_object_ref (GTK_OBJECT (view));
	gtk_signal_connect(GTK_OBJECT (menu_item), "activate",
		           GTK_SIGNAL_FUNC (open_cb), file);
	gtk_widget_show (menu_item);
	gtk_menu_append (menu, menu_item);

	menu_item = gtk_menu_item_new_with_label ("Open in New Window");
	/* Store directory view in menu item so callback can access it. */
	gtk_object_set_data_full (GTK_OBJECT (menu_item), "directory_view",
				  view, (GtkDestroyNotify) gtk_object_unref);
	gtk_object_ref (GTK_OBJECT (view));
	gtk_signal_connect(GTK_OBJECT (menu_item), "activate",
		           GTK_SIGNAL_FUNC (open_in_new_window_cb), file);
846 847 848 849 850 851 852 853 854
	gtk_widget_show (menu_item);
	gtk_menu_append (menu, menu_item);

	menu_item = gtk_menu_item_new_with_label ("Delete");
	gtk_widget_set_sensitive (menu_item, FALSE);
	gtk_widget_show (menu_item);
	gtk_menu_append (menu, menu_item);
}

855
/* FIXME - need better architecture for setting these. */
856 857 858 859 860 861 862

static GtkMenu *
create_item_context_menu (FMDirectoryView *view,
			  NautilusFile *file) 
{
	GtkMenu *menu;
	GtkWidget *menu_item;
863 864 865

	g_assert (FM_IS_DIRECTORY_VIEW (view));
	g_assert (NAUTILUS_IS_FILE (file));
866 867 868
	
	menu = GTK_MENU (gtk_menu_new ());

869 870 871
	gtk_signal_emit (GTK_OBJECT (view),
			 fm_directory_view_signals[APPEND_ITEM_CONTEXT_MENU_ITEMS], 
			 menu, file);
872
	
873 874 875 876 877 878 879 880 881
	/* separator between item-specific and view-general menu items */
	menu_item = gtk_menu_item_new ();
	gtk_widget_show (menu_item);
	gtk_menu_append (menu, menu_item);

	/* Show commands not specific to this item also, since it might
	 * be hard (especially in list view) to find a place to click
	 * that's not on an item.
	 */
882
	fm_directory_view_append_background_context_menu_items (view, menu);
883

884 885 886
	return menu;
}

887 888 889 890 891 892 893 894 895 896 897 898 899 900
/**
 * fm_directory_view_append_background_context_menu_items:
 *
 * Add background menu items (i.e., those not dependent on a particular file)
 * to a context menu.
 * @view: An FMDirectoryView.
 * @menu: The menu being constructed. Could be a background menu or an item-specific
 * menu, because the background items are present in both.
 * 
 **/
static void
fm_directory_view_append_background_context_menu_items (FMDirectoryView *view,
							GtkMenu *menu)
{
901
	gtk_signal_emit (GTK_OBJECT (view),
902 903 904 905 906
			 fm_directory_view_signals[APPEND_BACKGROUND_CONTEXT_MENU_ITEMS], 
			 menu);
}


907 908 909 910 911 912 913 914
/* FIXME - need a way for specific views to add custom commands here. */

static GtkMenu *
create_background_context_menu (FMDirectoryView *view)
{
	GtkMenu *menu;

	menu = GTK_MENU (gtk_menu_new ());
915
	fm_directory_view_append_background_context_menu_items (view, menu);
916
	
917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934
	return menu;
}

/**
 * fm_directory_view_popup_item_context_menu
 *
 * Pop up a context menu appropriate to a specific view item at the last right click location.
 * @view: FMDirectoryView of interest.
 * @file: The model object for which a menu should be popped up.
 * 
 * Return value: NautilusDirectory for this view.
 * 
 **/
void 
fm_directory_view_popup_item_context_menu  (FMDirectoryView *view,
					    NautilusFile *file)
{
	g_assert (FM_IS_DIRECTORY_VIEW (view));
935
	g_assert (NAUTILUS_IS_FILE (file));
936
	
937 938 939
	nautilus_pop_up_context_menu (create_item_context_menu (view, file),
				      NAUTILUS_DEFAULT_POPUP_MENU_DISPLACEMENT,
				      NAUTILUS_DEFAULT_POPUP_MENU_DISPLACEMENT);
940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955
}

/**
 * fm_directory_view_popup_background_context_menu
 *
 * Pop up a context menu appropriate to the view globally at the last right click location.
 * @view: FMDirectoryView of interest.
 * 
 * Return value: NautilusDirectory for this view.
 * 
 **/
void 
fm_directory_view_popup_background_context_menu  (FMDirectoryView *view)
{
	g_assert (FM_IS_DIRECTORY_VIEW (view));
	
956 957 958
	nautilus_pop_up_context_menu (create_background_context_menu (view),
				      NAUTILUS_DEFAULT_POPUP_MENU_DISPLACEMENT,
				      NAUTILUS_DEFAULT_POPUP_MENU_DISPLACEMENT);
959 960 961
}


962 963 964 965 966 967 968 969
/**
 * fm_directory_view_notify_selection_changed:
 * 
 * Notify this view that the selection has changed. This is normally
 * called only by subclasses.
 * @view: FMDirectoryView whose selection has changed.
 * 
 **/
970
void
971
fm_directory_view_notify_selection_changed (FMDirectoryView *view)
972 973 974
{
	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));

975
	/* Schedule a display of the new selection. */
976 977 978 979 980 981 982 983 984 985 986 987 988
	if (view->details->display_selection_idle_id == 0)
		view->details->display_selection_idle_id
			= gtk_idle_add (display_selection_info_idle_cb,
					view);
}

/**
 * fm_directory_view_activate_entry:
 * 
 * Activate an entry in this view. This might involve switching the displayed
 * location for the current window, or launching an application. This is normally
 * called only by subclasses.
 * @view: FMDirectoryView in question.
989
 * @file: A NautilusFile representing the entry in this view to activate.
990
 * @request_new_window: Should this item be opened in a new window?
991 992 993
 * 
 **/
void
994 995 996
fm_directory_view_activate_entry (FMDirectoryView *view, 
				  NautilusFile *file,
				  gboolean request_new_window)
997
{
998
	Nautilus_NavigationRequestInfo request;
999 1000

	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
1001
	g_return_if_fail (NAUTILUS_IS_FILE (file));
1002

1003 1004
	request.requested_uri = nautilus_file_get_uri (file);
	request.new_window_default = Nautilus_V_FALSE;
1005 1006 1007
	request.new_window_suggested = request_new_window ? 
				       Nautilus_V_TRUE : 
				       Nautilus_V_FALSE;
1008
	request.new_window_enforced = Nautilus_V_UNKNOWN;
1009
	nautilus_view_frame_request_location_change
1010
		(NAUTILUS_VIEW_FRAME (view->details->view_frame), &request);
1011

1012
	g_free (request.requested_uri);
1013 1014
}

1015 1016 1017 1018 1019 1020 1021 1022 1023
/**
 * fm_directory_view_load_uri:
 * 
 * Switch the displayed location to a new uri. If the uri is not valid,
 * the location will not be switched; user feedback will be provided instead.
 * @view: FMDirectoryView whose location will be changed.
 * @uri: A string representing the uri to switch to.
 * 
 **/
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1024
void
1025 1026
fm_directory_view_load_uri (FMDirectoryView *view,
			    const char *uri)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1027
{
1028
	Nautilus_ProgressRequestInfo progress;
1029
	NautilusDirectory *old_model;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1030

1031
	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1032 1033
	g_return_if_fail (uri != NULL);

1034
	fm_directory_view_stop (view);
1035 1036 1037
	fm_directory_view_clear (view);

	disconnect_model_handlers (view);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1038

1039
	old_model = view->details->model;
1040
	view->details->model = nautilus_directory_get (uri);
1041 1042 1043
	if (old_model != NULL)
		gtk_object_unref (GTK_OBJECT (old_model));

1044 1045
	memset(&progress, 0, sizeof(progress));
	progress.type = Nautilus_PROGRESS_UNDERWAY;
1046
	nautilus_view_frame_request_progress_change
1047 1048
		(NAUTILUS_VIEW_FRAME (view->details->view_frame), &progress);

1049 1050 1051 1052 1053
	/* Tell interested parties that we've begun loading this directory now.
	 * Subclasses use this to know that the new metadata is now available.
	 */
	gtk_signal_emit (GTK_OBJECT (view), fm_directory_view_signals[BEGIN_LOADING]);

1054
	schedule_timeout_display_of_pending_files (view);
1055
	view->details->loading = TRUE;
1056 1057 1058
	nautilus_directory_start_monitoring (view->details->model,
					     add_files_cb, view);

1059 1060 1061 1062 1063
	/* Attach a handler to get any further files that show up as we
	 * load and sychronize. We won't miss any files because this
	 * signal is emitted from an idle routine and so we will be
	 * connected before the next time it is emitted.
	 */
1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078
	view->details->add_files_handler_id = gtk_signal_connect
		(GTK_OBJECT (view->details->model), 
		 "files_added",
		 GTK_SIGNAL_FUNC (add_files_cb),
		 view);
}

static void
disconnect_model_handlers (FMDirectoryView *view)
{
	if (view->details->add_files_handler_id != 0) {
		gtk_signal_disconnect (GTK_OBJECT (view->details->model),
				       view->details->add_files_handler_id);
		view->details->add_files_handler_id = 0;
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1079 1080
}

1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094
/**
 * fm_directory_view_select_all:
 *
 * select all the items in the view
 * 
 **/
void
fm_directory_view_select_all (FMDirectoryView *view)
{
	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));

	(* FM_DIRECTORY_VIEW_CLASS (GTK_OBJECT (view)->klass)->select_all) (view);
}

1095 1096 1097 1098 1099 1100 1101
/**
 * fm_directory_view_stop:
 * 
 * Stop the current ongoing process, such as switching to a new uri.
 * @view: FMDirectoryView in question.
 * 
 **/
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1102
void
1103
fm_directory_view_stop (FMDirectoryView *view)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1104 1105
{
	g_return_if_fail (view != NULL);
1106
	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1107

1108 1109
	unschedule_display_of_pending_files (view);
	display_pending_files (view);
1110
	stop_load (view, FALSE);
1111
}