nautilus-list-view.c 98.6 KB
Newer Older
1 2
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */

3
/* fm-list-view.c - implementation of list view of directory.
4 5

   Copyright (C) 2000 Eazel, Inc.
Anders Carlsson's avatar
Anders Carlsson committed
6 7
   Copyright (C) 2001, 2002 Anders Carlsson <andersca@gnu.org>
   
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   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.

   Authors: John Sullivan <sullivan@eazel.com>
Anders Carlsson's avatar
Anders Carlsson committed
24
            Anders Carlsson <andersca@gnu.org>
25
	    David Emory Watson <dwatson@cs.ucr.edu>
26 27
*/

28
#include <config.h>
29
#include "nautilus-list-view.h"
30

31
#include "gedit-overlay.h"
32
#include "nautilus-list-model.h"
33
#include "nautilus-error-reporting.h"
34
#include "nautilus-view-dnd.h"
35 36
#include "nautilus-view-factory.h"

37
#include <string.h>
38
#include <eel/eel-vfs-extensions.h>
39
#include <eel/eel-gdk-extensions.h>
40
#include <eel/eel-glib-extensions.h>
41
#include <eel/eel-gtk-macros.h>
42
#include <gdk/gdk.h>
43
#include <gdk/gdkkeysyms.h>
44
#include <gtk/gtk.h>
Anders Carlsson's avatar
Anders Carlsson committed
45
#include <libegg/eggtreemultidnd.h>
46
#include <glib/gi18n.h>
47
#include <glib-object.h>
48
#include <libnautilus-extension/nautilus-column-provider.h>
49
#include <libnautilus-private/nautilus-clipboard-monitor.h>
50 51
#include <libnautilus-private/nautilus-column-chooser.h>
#include <libnautilus-private/nautilus-column-utilities.h>
52
#include <libnautilus-private/nautilus-dnd.h>
53
#include <libnautilus-private/nautilus-file-dnd.h>
54 55
#include <libnautilus-private/nautilus-file-utilities.h>
#include <libnautilus-private/nautilus-ui-utilities.h>
56
#include <libnautilus-private/nautilus-global-preferences.h>
57
#include <libnautilus-private/nautilus-icon-dnd.h>
58
#include <libnautilus-private/nautilus-metadata.h>
59
#include <libnautilus-private/nautilus-module.h>
60
#include <libnautilus-private/nautilus-tree-view-drag-dest.h>
61
#include <libnautilus-private/nautilus-clipboard.h>
62
#include <libnautilus-private/nautilus-cell-renderer-text-ellipsized.h>
63

64 65 66
#define DEBUG_FLAG NAUTILUS_DEBUG_LIST_VIEW
#include <libnautilus-private/nautilus-debug.h>

67
struct NautilusListViewDetails {
Anders Carlsson's avatar
Anders Carlsson committed
68
	GtkTreeView *tree_view;
69
	NautilusListModel *model;
70 71
	GtkActionGroup *list_action_group;
	guint list_merge_id;
72

73
	GtkTreeViewColumn   *file_name_column;
74
	int file_name_column_num;
75 76 77

	GtkCellRendererPixbuf *pixbuf_cell;
	GtkCellRendererText   *file_name_cell;
78
	GList *cells;
79
	GtkCellEditable *editable_widget;
80 81

	NautilusZoomLevel zoom_level;
82

83
	NautilusTreeViewDragDest *drag_dest;
84 85

	GtkTreePath *double_click_path[2]; /* Both clicks in a double click need to be on the same row */
86 87 88

	GtkTreePath *new_selection_path;   /* Path of the new selection after removing a file */

89 90
	GtkTreePath *hover_path;

91 92 93 94 95
	guint drag_button;
	int drag_x;
	int drag_y;

	gboolean drag_started;
96
	gboolean ignore_button_release;
97
	gboolean row_selected_on_button_down;
98
	gboolean menus_ready;
99
	gboolean active;
100 101 102
	
	GHashTable *columns;
	GtkWidget *column_editor;
103 104

	char *original_name;
105 106 107 108
	
	NautilusFile *renaming_file;
	gboolean rename_done;
	guint renaming_file_activate_timeout;
109

110 111
	gulong clipboard_handler_id;

112
	GQuark last_sort_attr;
Anders Carlsson's avatar
Anders Carlsson committed
113
};
114

115 116 117 118 119
struct SelectionForeachData {
	GList *list;
	GtkTreeSelection *selection;
};

120 121 122 123 124
/*
 * The row height should be large enough to not clip emblems.
 * Computing this would be costly, so we just choose a number
 * that works well with the set of emblems we've designed.
 */
Anders Carlsson's avatar
Anders Carlsson committed
125
#define LIST_VIEW_MINIMUM_ROW_HEIGHT	28
126

127
/* We wait two seconds after row is collapsed to unload the subdirectory */
128
#define COLLAPSE_TO_UNLOAD_DELAY 2 
129

130 131 132
/* Wait for the rename to end when activating a file being renamed */
#define WAIT_FOR_RENAME_ON_ACTIVATE 200

133
static GdkCursor *              hand_cursor = NULL;
134

135 136
static GtkTargetList *          source_target_list = NULL;

137 138 139 140 141 142 143 144 145 146 147 148 149
static GList *nautilus_list_view_get_selection                   (NautilusView   *view);
static GList *nautilus_list_view_get_selection_for_file_transfer (NautilusView   *view);
static void   nautilus_list_view_set_zoom_level                  (NautilusListView        *view,
								  NautilusZoomLevel  new_level,
								  gboolean           always_set_level);
static void   nautilus_list_view_scale_font_size                 (NautilusListView        *view,
								  NautilusZoomLevel  new_level);
static void   nautilus_list_view_scroll_to_file                  (NautilusListView        *view,
								  NautilusFile      *file);
static void   nautilus_list_view_rename_callback                 (NautilusFile      *file,
								  GFile             *result_location,
								  GError            *error,
								  gpointer           callback_data);
150

Diego González's avatar
Diego González committed
151

152
G_DEFINE_TYPE (NautilusListView, nautilus_list_view, NAUTILUS_TYPE_VIEW);
153

154 155 156 157 158 159 160 161
static const char * default_trash_visible_columns[] = {
	"name", "size", "type", "trashed_on", "trash_orig_path", NULL
};

static const char * default_trash_columns_order[] = {
	"name", "size", "type", "trashed_on", "trash_orig_path", NULL
};

162
/* for EEL_CALL_PARENT */
163
#define parent_class nautilus_list_view_parent_class
164

165

166 167 168
static const gchar*
get_default_sort_order (NautilusFile *file, gboolean *reversed)
{
169 170
	NautilusFileSortType default_sort_order;
	gboolean default_sort_reversed;
171
	const gchar *retval;
172 173 174 175 176 177 178 179 180 181 182
	const char *attributes[] = {
		"name", /* is really "manually" which doesn't apply to lists */
		"name",
		"uri",
		"size",
		"type",
		"date_modified",
		"date_accessed",
		"trashed_on",
		NULL
	};
183 184 185 186

	retval = nautilus_file_get_default_sort_attribute (file, reversed);

	if (retval == NULL) {
187 188 189 190 191 192 193
		default_sort_order = g_settings_get_enum (nautilus_preferences,
							  NAUTILUS_PREFERENCES_DEFAULT_SORT_ORDER);
		default_sort_reversed = g_settings_get_boolean (nautilus_preferences,
								NAUTILUS_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER);

		retval = attributes[default_sort_order];
		*reversed = default_sort_reversed;
194 195 196 197 198
	}

	return retval;
}

199
static void
Anders Carlsson's avatar
Anders Carlsson committed
200
list_selection_changed_callback (GtkTreeSelection *selection, gpointer user_data)
201
{
202
	NautilusView *view;
203

204
	view = NAUTILUS_VIEW (user_data);
205

206
	nautilus_view_notify_selection_changed (view);
207 208
}

209 210
/* Move these to eel? */

211
static void
212 213 214 215 216 217 218 219 220 221
tree_selection_foreach_set_boolean (GtkTreeModel *model,
				    GtkTreePath *path,
				    GtkTreeIter *iter,
				    gpointer callback_data)
{
	* (gboolean *) callback_data = TRUE;
}

static gboolean
tree_selection_not_empty (GtkTreeSelection *selection)
222
{
223
	gboolean not_empty;
224

225 226 227 228 229 230 231 232 233 234 235 236
	not_empty = FALSE;
	gtk_tree_selection_selected_foreach (selection,
					     tree_selection_foreach_set_boolean,
					     &not_empty);
	return not_empty;
}

static gboolean
tree_view_has_selection (GtkTreeView *view)
{
	return tree_selection_not_empty (gtk_tree_view_get_selection (view));
}
237

238
static void
239
activate_selected_items (NautilusListView *view)
240 241 242
{
	GList *file_list;
	
243
	file_list = nautilus_list_view_get_selection (NAUTILUS_VIEW (view));
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260

	
	if (view->details->renaming_file) {
		/* We're currently renaming a file, wait until the rename is
		   finished, or the activation uri will be wrong */
		if (view->details->renaming_file_activate_timeout == 0) {
			view->details->renaming_file_activate_timeout =
				g_timeout_add (WAIT_FOR_RENAME_ON_ACTIVATE, (GSourceFunc) activate_selected_items, view);
		}
		return;
	}
	
	if (view->details->renaming_file_activate_timeout != 0) {
		g_source_remove (view->details->renaming_file_activate_timeout);
		view->details->renaming_file_activate_timeout = 0;
	}
	
261 262
	nautilus_view_activate_files (NAUTILUS_VIEW (view),
				      file_list,
263
				      0, TRUE);
264 265 266 267 268
	nautilus_file_list_free (file_list);

}

static void
269
activate_selected_items_alternate (NautilusListView *view,
270 271
				   NautilusFile *file,
				   gboolean open_in_tab)
272 273
{
	GList *file_list;
274
	NautilusWindowOpenFlags flags;
275

276 277 278 279 280 281 282 283 284 285 286 287
	flags = 0;

	if (g_settings_get_boolean (nautilus_preferences,
				    NAUTILUS_PREFERENCES_ALWAYS_USE_BROWSER)) {
		if (open_in_tab) {
			flags |= NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB;
		} else {
			flags |= NAUTILUS_WINDOW_OPEN_FLAG_NEW_WINDOW;
		}
	} else {
		flags |= NAUTILUS_WINDOW_OPEN_FLAG_CLOSE_BEHIND;
	}
288 289 290 291 292

	if (file != NULL) {
		nautilus_file_ref (file);
		file_list = g_list_prepend (NULL, file);
	} else {
293
		file_list = nautilus_list_view_get_selection (NAUTILUS_VIEW (view));
294
	}
295 296 297 298
	nautilus_view_activate_files (NAUTILUS_VIEW (view),
				      file_list,
				      flags,
				      TRUE);
299 300 301 302 303 304 305 306 307 308
	nautilus_file_list_free (file_list);

}

static gboolean
button_event_modifies_selection (GdkEventButton *event)
{
	return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0;
}

309 310 311 312 313 314 315
static int
get_click_policy (void)
{
	return g_settings_get_enum (nautilus_preferences,
				    NAUTILUS_PREFERENCES_CLICK_POLICY);
}

316
static void
317 318
nautilus_list_view_did_not_drag (NautilusListView *view,
				 GdkEventButton *event)
319 320 321 322 323 324 325 326 327 328
{
	GtkTreeView *tree_view;
	GtkTreeSelection *selection;
	GtkTreePath *path;
	
	tree_view = view->details->tree_view;
	selection = gtk_tree_view_get_selection (tree_view);

	if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y,
					   &path, NULL, NULL, NULL)) {
329 330 331
		if ((event->button == 1 || event->button == 2)
		    && ((event->state & GDK_CONTROL_MASK) != 0 ||
			(event->state & GDK_SHIFT_MASK) == 0)
332
		    && view->details->row_selected_on_button_down) {
333 334 335 336 337 338
			if (!button_event_modifies_selection (event)) {
				gtk_tree_selection_unselect_all (selection);
				gtk_tree_selection_select_path (selection, path);
			} else {
				gtk_tree_selection_unselect_path (selection, path);
			}
339 340
		}

341
		if ((get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE)
342
		    && !button_event_modifies_selection(event)) {
343 344 345
			if (event->button == 1) {
				activate_selected_items (view);
			} else if (event->button == 2) {
346
				activate_selected_items_alternate (view, NULL, TRUE);
347
			}
348 349 350 351 352 353 354 355 356 357 358 359 360
		}
		gtk_tree_path_free (path);
	}
	
}

static void 
drag_data_get_callback (GtkWidget *widget,
			GdkDragContext *context,
			GtkSelectionData *selection_data,
			guint info,
			guint time)
{
361 362 363
	GtkTreeView *tree_view;
	GtkTreeModel *model;
	GList *ref_list;
364

365
	tree_view = GTK_TREE_VIEW (widget);
366
  
367
	model = gtk_tree_view_get_model (tree_view);
368
  
369 370 371
	if (model == NULL) {
		return;
	}
372

373
	ref_list = g_object_get_data (G_OBJECT (context), "drag-info");
374

375 376 377
	if (ref_list == NULL) {
		return;
	}
378

379 380 381 382 383
	if (EGG_IS_TREE_MULTI_DRAG_SOURCE (model)) {
		egg_tree_multi_drag_source_drag_data_get (EGG_TREE_MULTI_DRAG_SOURCE (model),
							  ref_list,
							  selection_data);
	}
384 385 386
}

static void
387 388 389 390
filtered_selection_foreach (GtkTreeModel *model,
			    GtkTreePath *path,
			    GtkTreeIter *iter,
			    gpointer data)
391
{
392 393 394
	struct SelectionForeachData *selection_data;
	GtkTreeIter parent;
	GtkTreeIter child;
395
	
396 397 398 399 400 401 402 403 404 405 406 407 408 409
	selection_data = data;

	/* If the parent folder is also selected, don't include this file in the
	 * file operation, since that would copy it to the toplevel target instead
	 * of keeping it as a child of the copied folder
	 */
	child = *iter;
	while (gtk_tree_model_iter_parent (model, &parent, &child)) {
		if (gtk_tree_selection_iter_is_selected (selection_data->selection,
							 &parent)) {
			return;
		}
		child = parent;
	}
410
	
411 412
	selection_data->list = g_list_prepend (selection_data->list, 
					       gtk_tree_row_reference_new (model, path));
413 414 415
}

static GList *
416
get_filtered_selection_refs (GtkTreeView *tree_view)
417
{
418
	struct SelectionForeachData selection_data;
419

420 421 422 423 424 425 426
	selection_data.list = NULL;
	selection_data.selection = gtk_tree_view_get_selection (tree_view);
	
	gtk_tree_selection_selected_foreach (selection_data.selection, 
					     filtered_selection_foreach, 
					     &selection_data);
	return g_list_reverse (selection_data.list);
427 428 429 430 431 432 433 434 435 436
}

static void
ref_list_free (GList *ref_list)
{
	g_list_foreach (ref_list, (GFunc) gtk_tree_row_reference_free, NULL);
	g_list_free (ref_list);
}

static void
437
stop_drag_check (NautilusListView *view)
438 439 440 441 442
{		
	view->details->drag_button = 0;
}

static GdkPixbuf *
443
get_drag_pixbuf (NautilusListView *view)
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
{
	GtkTreeModel *model;
	GtkTreePath *path;
	GtkTreeIter iter;
	GdkPixbuf *ret;
	GdkRectangle cell_area;
	
	ret = NULL;
	
	if (gtk_tree_view_get_path_at_pos (view->details->tree_view, 
					   view->details->drag_x,
					   view->details->drag_y,
					   &path, NULL, NULL, NULL)) {
		model = gtk_tree_view_get_model (view->details->tree_view);
		gtk_tree_model_get_iter (model, &iter, path);
		gtk_tree_model_get (model, &iter,
460
				    nautilus_list_model_get_column_id_from_zoom_level (view->details->zoom_level),
461 462 463 464 465 466 467 468
				    &ret,
				    -1);

		gtk_tree_view_get_cell_area (view->details->tree_view,
					     path, 
					     view->details->file_name_column, 
					     &cell_area);

Dave Camp's avatar
Dave Camp committed
469
		gtk_tree_path_free (path);
470 471 472 473 474
	}

	return ret;
}

475 476 477
static void
drag_begin_callback (GtkWidget *widget,
		     GdkDragContext *context,
478
		     NautilusListView *view)
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
{
	GList *ref_list;
	GdkPixbuf *pixbuf;

	pixbuf = get_drag_pixbuf (view);
	if (pixbuf) {
		gtk_drag_set_icon_pixbuf (context,
					  pixbuf,
					  0, 0);
		g_object_unref (pixbuf);
	} else {
		gtk_drag_set_icon_default (context);
	}

	stop_drag_check (view);
	view->details->drag_started = TRUE;
	
	ref_list = get_filtered_selection_refs (GTK_TREE_VIEW (widget));
	g_object_set_data_full (G_OBJECT (context),
				"drag-info",
				ref_list,
				(GDestroyNotify)ref_list_free);
}

503 504 505 506 507
static gboolean
motion_notify_callback (GtkWidget *widget,
			GdkEventMotion *event,
			gpointer callback_data)
{
508
	NautilusListView *view;
509
	
510
	view = NAUTILUS_LIST_VIEW (callback_data);
511

512 513 514 515
	if (event->window != gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget))) {
		return FALSE;
	}

516
	if (get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE) {
517
		GtkTreePath *old_hover_path;
518

519
		old_hover_path = view->details->hover_path;
520 521 522 523 524
		gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
					       event->x, event->y,
					       &view->details->hover_path,
					       NULL, NULL, NULL);

525 526
		if ((old_hover_path != NULL) != (view->details->hover_path != NULL)) {
			if (view->details->hover_path != NULL) {
527
				gdk_window_set_cursor (gtk_widget_get_window (widget), hand_cursor);
528
			} else {
529
				gdk_window_set_cursor (gtk_widget_get_window (widget), NULL);
530 531 532
			}
		}

533 534 535
		if (old_hover_path != NULL) {
			gtk_tree_path_free (old_hover_path);
		}
536 537
	}

538
	if (view->details->drag_button != 0) {
539
		if (!source_target_list) {
540
			source_target_list = nautilus_list_model_get_drag_target_list ();
541 542
		}

543 544 545 546 547
		if (gtk_drag_check_threshold (widget,
					      view->details->drag_x,
					      view->details->drag_y,
					      event->x, 
					      event->y)) {
548
			gtk_drag_begin
549
				(widget,
550
				 source_target_list,
551 552 553 554
				 GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK,
				 view->details->drag_button,
				 (GdkEvent*)event);
		}		      
Alexander Larsson's avatar
Alexander Larsson committed
555
		return TRUE;
556
	}
Alexander Larsson's avatar
Alexander Larsson committed
557 558
	
	return FALSE;
559 560
}

561 562 563 564 565
static gboolean
leave_notify_callback (GtkWidget *widget,
		       GdkEventCrossing *event,
		       gpointer callback_data)
{
566
	NautilusListView *view;
567

568
	view = NAUTILUS_LIST_VIEW (callback_data);
569

570
	if (get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE &&
571 572 573
	    view->details->hover_path != NULL) {
		gtk_tree_path_free (view->details->hover_path);
		view->details->hover_path = NULL;
574
	}
575

576 577 578 579 580 581 582 583
	return FALSE;
}

static gboolean
enter_notify_callback (GtkWidget *widget,
		       GdkEventCrossing *event,
		       gpointer callback_data)
{
584
	NautilusListView *view;
585

586
	view = NAUTILUS_LIST_VIEW (callback_data);
587

588
	if (get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE) {
589 590 591 592 593 594 595 596 597 598
		if (view->details->hover_path != NULL) {
			gtk_tree_path_free (view->details->hover_path);
		}

		gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
					       event->x, event->y,
					       &view->details->hover_path,
					       NULL, NULL, NULL);

		if (view->details->hover_path != NULL) {
599
			gdk_window_set_cursor (gtk_widget_get_window (widget), hand_cursor);
600
		}
601 602 603 604 605
	}

	return FALSE;
}

606
static void
607
do_popup_menu (GtkWidget *widget, NautilusListView *view, GdkEventButton *event)
608 609
{
 	if (tree_view_has_selection (GTK_TREE_VIEW (widget))) {
610
		nautilus_view_pop_up_selection_context_menu (NAUTILUS_VIEW (view), event);
611
	} else {
612
                nautilus_view_pop_up_background_context_menu (NAUTILUS_VIEW (view), event);
613 614 615
	}
}

616 617 618 619 620 621 622
static void
row_activated_callback (GtkTreeView *treeview, GtkTreePath *path, 
			GtkTreeViewColumn *column, NautilusListView *view)
{
	activate_selected_items (view);
}

623 624 625
static gboolean
button_press_callback (GtkWidget *widget, GdkEventButton *event, gpointer callback_data)
{
626
	NautilusListView *view;
627 628
	GtkTreeView *tree_view;
	GtkTreePath *path;
629
	gboolean call_parent;
630
	GtkTreeSelection *selection;
631
	GtkWidgetClass *tree_view_class;
632 633 634 635
	gint64 current_time;
	static gint64 last_click_time = 0;
	static int click_count = 0;
	int double_click_time;
Alexander Larsson's avatar
Alexander Larsson committed
636
	int expander_size, horizontal_separator;
637
	gboolean on_expander;
638

639
	view = NAUTILUS_LIST_VIEW (callback_data);
640
	tree_view = GTK_TREE_VIEW (widget);
641
	tree_view_class = GTK_WIDGET_GET_CLASS (tree_view);
642
	selection = gtk_tree_view_get_selection (tree_view);
643 644 645 646 647

	if (event->window != gtk_tree_view_get_bin_window (tree_view)) {
		return FALSE;
	}

648 649
	nautilus_list_model_set_drag_view
		(NAUTILUS_LIST_MODEL (gtk_tree_view_get_model (tree_view)),
Dave Camp's avatar
Dave Camp committed
650 651
		 tree_view,
		 event->x, event->y);
652 653

	g_object_get (G_OBJECT (gtk_widget_get_settings (widget)),
654 655 656 657 658 659 660 661 662 663 664 665 666 667 668
		      "gtk-double-click-time", &double_click_time,
		      NULL);

	/* Determine click count */
	current_time = eel_get_system_time ();
	if (current_time - last_click_time < double_click_time * 1000) {
		click_count++;
	} else {
		click_count = 0;
	}

	/* Stash time for next compare */
	last_click_time = current_time;

	/* Ignore double click if we are in single click mode */
669
	if (get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE && click_count >= 2) {
670 671
		return TRUE;
	}
Dave Camp's avatar
Dave Camp committed
672

673 674
	view->details->ignore_button_release = FALSE;

675
	call_parent = TRUE;
676 677
	if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y,
					   &path, NULL, NULL, NULL)) {
678 679 680 681 682 683 684 685 686 687 688
		gtk_widget_style_get (widget,
				      "expander-size", &expander_size,
				      "horizontal-separator", &horizontal_separator,
				      NULL);
		/* TODO we should not hardcode this extra padding. It is
		 * EXPANDER_EXTRA_PADDING from GtkTreeView.
		 */
		expander_size += 4;
		on_expander = (event->x <= horizontal_separator / 2 +
			       gtk_tree_path_get_depth (path) * expander_size);

689 690
		/* Keep track of path of last click so double clicks only happen
		 * on the same item */
691
		if ((event->button == 1 || event->button == 2)  && 
692 693 694 695 696 697 698
		    event->type == GDK_BUTTON_PRESS) {
			if (view->details->double_click_path[1]) {
				gtk_tree_path_free (view->details->double_click_path[1]);
			}
			view->details->double_click_path[1] = view->details->double_click_path[0];
			view->details->double_click_path[0] = gtk_tree_path_copy (path);
		}
699
		if (event->type == GDK_2BUTTON_PRESS) {
700 701
			/* Double clicking does not trigger a D&D action. */
			view->details->drag_button = 0;
702
			if (view->details->double_click_path[1] &&
703 704
			    gtk_tree_path_compare (view->details->double_click_path[0], view->details->double_click_path[1]) == 0 &&
			    !on_expander) {
705
				/* NOTE: Activation can actually destroy the view if we're switching */
706 707 708 709
				if (!button_event_modifies_selection (event)) {
					if ((event->button == 1 || event->button == 3)) {
						activate_selected_items (view);
					} else if (event->button == 2) {
710
						activate_selected_items_alternate (view, NULL, TRUE);
711 712 713 714
					}
				} else if (event->button == 1 &&
					   (event->state & GDK_SHIFT_MASK) != 0) {
					NautilusFile *file;
715
					file = nautilus_list_model_file_for_path (view->details->model, path);
716
					if (file != NULL) {
717
						activate_selected_items_alternate (view, file, TRUE);
718 719
						nautilus_file_unref (file);
					}
720
				}
721 722
			} else {
				tree_view_class->button_press_event (widget, event);
723
			}
724 725 726 727 728 729 730 731 732
		} else {
	
			/* We're going to filter out some situations where
			 * we can't let the default code run because all
			 * but one row would be would be deselected. We don't
			 * want that; we want the right click menu or single
			 * click to apply to everything that's currently selected. */
			
			if (event->button == 3 && gtk_tree_selection_path_is_selected (selection, path)) {
733
				call_parent = FALSE;
734 735 736 737
			} 
			
			if ((event->button == 1 || event->button == 2) &&
			    ((event->state & GDK_CONTROL_MASK) != 0 ||
738
			     (event->state & GDK_SHIFT_MASK) == 0)) {			
739 740
				view->details->row_selected_on_button_down = gtk_tree_selection_path_is_selected (selection, path);
				if (view->details->row_selected_on_button_down) {
741 742
					call_parent = on_expander;
					view->details->ignore_button_release = call_parent;
743 744 745 746
				} else if ((event->state & GDK_CONTROL_MASK) != 0) {
					GList *selected_rows;
					GList *l;

747
					call_parent = FALSE;
748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772
					if ((event->state & GDK_SHIFT_MASK) != 0) {
						GtkTreePath *cursor;
						gtk_tree_view_get_cursor (tree_view, &cursor, NULL);
						if (cursor != NULL) {
							gtk_tree_selection_select_range (selection, cursor, path);
						} else {
							gtk_tree_selection_select_path (selection, path);
						}
					} else {
						gtk_tree_selection_select_path (selection, path);
					}
					selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);

					/* This unselects everything */
					gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);

					/* So select it again */
					l = selected_rows;
					while (l != NULL) {
						GtkTreePath *p = l->data;
						l = l->next;
						gtk_tree_selection_select_path (selection, p);
						gtk_tree_path_free (p);
					}
					g_list_free (selected_rows);
773 774
				} else {
					view->details->ignore_button_release = on_expander;
775 776 777 778
				}
			}
		
			if (call_parent) {
779 780 781 782
				g_signal_handlers_block_by_func (tree_view,
								 row_activated_callback,
								 view);

783
				tree_view_class->button_press_event (widget, event);
784 785 786 787

				g_signal_handlers_unblock_by_func (tree_view,
								   row_activated_callback,
								   view);
788 789 790 791 792 793 794 795 796 797 798 799 800 801
			} else if (gtk_tree_selection_path_is_selected (selection, path)) {
				gtk_widget_grab_focus (widget);
			}
			
			if ((event->button == 1 || event->button == 2) &&
			    event->type == GDK_BUTTON_PRESS) {
				view->details->drag_started = FALSE;
				view->details->drag_button = event->button;
				view->details->drag_x = event->x;
				view->details->drag_y = event->y;
			}
			
			if (event->button == 3) {
				do_popup_menu (widget, view, event);
802
			}
803 804
		}

805 806
		gtk_tree_path_free (path);
	} else {
807 808 809 810 811 812 813 814
		if ((event->button == 1 || event->button == 2)  && 
		    event->type == GDK_BUTTON_PRESS) {
			if (view->details->double_click_path[1]) {
				gtk_tree_path_free (view->details->double_click_path[1]);
			}
			view->details->double_click_path[1] = view->details->double_click_path[0];
			view->details->double_click_path[0] = NULL;
		}
815 816 817
		/* Deselect if people click outside any row. It's OK to
		   let default code run; it won't reselect anything. */
		gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (tree_view));
818
		tree_view_class->button_press_event (widget, event);
819 820 821 822

		if (event->button == 3) {
			do_popup_menu (widget, view, event);
		}
823
	}
824 825 826 827
	
	/* We chained to the default handler in this method, so never
	 * let the default handler run */ 
	return TRUE;
828 829
}

830 831 832 833 834
static gboolean
button_release_callback (GtkWidget *widget, 
			 GdkEventButton *event, 
			 gpointer callback_data)
{
835
	NautilusListView *view;
836
	
837
	view = NAUTILUS_LIST_VIEW (callback_data);
838 839 840

	if (event->button == view->details->drag_button) {
		stop_drag_check (view);
841 842
		if (!view->details->drag_started &&
		    !view->details->ignore_button_release) {
843
			nautilus_list_view_did_not_drag (view, event);
844 845 846 847 848
		}
	}
	return FALSE;
}

849 850 851
static gboolean
popup_menu_callback (GtkWidget *widget, gpointer callback_data)
{
852
 	NautilusListView *view;
853

854
	view = NAUTILUS_LIST_VIEW (callback_data);
855 856

	do_popup_menu (widget, view, NULL);
Dave Camp's avatar
Dave Camp committed
857 858

	return TRUE;
859 860
}

Alexander Larsson's avatar
Alexander Larsson committed
861
static void
862
subdirectory_done_loading_callback (NautilusDirectory *directory, NautilusListView *view)
Alexander Larsson's avatar
Alexander Larsson committed
863
{
864
	nautilus_list_model_subdirectory_done_loading (view->details->model, directory);
Alexander Larsson's avatar
Alexander Larsson committed
865 866 867 868 869
}

static void
row_expanded_callback (GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, gpointer callback_data)
{
870
 	NautilusListView *view;
Alexander Larsson's avatar
Alexander Larsson committed
871 872
 	NautilusDirectory *directory;

873
	view = NAUTILUS_LIST_VIEW (callback_data);
Alexander Larsson's avatar
Alexander Larsson committed
874

875
	if (nautilus_list_model_load_subdirectory (view->details->model, path, &directory)) {
876 877 878
		char *uri;

		uri = nautilus_directory_get_uri (directory);
879
		DEBUG ("Row expaded callback for uri %s", uri);
880 881
		g_free (uri);

882
		nautilus_view_add_subdirectory (NAUTILUS_VIEW (view), directory);
883 884
		
		if (nautilus_directory_are_all_files_seen (directory)) {
885
			nautilus_list_model_subdirectory_done_loading (view->details->model,
886 887 888 889 890 891 892 893 894 895
								 directory);
		} else {
			g_signal_connect_object (directory, "done_loading",
						 G_CALLBACK (subdirectory_done_loading_callback),
						 view, 0);
		}
		
		nautilus_directory_unref (directory);
	}
}
Alexander Larsson's avatar
Alexander Larsson committed
896

897 898
struct UnloadDelayData {
	NautilusFile *file;
899
	NautilusDirectory *directory;
900
	NautilusListView *view;
901 902 903 904 905 906 907
};

static gboolean
unload_file_timeout (gpointer data)
{
	struct UnloadDelayData *unload_data = data;
	GtkTreeIter iter;
908
	NautilusListModel *model;
909 910 911 912
	GtkTreePath *path;

	if (unload_data->view != NULL) {
		model = unload_data->view->details->model;
913
		if (nautilus_list_model_get_tree_iter_from_file (model,
914
							   unload_data->file,
915
							   unload_data->directory,
916 917 918 919
							   &iter)) {
			path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
			if (!gtk_tree_view_row_expanded (unload_data->view->details->tree_view,
							 path)) {
920
				nautilus_list_model_unload_subdirectory (model, &iter);
921 922 923
			}
			gtk_tree_path_free (path);
		}
Alexander Larsson's avatar
Alexander Larsson committed
924
	}
925 926 927

	eel_remove_weak_pointer (&unload_data->view);
	
928 929 930 931
	if (unload_data->directory) {
		nautilus_directory_unref (unload_data->directory);
	}
	nautilus_file_unref (unload_data->file);
932 933 934 935 936 937 938
	g_free (unload_data);
	return FALSE;
}

static void
row_collapsed_callback (GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, gpointer callback_data)
{
939
 	NautilusListView *view;
940
 	NautilusFile *file;
941 942
	NautilusDirectory *directory;
	GtkTreeIter parent;
943
	struct UnloadDelayData *unload_data;
944
	GtkTreeModel *model;
945
	char *uri;
946
	
947
	view = NAUTILUS_LIST_VIEW (callback_data);
948
	model = GTK_TREE_MODEL (view->details->model);
949
		
950
	gtk_tree_model_get (model, iter, 
951
			    NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
952
			    -1);
953 954 955 956

	directory = NULL;
	if (gtk_tree_model_iter_parent (model, &parent, iter)) {
		gtk_tree_model_get (model, &parent, 
957
				    NAUTILUS_LIST_MODEL_SUBDIRECTORY_COLUMN, &directory,
958 959
				    -1);
	}
960
	
961 962

	uri = nautilus_file_get_uri (file);
963
	DEBUG ("Row collapsed callback for uri %s", uri);
964 965
	g_free (uri);

966 967 968
	unload_data = g_new (struct UnloadDelayData, 1);
	unload_data->view = view;
	unload_data->file = file;
969
	unload_data->directory = directory;
970 971

	eel_add_weak_pointer (&unload_data->view);
Alexander Larsson's avatar
Alexander Larsson committed
972
	
973
	g_timeout_add_seconds (COLLAPSE_TO_UNLOAD_DELAY,
974 975
			       unload_file_timeout,
			       unload_data);
976 977 978
}

static void
979
subdirectory_unloaded_callback (NautilusListModel *model,
980 981 982
				NautilusDirectory *directory,
				gpointer callback_data)
{
983
	NautilusListView *view;
984
	
985
	g_return_if_fail (NAUTILUS_IS_LIST_MODEL (model));
986 987
	g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory));

988
	view = NAUTILUS_LIST_VIEW(callback_data);
989 990 991 992
	
	g_signal_handlers_disconnect_by_func (directory,
					      G_CALLBACK (subdirectory_done_loading_callback),
					      view);
993
	nautilus_view_remove_subdirectory (NAUTILUS_VIEW (view), directory);
Alexander Larsson's avatar
Alexander Larsson committed
994 995
}

996 997 998
static gboolean
key_press_callback (GtkWidget *widget, GdkEventKey *event, gpointer callback_data)
{
999
	NautilusView *view;
1000
	GdkEventButton button_event = { 0 };
1001
	gboolean handled;
1002 1003 1004 1005
	GtkTreeView *tree_view;
	GtkTreePath *path;

	tree_view = GTK_TREE_VIEW (widget);
1006

1007
	view = NAUTILUS_VIEW (callback_data);
1008
	handled = FALSE;
1009

1010
	switch (event->keyval) {
1011
	case GDK_KEY_F10:
1012
		if (event->state & GDK_CONTROL_MASK) {
1013
			nautilus_view_pop_up_background_context_menu (view, &button_event);
1014
			handled = TRUE;
1015 1016
		}
		break;
1017
	case GDK_KEY_Right:
1018 1019 1020 1021 1022 1023 1024
		gtk_tree_view_get_cursor (tree_view, &path, NULL);
		if (path) {
			gtk_tree_view_expand_row (tree_view, path, FALSE);
			gtk_tree_path_free (path);
		}
		handled = TRUE;
		break;
1025
	case GDK_KEY_Left:
1026 1027
		gtk_tree_view_get_cursor (tree_view, &path, NULL);
		if (path) {
1028 1029 1030 1031 1032 1033 1034 1035 1036
			if (!gtk_tree_view_collapse_row (tree_view, path)) {
				/* if the row is already collapsed or doesn't have any children,
				 * jump to the parent row instead.
				 */
				if ((gtk_tree_path_get_depth (path) > 1) && gtk_tree_path_up (path)) {
					gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
				}
			}

1037 1038 1039 1040
			gtk_tree_path_free (path);
		}
		handled = TRUE;
		break;
1041
	case GDK_KEY_space:
1042
		if (event->state & GDK_CONTROL_MASK) {
1043 1044
			handled = FALSE;
			break;
1045
		}
1046
		if (!gtk_widget_has_focus (GTK_WIDGET (NAUTILUS_LIST_VIEW (view)->details->tree_view))) {
1047 1048
			handled = FALSE;
			break;
1049
		}
1050
		if ((event->state & GDK_SHIFT_MASK) != 0) {
1051
			activate_selected_items_alternate (NAUTILUS_LIST_VIEW (view), NULL, TRUE);
1052
		} else {
1053
			activate_selected_items (NAUTILUS_LIST_VIEW (view));
1054
		}
1055 1056
		handled = TRUE;
		break;
1057 1058
	case GDK_KEY_Return:
	case GDK_KEY_KP_Enter:
1059
		if ((event->state & GDK_SHIFT_MASK) != 0) {
1060
			activate_selected_items_alternate (NAUTILUS_LIST_VIEW (view), NULL, TRUE);
1061
		} else {
1062
			activate_selected_items (NAUTILUS_LIST_VIEW (view));
1063
		}
1064 1065
		handled = TRUE;
		break;
1066
	case GDK_KEY_v:
1067 1068 1069 1070 1071
		/* Eat Control + v to not enable type ahead */
		if ((event->state & GDK_CONTROL_MASK) != 0) {
			handled = TRUE;
		}
		break;
1072 1073

	default:
1074
		handled = FALSE;
1075
	}
1076

1077
	return handled;
1078 1079
}

1080
static void
1081
nautilus_list_view_reveal_selection (NautilusView *view)
1082 1083 1084
{
	GList *selection;

1085
	g_return_if_fail (NAUTILUS_IS_LIST_VIEW (view));
1086

1087
        selection = nautilus_view_get_selection (view);
1088 1089 1090

	/* Make sure at least one of the selected items is scrolled into view */
	if (selection != NULL) {
1091
		NautilusListView *list_view;
1092 1093 1094 1095
		NautilusFile *file;
		GtkTreeIter iter;
		GtkTreePath *path;
		
1096
		list_view = NAUTILUS_LIST_VIEW (view);
1097
		file = selection->data;
1098
		if (nautilus_list_model_get_first_iter_for_file (list_view->details->model, file, &iter)) {
1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109
			path = gtk_tree_model_get_path (GTK_TREE_MODEL (list_view->details->model), &iter);

			gtk_tree_view_scroll_to_cell (list_view->details->tree_view, path, NULL, FALSE, 0.0, 0.0);
			
			gtk_tree_path_free (path);
		}
	}

        nautilus_file_list_free (selection);
}

1110 1111 1112 1113 1114 1115
static gboolean
sort_criterion_changes_due_to_user (GtkTreeView *tree_view)
{
	GList *columns, *p;
	GtkTreeViewColumn *column;
	GSignalInvocationHint *ihint;
1116
	gboolean ret;
1117

1118 1119
	ret = FALSE;

1120 1121 1122 1123 1124
	columns = gtk_tree_view_get_columns (tree_view);
	for (p = columns; p != NULL; p = p->next) {
		column = p->data;
		ihint = g_signal_get_invocation_hint (column);
		if (ihint != NULL) {
1125 1126
			ret = TRUE;
			break;
1127 1128
		}
	}
1129
	g_list_free (columns);
1130

1131
	return ret;
1132 1133
}

Diego González's avatar
Diego González committed
1134
static void
1135
sort_column_changed_callback (GtkTreeSortable *sortable, 
1136
			      NautilusListView *view)
Diego González's avatar
Diego González committed
1137 1138
{
	NautilusFile *file;
1139
	gint sort_column_id, default_sort_column_id;
1140
	GtkSortType reversed;
1141
	GQuark sort_attr, default_sort_attr;
1142
	char *reversed_attr, *default_reversed_attr;
1143
	gboolean default_sort_reversed;
Diego González's avatar
Diego González committed
1144

1145
	file = nautilus_view_get_directory_as_file (NAUTILUS_VIEW (view));
Diego González's avatar
Diego González committed
1146

1147
	gtk_tree_sortable_get_sort_column_id (sortable, &sort_column_id, &reversed);
1148
	sort_attr = nautilus_list_model_get_attribute_from_sort_column_id (view->details->model, sort_column_id);
1149

1150
	default_sort_column_id = nautilus_list_model_get_sort_column_id_from_attribute (view->details->model,
1151
										  g_quark_from_string (get_default_sort_order (file, &default_sort_reversed)));
1152
	default_sort_attr = nautilus_list_model_get_attribute_from_sort_column_id (view->details->model, default_sort_column_id);
1153
	nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_COLUMN,
1154
				    g_quark_to_string (default_sort_attr), g_quark_to_string (sort_attr));
Diego González's avatar
Diego González committed
1155

1156
	default_reversed_attr = (default_sort_reversed ? "true" : "false");
1157 1158 1159 1160 1161 1162 1163 1164

	if (view->details->last_sort_attr != sort_attr &&
	    sort_criterion_changes_due_to_user (view->details->tree_view)) {
		/* at this point, the sort order is always GTK_SORT_ASCENDING, if the sort column ID
		 * switched. Invert the sort order, if it's the default criterion with a reversed preference,
		 * or if it makes sense for the attribute (i.e. date). */
		if (sort_attr == default_sort_attr) {
			/* use value from preferences */
1165 1166
			reversed = g_settings_get_boolean (nautilus_preferences,
							   NAUTILUS_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER);
1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180
		} else {
			reversed = nautilus_file_is_date_sort_attribute_q (sort_attr);
		}

		if (reversed) {
			g_signal_handlers_block_by_func (sortable, sort_column_changed_callback, view);
			gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (view->details->model),
							      sort_column_id,
							      GTK_SORT_DESCENDING);
			g_signal_handlers_unblock_by_func (sortable, sort_column_changed_callback, view);
		}
	}


1181 1182 1183
	reversed_attr = (reversed ? "true" : "false");
	nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_REVERSED,
				    default_reversed_attr, reversed_attr);
1184

1185
	/* Make sure selected item(s) is visible after sort */
1186
	nautilus_list_view_reveal_selection (NAUTILUS_VIEW (view));
1187 1188

	view->details->last_sort_attr = sort_attr;
Diego González's avatar
Diego González committed
1189 1190
}

1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201
static void
editable_focus_out_cb (GtkWidget *widget,
		       GdkEvent *event,
		       gpointer user_data)
{
	NautilusListView *view = user_data;

	nautilus_view_unfreeze_updates (NAUTILUS_VIEW (view));
	view->details->editable_widget = NULL;
}

1202 1203 1204 1205
static void
cell_renderer_editing_started_cb (GtkCellRenderer *renderer,
				  GtkCellEditable *editable,
				  const gchar *path_str,
1206
				  NautilusListView *list_view)
1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217
{
	GtkEntry *entry;

	entry = GTK_ENTRY (editable);
	list_view->details->editable_widget = editable;

	/* Free a previously allocated original_name */
	g_free (list_view->details->original_name);

	list_view->details->original_name = g_strdup (gtk_entry_get_text (entry));

1218 1219 1220
	g_signal_connect (entry, "focus-out-event",
			  G_CALLBACK (editable_focus_out_cb), list_view);

1221 1222
	nautilus_clipboard_set_up_editable
		(GTK_EDITABLE (entry),
1223
		 nautilus_view_get_ui_manager (NAUTILUS_VIEW (list_view)),
1224 1225 1226
		 FALSE);
}

1227 1228
static void
cell_renderer_editing_canceled (GtkCellRendererText *cell,
1229
				NautilusListView          *view)
1230
{
1231 1232
	view->details->editable_widget = NULL;

1233
	nautilus_view_unfreeze_updates (NAUTILUS_VIEW (view));
1234 1235
}

1236 1237 1238 1239
static void
cell_renderer_edited (GtkCellRendererText *cell,
		      const char          *path_str,
		      const char          *new_text,
1240
		      NautilusListView          *view)
1241 1242 1243 1244
{
	GtkTreePath *path;
	NautilusFile *file;
	GtkTreeIter iter;
1245

1246 1247
	view->details->editable_widget = NULL;

1248 1249 1250 1251 1252 1253 1254
	/* Don't allow a rename with an empty string. Revert to original 
	 * without notifying the user.
	 */
	if (new_text[0] == '\0') {
		g_object_set (G_OBJECT (view->details->file_name_cell),
			      "editable", FALSE,
			      NULL);
1255
		nautilus_view_unfreeze_updates (NAUTILUS_VIEW (view));
1256 1257 1258
		return;
	}
	
1259 1260 1261 1262 1263 1264 1265 1266 1267
	path = gtk_tree_path_new_from_string (path_str);

	gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->model),
				 &iter, path);

	gtk_tree_path_free (path);
	
	gtk_tree_model_get (GTK_TREE_MODEL (view->details->model),
			    &iter,
1268
			    NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
1269 1270
			    -1);

1271 1272
	/* Only rename if name actually changed */
	if (strcmp (new_text, view->details->original_name) != 0) {
1273 1274
		view->details->renaming_file = nautilus_file_ref (file);
		view->details->rename_done = FALSE;
1275
		nautilus_rename_file (file, new_text, nautilus_list_view_rename_callback, g_object_ref (view));
1276 1277
		g_free (view->details->original_name);
		view->details->original_name = g_strdup (new_text);
1278
	}
1279 1280
	
	nautilus_file_unref (file);
1281 1282 1283 1284 1285 1286

	/*We're done editing - make the filename-cells readonly again.*/
	g_object_set (G_OBJECT