[nautilus] list-view: add back a treeview option



commit 9fd0e21817ef1392a2d1f81308ec835dd2fa373d
Author: Cosimo Cecchi <cosimoc gnome org>
Date:   Mon Feb 11 15:17:07 2013 -0500

    list-view: add back a treeview option
    
    Re-add most of the code that handled the tree view in NautilusListModel
    and NautilusListView, cleaned up and rebased to master.
    
    The tree mode will be activated by a checkbox in the Preferences dialog.

 libnautilus-private/nautilus-tree-view-drag-dest.c |  129 ++++-
 src/nautilus-list-model.c                          |   63 ++-
 src/nautilus-list-view.c                           |  526 +++++++++++++++-----
 3 files changed, 544 insertions(+), 174 deletions(-)
---
diff --git a/libnautilus-private/nautilus-tree-view-drag-dest.c b/libnautilus-private/nautilus-tree-view-drag-dest.c
index 67d36ff..9861688 100644
--- a/libnautilus-private/nautilus-tree-view-drag-dest.c
+++ b/libnautilus-private/nautilus-tree-view-drag-dest.c
@@ -34,6 +34,7 @@
 
 #include "nautilus-file-dnd.h"
 #include "nautilus-file-changes-queue.h"
+#include "nautilus-global-preferences.h"
 #include "nautilus-link.h"
 
 #include <gtk/gtk.h>
@@ -45,6 +46,7 @@
 #include "nautilus-debug.h"
 
 #define AUTO_SCROLL_MARGIN 20
+#define HOVER_EXPAND_TIMEOUT 1
 
 struct _NautilusTreeViewDragDestDetails {
 	GtkTreeView *tree_view;
@@ -59,6 +61,7 @@ struct _NautilusTreeViewDragDestDetails {
 	guint hover_id;
 	guint highlight_id;
 	guint scroll_id;
+	guint expand_id;
 
 	char *direct_save_uri;
 	char *target_uri;
@@ -147,6 +150,33 @@ remove_scroll_timeout (NautilusTreeViewDragDest *dest)
 	}
 }
 
+static int
+expand_timeout (gpointer data)
+{
+       GtkTreeView *tree_view;
+       GtkTreePath *drop_path;
+
+       tree_view = GTK_TREE_VIEW (data);
+
+       gtk_tree_view_get_drag_dest_row (tree_view, &drop_path, NULL);
+
+       if (drop_path) {
+               gtk_tree_view_expand_row (tree_view, drop_path, FALSE);
+               gtk_tree_path_free (drop_path);
+       }
+
+       return FALSE;
+}
+
+static void
+remove_expand_timer (NautilusTreeViewDragDest *dest)
+{
+       if (dest->details->expand_id) {
+               g_source_remove (dest->details->expand_id);
+               dest->details->expand_id = 0;
+       }
+}
+
 static gboolean
 highlight_draw (GtkWidget *widget,
 		cairo_t   *cr,
@@ -281,6 +311,7 @@ free_drag_data (NautilusTreeViewDragDest *dest)
 	dest->details->target_uri = NULL;
 
 	remove_hover_timer (dest);
+	remove_expand_timer (dest);
 }
 
 static gboolean
@@ -322,6 +353,33 @@ check_hover_timer (NautilusTreeViewDragDest *dest,
 	}
 }
 
+static void
+check_expand_timer (NautilusTreeViewDragDest *dest,
+		    GtkTreePath *drop_path,
+		    GtkTreePath *old_drop_path)
+{
+	GtkTreeModel *model;
+	GtkTreeIter drop_iter;
+
+	model = gtk_tree_view_get_model (dest->details->tree_view);
+
+	if (drop_path == NULL ||
+	    (old_drop_path != NULL && gtk_tree_path_compare (old_drop_path, drop_path) != 0)) {
+		remove_expand_timer (dest);
+	}
+
+	if (dest->details->expand_id == 0 &&
+	    drop_path != NULL) {
+		gtk_tree_model_get_iter (model, &drop_iter, drop_path);
+		if (gtk_tree_model_iter_has_child (model, &drop_iter)) {
+			dest->details->expand_id =
+				g_timeout_add_seconds (HOVER_EXPAND_TIMEOUT,
+						       expand_timeout,
+						       dest->details->tree_view);
+		}
+	}
+}
+
 static char *
 get_root_uri (NautilusTreeViewDragDest *dest)
 {
@@ -354,6 +412,48 @@ file_for_path (NautilusTreeViewDragDest *dest, GtkTreePath *path)
 	return file;
 }
 
+static char *
+get_drop_target_uri_for_path (NautilusTreeViewDragDest *dest,
+			      GtkTreePath *path)
+{
+	NautilusFile *file;
+	char *target = NULL;
+	gboolean can;
+
+	file = file_for_path (dest, path);
+	if (file == NULL) {
+		return NULL;
+	}
+	can = nautilus_drag_can_accept_info (file,
+					     dest->details->drag_type,
+					     dest->details->drag_list);
+	if (can) {
+		target = nautilus_file_get_drop_target_uri (file);
+	}
+	nautilus_file_unref (file);
+
+	return target;
+}
+
+static void
+check_hover_expand_timer (NautilusTreeViewDragDest *dest,
+			  GtkTreePath *path,
+			  GtkTreePath *drop_path,
+			  GtkTreePath *old_drop_path)
+{
+	gboolean use_tree = g_settings_get_boolean (nautilus_list_view_preferences,
+						    NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE);
+
+	if (use_tree) {
+		check_expand_timer (dest, drop_path, old_drop_path);
+	} else {
+		char *uri;
+		uri = get_drop_target_uri_for_path (dest, path);
+		check_hover_timer (dest, uri);
+		g_free (uri);
+	}
+}
+
 static GtkTreePath *
 get_drop_path (NautilusTreeViewDragDest *dest,
 	       GtkTreePath *path)
@@ -389,29 +489,6 @@ get_drop_path (NautilusTreeViewDragDest *dest,
 	return ret;
 }
 
-static char *
-get_drop_target_uri_for_path (NautilusTreeViewDragDest *dest,
-			      GtkTreePath *path)
-{
-	NautilusFile *file;
-	char *target = NULL;
-	gboolean can;
-
-	file = file_for_path (dest, path);
-	if (file == NULL) {
-		return NULL;
-	}
-	can = nautilus_drag_can_accept_info (file,
-					     dest->details->drag_type,
-					     dest->details->drag_list);
-	if (can) {
-		target = nautilus_file_get_drop_target_uri (file);
-	}
-	nautilus_file_unref (file);
-	
-	return target;
-}
-
 static guint
 get_drop_action (NautilusTreeViewDragDest *dest, 
 		 GdkDragContext *context,
@@ -509,14 +586,12 @@ drag_motion_callback (GtkWidget *widget,
 					 NULL);
 	
 	if (action) {
-		char *uri;
 		set_drag_dest_row (dest, drop_path);
-		uri = get_drop_target_uri_for_path (dest, path);
-		check_hover_timer (dest, uri);
-		g_free (uri);
+		check_hover_expand_timer (dest, path, drop_path, old_drop_path);
 	} else {
 		clear_drag_dest_row (dest);
 		remove_hover_timer (dest);
+		remove_expand_timer (dest);
 	}
 
 	if (path) {
diff --git a/src/nautilus-list-model.c b/src/nautilus-list-model.c
index 6ddcac6..74faf7f 100644
--- a/src/nautilus-list-model.c
+++ b/src/nautilus-list-model.c
@@ -397,31 +397,39 @@ nautilus_list_model_iter_children (GtkTreeModel *tree_model, GtkTreeIter *iter,
 {
 	NautilusListModel *model;
 	GSequence *files;
+	FileEntry *file_entry;
 
 	model = (NautilusListModel *)tree_model;
 
-	/* this is a list, nodes have no children */
-	if (parent != NULL) {
-		iter->stamp = 0;
-		return FALSE;
+	if (parent == NULL) {
+		files = model->details->files;
+	} else {
+		file_entry = g_sequence_get (parent->user_data);
+		files = file_entry->files;
 	}
 
-	files = model->details->files;
-
-	if (g_sequence_get_length (files) > 0) {
-		iter->stamp = model->details->stamp;
-		iter->user_data = g_sequence_get_begin_iter (files);
-		return TRUE;
-	} else {
-		iter->stamp = 0;
+	if (files == NULL || g_sequence_get_length (files) == 0) {
 		return FALSE;
 	}
+
+	iter->stamp = model->details->stamp;
+	iter->user_data = g_sequence_get_begin_iter (files);
+
+	return TRUE;
 }
 
 static gboolean
 nautilus_list_model_iter_has_child (GtkTreeModel *tree_model, GtkTreeIter *iter)
 {
-	return FALSE;
+	FileEntry *file_entry;
+
+	if (iter == NULL) {
+		return !nautilus_list_model_is_empty (NAUTILUS_LIST_MODEL (tree_model));
+	}
+
+	file_entry = g_sequence_get (iter->user_data);
+
+	return (file_entry->files != NULL && g_sequence_get_length (file_entry->files) > 0);
 }
 
 static int
@@ -429,13 +437,15 @@ nautilus_list_model_iter_n_children (GtkTreeModel *tree_model, GtkTreeIter *iter
 {
 	NautilusListModel *model;
 	GSequence *files;
+	FileEntry *file_entry;
 
 	model = (NautilusListModel *)tree_model;
 
 	if (iter == NULL) {
 		files = model->details->files;
 	} else {
-		return 0;
+		file_entry = g_sequence_get (iter->user_data);
+		files = file_entry->files;
 	}
 
 	return g_sequence_get_length (files);
@@ -447,16 +457,17 @@ nautilus_list_model_iter_nth_child (GtkTreeModel *tree_model, GtkTreeIter *iter,
 	NautilusListModel *model;
 	GSequenceIter *child;
 	GSequence *files;
+	FileEntry *file_entry;
 
 	model = (NautilusListModel *)tree_model;
 
-	iter->stamp = 0;
-
 	if (parent != NULL) {
-		return FALSE;
+		file_entry = g_sequence_get (parent->user_data);
+		files = file_entry->files;
+	} else {
+		files = model->details->files;
 	}
 
-	files = model->details->files;
 	child = g_sequence_get_iter_at_pos (files, n);
 
 	if (g_sequence_iter_is_end (child)) {
@@ -472,9 +483,21 @@ nautilus_list_model_iter_nth_child (GtkTreeModel *tree_model, GtkTreeIter *iter,
 static gboolean
 nautilus_list_model_iter_parent (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child)
 {
-	iter->stamp = 0;
+	NautilusListModel *model;
+	FileEntry *file_entry;
 
-	return FALSE;
+	model = (NautilusListModel *)tree_model;
+
+	file_entry = g_sequence_get (child->user_data);
+
+	if (file_entry->parent == NULL) {
+		return FALSE;
+	}
+
+	iter->stamp = model->details->stamp;
+	iter->user_data = file_entry->parent->ptr;
+
+	return TRUE;
 }
 
 static GSequenceIter *
diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c
index 9d31ba6..11bd82d 100644
--- a/src/nautilus-list-view.c
+++ b/src/nautilus-list-view.c
@@ -88,6 +88,7 @@ struct NautilusListViewDetails {
 	int drag_y;
 
 	gboolean drag_started;
+	gboolean ignore_button_release;
 	gboolean row_selected_on_button_down;
 	gboolean menus_ready;
 	gboolean active;
@@ -118,6 +119,9 @@ struct SelectionForeachData {
  */
 #define LIST_VIEW_MINIMUM_ROW_HEIGHT	28
 
+/* We wait two seconds after row is collapsed to unload the subdirectory */
+#define COLLAPSE_TO_UNLOAD_DELAY 2
+
 /* Wait for the rename to end when activating a file being renamed */
 #define WAIT_FOR_RENAME_ON_ACTIVATE 200
 
@@ -634,13 +638,16 @@ button_press_callback (GtkWidget *widget, GdkEventButton *event, gpointer callba
 	NautilusListView *view;
 	GtkTreeView *tree_view;
 	GtkTreePath *path;
-	gboolean call_parent;
 	GtkTreeSelection *selection;
 	GtkWidgetClass *tree_view_class;
 	gint64 current_time;
 	static gint64 last_click_time = 0;
 	static int click_count = 0;
 	int double_click_time;
+	int expander_size, horizontal_separator;
+	gboolean call_parent, on_expander;
+	gboolean is_simple_click, path_selected;
+	NautilusFile *file;
 
 	view = NAUTILUS_LIST_VIEW (callback_data);
 	tree_view = GTK_TREE_VIEW (widget);
@@ -681,141 +688,146 @@ button_press_callback (GtkWidget *widget, GdkEventButton *event, gpointer callba
 		return TRUE;
 	}
 
-	call_parent = TRUE;
-	if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y,
-					   &path, NULL, NULL, NULL)) {
-		/* Keep track of path of last click so double clicks only happen
-		 * on the same item */
-		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->ignore_button_release = FALSE;
+	is_simple_click = ((event->button == 1 || event->button == 2) && (event->type == GDK_BUTTON_PRESS));
+
+	/* No item at this position */
+	if (!gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y,
+					    &path, NULL, NULL, NULL)) {
+		if (is_simple_click) {
+			g_clear_pointer (&view->details->double_click_path[1], gtk_tree_path_free);
 			view->details->double_click_path[1] = view->details->double_click_path[0];
-			view->details->double_click_path[0] = gtk_tree_path_copy (path);
+			view->details->double_click_path[0] = NULL;
 		}
-		if (event->type == GDK_2BUTTON_PRESS) {
-			/* Double clicking does not trigger a D&D action. */
-			view->details->drag_button = 0;
-			if (view->details->double_click_path[1] &&
-			    gtk_tree_path_compare (view->details->double_click_path[0], view->details->double_click_path[1]) == 0) {
-				/* NOTE: Activation can actually destroy the view if we're switching */
-				if (!button_event_modifies_selection (event)) {
-					if ((event->button == 1 || event->button == 3)) {
-						activate_selected_items (view);
-					} else if (event->button == 2) {
-						activate_selected_items_alternate (view, NULL, TRUE);
-					}
-				} else if (event->button == 1 &&
-					   (event->state & GDK_SHIFT_MASK) != 0) {
-					NautilusFile *file;
-					file = nautilus_list_model_file_for_path (view->details->model, path);
-					if (file != NULL) {
-						activate_selected_items_alternate (view, file, TRUE);
-						nautilus_file_unref (file);
-					}
+
+		/* 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));
+		tree_view_class->button_press_event (widget, event);
+
+		if (event->button == 3) {
+			do_popup_menu (widget, view, event);
+		}
+
+		return TRUE;
+	}
+
+	call_parent = TRUE;
+	path_selected = gtk_tree_selection_path_is_selected (selection, path);
+
+	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);
+
+	/* Keep track of path of last click so double clicks only happen
+	 * on the same item */
+	if (is_simple_click) {
+		g_clear_pointer (&view->details->double_click_path[1], gtk_tree_path_free);
+		view->details->double_click_path[1] = view->details->double_click_path[0];
+		view->details->double_click_path[0] = gtk_tree_path_copy (path);
+	}
+
+	if (event->type == GDK_2BUTTON_PRESS) {
+		/* Double clicking does not trigger a D&D action. */
+		view->details->drag_button = 0;
+
+		/* NOTE: Activation can actually destroy the view if we're switching */
+		if (!on_expander &&
+		    view->details->double_click_path[1] &&
+		    gtk_tree_path_compare (view->details->double_click_path[0], view->details->double_click_path[1]) == 0) {
+			if ((event->button == 1) && button_event_modifies_selection (event)) {
+				file = nautilus_list_model_file_for_path (view->details->model, path);
+				if (file != NULL) {
+					activate_selected_items_alternate (view, file, TRUE);
+					nautilus_file_unref (file);
 				}
 			} else {
-				tree_view_class->button_press_event (widget, event);
+				if ((event->button == 1 || event->button == 3)) {
+					activate_selected_items (view);
+				} else if (event->button == 2) {
+					activate_selected_items_alternate (view, NULL, TRUE);
+				}
 			}
 		} 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. */
+			tree_view_class->button_press_event (widget, event);
+		}
+	} 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 && path_selected) {
+			call_parent = FALSE;
+		}
 
-			if (event->button == 3 && gtk_tree_selection_path_is_selected (selection, path)) {
-				call_parent = FALSE;
-			}
+		if ((event->button == 1 || event->button == 2) &&
+		    ((event->state & GDK_CONTROL_MASK) != 0 || (event->state & GDK_SHIFT_MASK) == 0)) {
+			view->details->row_selected_on_button_down = path_selected;
+
+			if (path_selected) {
+				call_parent = on_expander;
+				view->details->ignore_button_release = on_expander;
+			} else if ((event->state & GDK_CONTROL_MASK) != 0) {
+				GList *selected_rows, *l;
 
-			if ((event->button == 1 || event->button == 2) &&
-			    ((event->state & GDK_CONTROL_MASK) != 0 ||
-			     (event->state & GDK_SHIFT_MASK) == 0)) {			
-				view->details->row_selected_on_button_down = gtk_tree_selection_path_is_selected (selection, path);
-				if (view->details->row_selected_on_button_down) {
-					call_parent = FALSE;
-				} else if ((event->state & GDK_CONTROL_MASK) != 0) {
-					GList *selected_rows;
-					GList *l;
-
-					call_parent = FALSE;
-					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);
-						}
+				call_parent = FALSE;
+				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);
 					}
-					selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
+				} 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);
+				/* 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);
+				/* So select it again */
+				for (l = selected_rows; l != NULL; l = l->next) {
+					gtk_tree_selection_select_path (selection, l->data);
 				}
+				g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free);
+			} else {
+				view->details->ignore_button_release = on_expander;
 			}
+		}
 		
-			if (call_parent) {
-				g_signal_handlers_block_by_func (tree_view,
-								 row_activated_callback,
-								 view);
-
-				tree_view_class->button_press_event (widget, event);
-
-				g_signal_handlers_unblock_by_func (tree_view,
-								   row_activated_callback,
-								   view);
-			} 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);
-			}
+		if (call_parent) {
+			g_signal_handlers_block_by_func (tree_view, row_activated_callback, view);
+			tree_view_class->button_press_event (widget, event);
+			g_signal_handlers_unblock_by_func (tree_view, row_activated_callback, view);
+		} else if (path_selected) {
+			gtk_widget_grab_focus (widget);
 		}
 
-		gtk_tree_path_free (path);
-	} else {
-		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;
+		if (is_simple_click) {
+			view->details->drag_started = FALSE;
+			view->details->drag_button = event->button;
+			view->details->drag_x = event->x;
+			view->details->drag_y = event->y;
 		}
-		/* 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));
-		tree_view_class->button_press_event (widget, event);
 
 		if (event->button == 3) {
 			do_popup_menu (widget, view, event);
 		}
 	}
-	
+
+	gtk_tree_path_free (path);
+
 	/* We chained to the default handler in this method, so never
 	 * let the default handler run */ 
 	return TRUE;
@@ -832,7 +844,8 @@ button_release_callback (GtkWidget *widget,
 
 	if (event->button == view->details->drag_button) {
 		stop_drag_check (view);
-		if (!view->details->drag_started) {
+		if (!view->details->drag_started &&
+		    !view->details->ignore_button_release) {
 			nautilus_list_view_did_not_drag (view, event);
 		}
 	}
@@ -857,11 +870,148 @@ subdirectory_done_loading_callback (NautilusDirectory *directory, NautilusListVi
 	nautilus_list_model_subdirectory_done_loading (view->details->model, directory);
 }
 
-struct UnloadDelayData {
+static void
+row_expanded_callback (GtkTreeView *treeview,
+		       GtkTreeIter *iter,
+		       GtkTreePath *path,
+		       gpointer callback_data)
+{
+	NautilusListView *view;
+	NautilusDirectory *directory;
+	char *uri;
+
+	view = NAUTILUS_LIST_VIEW (callback_data);
+
+	if (!nautilus_list_model_load_subdirectory (view->details->model, path, &directory)) {
+		return;
+	}
+
+	uri = nautilus_directory_get_uri (directory);
+	DEBUG ("Row expaded callback for uri %s", uri);
+	g_free (uri);
+
+	nautilus_view_add_subdirectory (NAUTILUS_VIEW (view), directory);
+
+	if (nautilus_directory_are_all_files_seen (directory)) {
+		nautilus_list_model_subdirectory_done_loading (view->details->model,
+							       directory);
+	} else {
+		g_signal_connect_object (directory, "done-loading",
+					 G_CALLBACK (subdirectory_done_loading_callback),
+					 view, 0);
+	}
+
+	nautilus_directory_unref (directory);
+}
+
+typedef struct {
 	NautilusFile *file;
 	NautilusDirectory *directory;
 	NautilusListView *view;
-};
+} UnloadDelayData;
+
+static void
+unload_delay_data_free (UnloadDelayData *unload_data)
+{
+	if (unload_data->view != NULL) {
+		g_object_remove_weak_pointer (G_OBJECT (unload_data->view),
+					      (gpointer *) &unload_data->view);
+	}
+
+	nautilus_directory_unref (unload_data->directory);
+	nautilus_file_unref (unload_data->file);
+
+	g_slice_free (UnloadDelayData, unload_data);
+}
+
+static UnloadDelayData *
+unload_delay_data_new (NautilusFile *file,
+		       NautilusDirectory *parent_directory,
+		       NautilusListView *view)
+{
+	UnloadDelayData *unload_data;
+
+	unload_data = g_slice_new0 (UnloadDelayData);
+	unload_data->view = view;
+	unload_data->file = nautilus_file_ref (file);
+	unload_data->directory = nautilus_directory_ref (parent_directory);
+
+	g_object_add_weak_pointer (G_OBJECT (unload_data->view),
+				   (gpointer *) &unload_data->view);
+
+	return unload_data;
+}
+
+static gboolean
+unload_file_timeout (gpointer data)
+{
+	UnloadDelayData *unload_data = data;
+	GtkTreeIter iter;
+	NautilusListModel *model;
+	GtkTreePath *path;
+
+	if (unload_data->view == NULL) {
+		goto out;
+	}
+
+	model = unload_data->view->details->model;
+	if (nautilus_list_model_get_tree_iter_from_file (model,
+							 unload_data->file,
+							 unload_data->directory,
+							 &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)) {
+			nautilus_list_model_unload_subdirectory (model, &iter);
+		}
+		gtk_tree_path_free (path);
+	}
+
+ out:
+	unload_delay_data_free (unload_data);
+	return FALSE;
+}
+
+static void
+row_collapsed_callback (GtkTreeView *treeview,
+			GtkTreeIter *iter,
+			GtkTreePath *path,
+			gpointer callback_data)
+{
+	NautilusListView *view;
+	NautilusFile *file;
+	NautilusDirectory *directory;
+	GtkTreeIter parent;
+	UnloadDelayData *unload_data;
+	GtkTreeModel *model;
+	char *uri;
+
+	view = NAUTILUS_LIST_VIEW (callback_data);
+	model = GTK_TREE_MODEL (view->details->model);
+
+	gtk_tree_model_get (model, iter,
+			    NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+			    -1);
+
+	uri = nautilus_file_get_uri (file);
+	DEBUG ("Row collapsed callback for uri %s", uri);
+	g_free (uri);
+
+	directory = NULL;
+	if (gtk_tree_model_iter_parent (model, &parent, iter)) {
+		gtk_tree_model_get (model, &parent,
+				    NAUTILUS_LIST_MODEL_SUBDIRECTORY_COLUMN, &directory,
+				    -1);
+	}
+
+	unload_data = unload_delay_data_new (file, directory, view);
+	g_timeout_add_seconds (COLLAPSE_TO_UNLOAD_DELAY,
+			       unload_file_timeout,
+			       unload_data);
+
+	nautilus_file_unref (file);
+	nautilus_directory_unref (directory);
+}
 
 static void
 subdirectory_unloaded_callback (NautilusListModel *model,
@@ -887,6 +1037,10 @@ key_press_callback (GtkWidget *widget, GdkEventKey *event, gpointer callback_dat
 	NautilusView *view;
 	GdkEventButton button_event = { 0 };
 	gboolean handled;
+	GtkTreeView *tree_view;
+	GtkTreePath *path;
+
+	tree_view = GTK_TREE_VIEW (widget);
 
 	view = NAUTILUS_VIEW (callback_data);
 	handled = FALSE;
@@ -898,6 +1052,30 @@ key_press_callback (GtkWidget *widget, GdkEventKey *event, gpointer callback_dat
 			handled = TRUE;
 		}
 		break;
+	case GDK_KEY_Right:
+		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;
+	case GDK_KEY_Left:
+		gtk_tree_view_get_cursor (tree_view, &path, NULL);
+		if (path) {
+			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);
+				}
+			}
+
+			gtk_tree_path_free (path);
+		}
+		handled = TRUE;
+		break;
 	case GDK_KEY_space:
 		if (event->state & GDK_CONTROL_MASK) {
 			handled = FALSE;
@@ -1447,6 +1625,10 @@ create_and_set_up_tree_view (NautilusListView *view)
 				 G_CALLBACK (key_press_callback), view, 0);
 	g_signal_connect_object (view->details->tree_view, "popup-menu",
                                  G_CALLBACK (popup_menu_callback), view, 0);
+	g_signal_connect_object (view->details->tree_view, "row-expanded",
+                                 G_CALLBACK (row_expanded_callback), view, 0);
+	g_signal_connect_object (view->details->tree_view, "row-collapsed",
+                                 G_CALLBACK (row_collapsed_callback), view, 0);
 	g_signal_connect_object (view->details->tree_view, "row-activated",
                                  G_CALLBACK (row_activated_callback), view, 0);
 	
@@ -1832,6 +2014,70 @@ nautilus_list_view_file_changed (NautilusView *view, NautilusFile *file, Nautilu
 	}
 }
 
+typedef struct {
+	GtkTreePath *path;
+	gboolean is_common;
+	gboolean is_root;
+} HasCommonParentData;
+
+static void
+tree_selection_has_common_parent_foreach_func (GtkTreeModel *model,
+						GtkTreePath *path,
+						GtkTreeIter *iter,
+						gpointer user_data)
+{
+	HasCommonParentData *data;
+	GtkTreePath *parent_path;
+	gboolean has_parent;
+
+	data = (HasCommonParentData *) user_data;
+
+	parent_path = gtk_tree_path_copy (path);
+	gtk_tree_path_up (parent_path);
+
+	has_parent = (gtk_tree_path_get_depth (parent_path) > 0) ? TRUE : FALSE;
+
+	if (!has_parent) {
+		data->is_root = TRUE;
+	}
+
+	if (data->is_common && !data->is_root) {
+		if (data->path == NULL) {
+			data->path = gtk_tree_path_copy (parent_path);
+		} else if (gtk_tree_path_compare (data->path, parent_path) != 0) {
+			data->is_common = FALSE;
+		}
+	}
+
+	gtk_tree_path_free (parent_path);
+}
+
+static void
+tree_selection_has_common_parent (GtkTreeSelection *selection,
+				  gboolean *is_common,
+				  gboolean *is_root)
+{
+	HasCommonParentData data;
+
+	g_assert (is_common != NULL);
+	g_assert (is_root != NULL);
+
+	data.path = NULL;
+	data.is_common = *is_common = TRUE;
+	data.is_root = *is_root = FALSE;
+
+	gtk_tree_selection_selected_foreach (selection,
+					     tree_selection_has_common_parent_foreach_func,
+					     &data);
+
+	*is_common = data.is_common;
+	*is_root = data.is_root;
+
+	if (data.path != NULL) {
+		gtk_tree_path_free (data.path);
+	}
+}
+
 static char *
 nautilus_list_view_get_backing_uri (NautilusView *view)
 {
@@ -1853,12 +2099,14 @@ nautilus_list_view_get_backing_uri (NautilusView *view)
 
 	g_assert (list_model);
 
-	/* We currently handle two common cases here:
+	/* We currently handle three common cases here:
 	 * (a) if the selection contains non-filesystem items (i.e., the
 	 *     "(Empty)" label), we return the uri of the parent.
-	 * (b) if the selection consists of either exactly one item or
-	 *     multiple items in the same directory, we return the URI
-	 *     of the common parent.
+	 * (b) if the selection consists of exactly one _expanded_ directory, we
+	 *     return its URI.
+	 * (c) if the selection consists of either exactly one item which is not
+	 *     an expanded directory) or multiple items in the same directory,
+	 *     we return the URI of the common parent.
 	 */
 
 	uri = NULL;
@@ -1872,21 +2120,45 @@ nautilus_list_view_get_backing_uri (NautilusView *view)
 		path = (GtkTreePath *) paths->data;
 
 		file = nautilus_list_model_file_for_path (list_model, path);
+		if (file == NULL) {
+			/* The selected item is a label, not a file */
+			gtk_tree_path_up (path);
+			file = nautilus_list_model_file_for_path (list_model, path);
+		}
+
+		if (file != NULL) {
+			if (nautilus_file_is_directory (file) &&
+			    gtk_tree_view_row_expanded (tree_view, path)) {
+				uri = nautilus_file_get_uri (file);
+			}
+			nautilus_file_unref (file);
+		}
+
 		gtk_tree_path_free (path);
 		g_list_free (paths);
 	}
 
 	if (uri == NULL && length > 0) {
-		paths = gtk_tree_selection_get_selected_rows (selection, NULL);
-		path = (GtkTreePath *) paths->data;
 
-		file = nautilus_list_model_file_for_path (list_model, path);
-		g_assert (file != NULL);
-		uri = nautilus_file_get_parent_uri (file);
-		nautilus_file_unref (file);
+		gboolean is_common, is_root;
 
-		g_list_foreach (paths, (GFunc) gtk_tree_path_free, NULL);
-		g_list_free (paths);
+		/* Check that all the selected items belong to the same
+		 * directory and that directory is not the root directory (which
+		 * is handled by NautilusView::get_backing_directory.) */
+
+		tree_selection_has_common_parent (selection, &is_common, &is_root);
+
+		if (is_common && !is_root) {
+			paths = gtk_tree_selection_get_selected_rows (selection, NULL);
+			path = (GtkTreePath *) paths->data;
+
+			file = nautilus_list_model_file_for_path (list_model, path);
+			g_assert (file != NULL);
+			uri = nautilus_file_get_parent_uri (file);
+			nautilus_file_unref (file);
+
+			g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
+		}
 	}
 
 	if (uri != NULL) {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]