diff options
Diffstat (limited to 'src/file-manager/fm-list-view.c')
-rw-r--r-- | src/file-manager/fm-list-view.c | 3415 |
1 files changed, 3415 insertions, 0 deletions
diff --git a/src/file-manager/fm-list-view.c b/src/file-manager/fm-list-view.c new file mode 100644 index 00000000..1c82af9b --- /dev/null +++ b/src/file-manager/fm-list-view.c @@ -0,0 +1,3415 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-list-view.c - implementation of list view of directory. + + Copyright (C) 2000 Eazel, Inc. + Copyright (C) 2001, 2002 Anders Carlsson <[email protected]> + + The Mate 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 Mate 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 Mate 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 <[email protected]> + Anders Carlsson <[email protected]> + David Emory Watson <[email protected]> +*/ + +#include <config.h> +#include "fm-list-view.h" + +#include <string.h> +#include "fm-error-reporting.h" +#include "fm-list-model.h" +#include <string.h> +#include <eel/eel-vfs-extensions.h> +#include <eel/eel-gdk-extensions.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gtk-macros.h> +#include <gdk/gdk.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> +#include <libegg/eggtreemultidnd.h> +#include <glib/gi18n.h> +#include <glib-object.h> +#include <libcaja-extension/caja-column-provider.h> +#include <libcaja-private/caja-clipboard-monitor.h> +#include <libcaja-private/caja-column-chooser.h> +#include <libcaja-private/caja-column-utilities.h> +#include <libcaja-private/caja-debug-log.h> +#include <libcaja-private/caja-directory-background.h> +#include <libcaja-private/caja-dnd.h> +#include <libcaja-private/caja-file-dnd.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-ui-utilities.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-icon-dnd.h> +#include <libcaja-private/caja-metadata.h> +#include <libcaja-private/caja-module.h> +#include <libcaja-private/caja-tree-view-drag-dest.h> +#include <libcaja-private/caja-view-factory.h> +#include <libcaja-private/caja-clipboard.h> +#include <libcaja-private/caja-cell-renderer-pixbuf-emblem.h> +#include <libcaja-private/caja-cell-renderer-text-ellipsized.h> + +struct FMListViewDetails +{ + GtkTreeView *tree_view; + FMListModel *model; + GtkActionGroup *list_action_group; + guint list_merge_id; + + GtkTreeViewColumn *file_name_column; + int file_name_column_num; + + GtkCellRendererPixbuf *pixbuf_cell; + GtkCellRendererText *file_name_cell; + GList *cells; + GtkCellEditable *editable_widget; + + CajaZoomLevel zoom_level; + + CajaTreeViewDragDest *drag_dest; + + GtkTreePath *double_click_path[2]; /* Both clicks in a double click need to be on the same row */ + + GtkTreePath *new_selection_path; /* Path of the new selection after removing a file */ + + GtkTreePath *hover_path; + + guint drag_button; + int drag_x; + int drag_y; + + gboolean drag_started; + + gboolean ignore_button_release; + + gboolean row_selected_on_button_down; + + gboolean menus_ready; + + GHashTable *columns; + GtkWidget *column_editor; + + char *original_name; + + CajaFile *renaming_file; + gboolean rename_done; + guint renaming_file_activate_timeout; + + gulong clipboard_handler_id; + + GQuark last_sort_attr; +}; + +struct SelectionForeachData +{ + GList *list; + GtkTreeSelection *selection; +}; + +/* + * 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. + */ +#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 + +static int click_policy_auto_value; +static char * default_sort_order_auto_value; +static gboolean default_sort_reversed_auto_value; +static CajaZoomLevel default_zoom_level_auto_value; +static char ** default_visible_columns_auto_value; +static char ** default_column_order_auto_value; +static GdkCursor * hand_cursor = NULL; + +static GtkTargetList * source_target_list = NULL; + +static GList *fm_list_view_get_selection (FMDirectoryView *view); +static GList *fm_list_view_get_selection_for_file_transfer (FMDirectoryView *view); +static void fm_list_view_set_zoom_level (FMListView *view, + CajaZoomLevel new_level, + gboolean always_set_level); +static void fm_list_view_scale_font_size (FMListView *view, + CajaZoomLevel new_level); +static void fm_list_view_scroll_to_file (FMListView *view, + CajaFile *file); +static void fm_list_view_iface_init (CajaViewIface *iface); +static void fm_list_view_rename_callback (CajaFile *file, + GFile *result_location, + GError *error, + gpointer callback_data); + + +G_DEFINE_TYPE_WITH_CODE (FMListView, fm_list_view, FM_TYPE_DIRECTORY_VIEW, + G_IMPLEMENT_INTERFACE (CAJA_TYPE_VIEW, + fm_list_view_iface_init)); + +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 +}; + +/* for EEL_CALL_PARENT */ +#define parent_class fm_list_view_parent_class + +static const gchar* +get_default_sort_order (CajaFile *file, gboolean *reversed) +{ + const gchar *retval; + + retval = caja_file_get_default_sort_attribute (file, reversed); + + if (retval == NULL) + { + retval = default_sort_order_auto_value; + *reversed = default_sort_reversed_auto_value; + } + + return retval; +} + +static void +list_selection_changed_callback (GtkTreeSelection *selection, gpointer user_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (user_data); + + fm_directory_view_notify_selection_changed (view); +} + +/* Move these to eel? */ + +static void +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) +{ + gboolean not_empty; + + not_empty = FALSE; + gtk_tree_selection_selected_foreach (selection, + tree_selection_foreach_set_boolean, + ¬_empty); + return not_empty; +} + +static gboolean +tree_view_has_selection (GtkTreeView *view) +{ + return tree_selection_not_empty (gtk_tree_view_get_selection (view)); +} + +static void +activate_selected_items (FMListView *view) +{ + GList *file_list; + + file_list = fm_list_view_get_selection (FM_DIRECTORY_VIEW (view)); + + + 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; + } + + fm_directory_view_activate_files (FM_DIRECTORY_VIEW (view), + file_list, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, + 0, + TRUE); + caja_file_list_free (file_list); + +} + +static void +activate_selected_items_alternate (FMListView *view, + CajaFile *file, + gboolean open_in_tab) +{ + GList *file_list; + CajaWindowOpenFlags flags; + + flags = CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND; + + if (open_in_tab) + { + flags |= CAJA_WINDOW_OPEN_FLAG_NEW_TAB; + } + else + { + flags |= CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW; + } + + if (file != NULL) + { + caja_file_ref (file); + file_list = g_list_prepend (NULL, file); + } + else + { + file_list = fm_list_view_get_selection (FM_DIRECTORY_VIEW (view)); + } + fm_directory_view_activate_files (FM_DIRECTORY_VIEW (view), + file_list, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, + flags, + TRUE); + caja_file_list_free (file_list); + +} + +static gboolean +button_event_modifies_selection (GdkEventButton *event) +{ + return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0; +} + +static void +fm_list_view_did_not_drag (FMListView *view, + GdkEventButton *event) +{ + 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)) + { + 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) + { + 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); + } + } + + if ((click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) + && !button_event_modifies_selection(event)) + { + if (event->button == 1) + { + activate_selected_items (view); + } + else if (event->button == 2) + { + activate_selected_items_alternate (view, NULL, TRUE); + } + } + gtk_tree_path_free (path); + } + +} + +static void +drag_data_get_callback (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time) +{ + GtkTreeView *tree_view; + GtkTreeModel *model; + GList *ref_list; + + tree_view = GTK_TREE_VIEW (widget); + + model = gtk_tree_view_get_model (tree_view); + + if (model == NULL) + { + return; + } + + ref_list = g_object_get_data (G_OBJECT (context), "drag-info"); + + if (ref_list == NULL) + { + return; + } + + 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); + } +} + +static void +filtered_selection_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + struct SelectionForeachData *selection_data; + GtkTreeIter parent; + GtkTreeIter child; + + 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; + } + + selection_data->list = g_list_prepend (selection_data->list, + gtk_tree_row_reference_new (model, path)); +} + +static GList * +get_filtered_selection_refs (GtkTreeView *tree_view) +{ + struct SelectionForeachData selection_data; + + 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); +} + +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 +stop_drag_check (FMListView *view) +{ + view->details->drag_button = 0; +} + +static GdkPixbuf * +get_drag_pixbuf (FMListView *view) +{ + 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, + fm_list_model_get_column_id_from_zoom_level (view->details->zoom_level), + &ret, + -1); + + gtk_tree_view_get_cell_area (view->details->tree_view, + path, + view->details->file_name_column, + &cell_area); + + gtk_tree_path_free (path); + } + + return ret; +} + +static void +drag_begin_callback (GtkWidget *widget, + GdkDragContext *context, + FMListView *view) +{ + 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); +} + +static gboolean +motion_notify_callback (GtkWidget *widget, + GdkEventMotion *event, + gpointer callback_data) +{ + FMListView *view; + GdkDragContext *context; + + view = FM_LIST_VIEW (callback_data); + + if (event->window != gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget))) + { + return FALSE; + } + + if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) + { + GtkTreePath *old_hover_path; + + old_hover_path = 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 ((old_hover_path != NULL) != (view->details->hover_path != NULL)) + { + if (view->details->hover_path != NULL) + { + gdk_window_set_cursor (gtk_widget_get_window (widget), hand_cursor); + } + else + { + gdk_window_set_cursor (gtk_widget_get_window (widget), NULL); + } + } + + if (old_hover_path != NULL) + { + gtk_tree_path_free (old_hover_path); + } + } + + if (view->details->drag_button != 0) + { + if (!source_target_list) + { + source_target_list = fm_list_model_get_drag_target_list (); + } + + if (gtk_drag_check_threshold (widget, + view->details->drag_x, + view->details->drag_y, + event->x, + event->y)) + { + context = gtk_drag_begin + (widget, + source_target_list, + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK, + view->details->drag_button, + (GdkEvent*)event); + } + return TRUE; + } + + return FALSE; +} + +static gboolean +leave_notify_callback (GtkWidget *widget, + GdkEventCrossing *event, + gpointer callback_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (callback_data); + + if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE && + view->details->hover_path != NULL) + { + gtk_tree_path_free (view->details->hover_path); + view->details->hover_path = NULL; + } + + return FALSE; +} + +static gboolean +enter_notify_callback (GtkWidget *widget, + GdkEventCrossing *event, + gpointer callback_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (callback_data); + + if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) + { + 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) + { + gdk_window_set_cursor (gtk_widget_get_window (widget), hand_cursor); + } + } + + return FALSE; +} + +static void +do_popup_menu (GtkWidget *widget, FMListView *view, GdkEventButton *event) +{ + if (tree_view_has_selection (GTK_TREE_VIEW (widget))) + { + fm_directory_view_pop_up_selection_context_menu (FM_DIRECTORY_VIEW (view), event); + } + else + { + fm_directory_view_pop_up_background_context_menu (FM_DIRECTORY_VIEW (view), event); + } +} + +static gboolean +button_press_callback (GtkWidget *widget, GdkEventButton *event, gpointer callback_data) +{ + FMListView *view; + GtkTreeView *tree_view; + GtkTreePath *path; + gboolean call_parent; + gboolean allow_drag; + 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 on_expander; + + view = FM_LIST_VIEW (callback_data); + tree_view = GTK_TREE_VIEW (widget); + tree_view_class = GTK_WIDGET_GET_CLASS (tree_view); + selection = gtk_tree_view_get_selection (tree_view); + + if (event->window != gtk_tree_view_get_bin_window (tree_view)) + { + return FALSE; + } + + fm_list_model_set_drag_view + (FM_LIST_MODEL (gtk_tree_view_get_model (tree_view)), + tree_view, + event->x, event->y); + + g_object_get (G_OBJECT (gtk_widget_get_settings (widget)), + "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 */ + if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE && click_count >= 2) + { + return TRUE; + } + + view->details->ignore_button_release = FALSE; + + call_parent = TRUE; + allow_drag = FALSE; + if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, + &path, NULL, NULL, NULL)) + { + 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 ((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] = 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; + if (view->details->double_click_path[1] && + gtk_tree_path_compare (view->details->double_click_path[0], view->details->double_click_path[1]) == 0 && + !on_expander) + { + /* 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) + { + CajaFile *file; + file = fm_list_model_file_for_path (view->details->model, path); + if (file != NULL) + { + activate_selected_items_alternate (view, file, TRUE); + caja_file_unref (file); + } + } + } + else + { + 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 && 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 = gtk_tree_selection_path_is_selected (selection, path); + if (view->details->row_selected_on_button_down) + { + call_parent = on_expander; + view->details->ignore_button_release = call_parent; + } + 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); + } + } + 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); + } + else + { + view->details->ignore_button_release = on_expander; + } + } + + if (call_parent) + { + tree_view_class->button_press_event (widget, event); + } + 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); + } + } + + 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; + } + /* 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); + } + } + + /* We chained to the default handler in this method, so never + * let the default handler run */ + return TRUE; +} + +static gboolean +button_release_callback (GtkWidget *widget, + GdkEventButton *event, + gpointer callback_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (callback_data); + + if (event->button == view->details->drag_button) + { + stop_drag_check (view); + if (!view->details->drag_started && + !view->details->ignore_button_release) + { + fm_list_view_did_not_drag (view, event); + } + } + return FALSE; +} + +static gboolean +popup_menu_callback (GtkWidget *widget, gpointer callback_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (callback_data); + + do_popup_menu (widget, view, NULL); + + return TRUE; +} + +static void +subdirectory_done_loading_callback (CajaDirectory *directory, FMListView *view) +{ + fm_list_model_subdirectory_done_loading (view->details->model, directory); +} + +static void +row_expanded_callback (GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, gpointer callback_data) +{ + FMListView *view; + CajaDirectory *directory; + + view = FM_LIST_VIEW (callback_data); + + if (fm_list_model_load_subdirectory (view->details->model, path, &directory)) + { + char *uri; + + uri = caja_directory_get_uri (directory); + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "list view row expanded window=%p: %s", + fm_directory_view_get_containing_window (FM_DIRECTORY_VIEW (view)), + uri); + g_free (uri); + + fm_directory_view_add_subdirectory (FM_DIRECTORY_VIEW (view), directory); + + if (caja_directory_are_all_files_seen (directory)) + { + fm_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); + } + + caja_directory_unref (directory); + } +} + +struct UnloadDelayData +{ + CajaFile *file; + CajaDirectory *directory; + FMListView *view; +}; + +static gboolean +unload_file_timeout (gpointer data) +{ + struct UnloadDelayData *unload_data = data; + GtkTreeIter iter; + FMListModel *model; + GtkTreePath *path; + + if (unload_data->view != NULL) + { + model = unload_data->view->details->model; + if (fm_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)) + { + fm_list_model_unload_subdirectory (model, &iter); + } + gtk_tree_path_free (path); + } + } + + eel_remove_weak_pointer (&unload_data->view); + + if (unload_data->directory) + { + caja_directory_unref (unload_data->directory); + } + caja_file_unref (unload_data->file); + g_free (unload_data); + return FALSE; +} + +static void +row_collapsed_callback (GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, gpointer callback_data) +{ + FMListView *view; + CajaFile *file; + CajaDirectory *directory; + GtkTreeIter parent; + struct UnloadDelayData *unload_data; + GtkTreeModel *model; + char *uri; + + view = FM_LIST_VIEW (callback_data); + model = GTK_TREE_MODEL (view->details->model); + + gtk_tree_model_get (model, iter, + FM_LIST_MODEL_FILE_COLUMN, &file, + -1); + + directory = NULL; + if (gtk_tree_model_iter_parent (model, &parent, iter)) + { + gtk_tree_model_get (model, &parent, + FM_LIST_MODEL_SUBDIRECTORY_COLUMN, &directory, + -1); + } + + + uri = caja_file_get_uri (file); + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "list view row collapsed window=%p: %s", + fm_directory_view_get_containing_window (FM_DIRECTORY_VIEW (view)), + uri); + g_free (uri); + + unload_data = g_new (struct UnloadDelayData, 1); + unload_data->view = view; + unload_data->file = file; + unload_data->directory = directory; + + eel_add_weak_pointer (&unload_data->view); + + g_timeout_add_seconds (COLLAPSE_TO_UNLOAD_DELAY, + unload_file_timeout, + unload_data); +} + +static void +row_activated_callback (GtkTreeView *treeview, GtkTreePath *path, + GtkTreeViewColumn *column, FMListView *view) +{ + activate_selected_items (view); +} + +static void +subdirectory_unloaded_callback (FMListModel *model, + CajaDirectory *directory, + gpointer callback_data) +{ + FMListView *view; + + g_return_if_fail (FM_IS_LIST_MODEL (model)); + g_return_if_fail (CAJA_IS_DIRECTORY (directory)); + + view = FM_LIST_VIEW(callback_data); + + g_signal_handlers_disconnect_by_func (directory, + G_CALLBACK (subdirectory_done_loading_callback), + view); + fm_directory_view_remove_subdirectory (FM_DIRECTORY_VIEW (view), directory); +} + +static gboolean +key_press_callback (GtkWidget *widget, GdkEventKey *event, gpointer callback_data) +{ + FMDirectoryView *view; + GdkEventButton button_event = { 0 }; + gboolean handled; + GtkTreeView *tree_view; + GtkTreePath *path; + + tree_view = GTK_TREE_VIEW (widget); + + view = FM_DIRECTORY_VIEW (callback_data); + handled = FALSE; + + switch (event->keyval) + { + case GDK_F10: + if (event->state & GDK_CONTROL_MASK) + { + fm_directory_view_pop_up_background_context_menu (view, &button_event); + handled = TRUE; + } + break; + case GDK_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_Left: + gtk_tree_view_get_cursor (tree_view, &path, NULL); + if (path) + { + gtk_tree_view_collapse_row (tree_view, path); + gtk_tree_path_free (path); + } + handled = TRUE; + break; + case GDK_space: + if (event->state & GDK_CONTROL_MASK) + { + handled = FALSE; + break; + } + if (!gtk_widget_has_focus (GTK_WIDGET (FM_LIST_VIEW (view)->details->tree_view))) + { + handled = FALSE; + break; + } + if ((event->state & GDK_SHIFT_MASK) != 0) + { + activate_selected_items_alternate (FM_LIST_VIEW (view), NULL, TRUE); + } + else + { + activate_selected_items (FM_LIST_VIEW (view)); + } + handled = TRUE; + break; + case GDK_Return: + case GDK_KP_Enter: + if ((event->state & GDK_SHIFT_MASK) != 0) + { + activate_selected_items_alternate (FM_LIST_VIEW (view), NULL, TRUE); + } + else + { + activate_selected_items (FM_LIST_VIEW (view)); + } + handled = TRUE; + break; + case GDK_v: + /* Eat Control + v to not enable type ahead */ + if ((event->state & GDK_CONTROL_MASK) != 0) + { + handled = TRUE; + } + break; + + default: + handled = FALSE; + } + + return handled; +} + +static void +fm_list_view_reveal_selection (FMDirectoryView *view) +{ + GList *selection; + + g_return_if_fail (FM_IS_LIST_VIEW (view)); + + selection = fm_directory_view_get_selection (view); + + /* Make sure at least one of the selected items is scrolled into view */ + if (selection != NULL) + { + FMListView *list_view; + CajaFile *file; + GtkTreeIter iter; + GtkTreePath *path; + + list_view = FM_LIST_VIEW (view); + file = selection->data; + if (fm_list_model_get_first_iter_for_file (list_view->details->model, file, &iter)) + { + 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); + } + } + + caja_file_list_free (selection); +} + +static gboolean +sort_criterion_changes_due_to_user (GtkTreeView *tree_view) +{ + GList *columns, *p; + GtkTreeViewColumn *column; + GSignalInvocationHint *ihint; + unsigned int sort_signal_id; + gboolean ret; + + sort_signal_id = g_signal_lookup ("clicked", gtk_tree_view_column_get_type ()); + + ret = FALSE; + + 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) + { + ret = TRUE; + break; + } + } + g_list_free (columns); + + return ret; +} + +static void +sort_column_changed_callback (GtkTreeSortable *sortable, + FMListView *view) +{ + CajaFile *file; + gint sort_column_id, default_sort_column_id; + GtkSortType reversed; + GQuark sort_attr, default_sort_attr; + char *reversed_attr, *default_reversed_attr; + gboolean default_sort_reversed; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)); + + gtk_tree_sortable_get_sort_column_id (sortable, &sort_column_id, &reversed); + sort_attr = fm_list_model_get_attribute_from_sort_column_id (view->details->model, sort_column_id); + + default_sort_column_id = fm_list_model_get_sort_column_id_from_attribute (view->details->model, + g_quark_from_string (get_default_sort_order (file, &default_sort_reversed))); + default_sort_attr = fm_list_model_get_attribute_from_sort_column_id (view->details->model, default_sort_column_id); + caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN, + g_quark_to_string (default_sort_attr), g_quark_to_string (sort_attr)); + + default_reversed_attr = (default_sort_reversed ? "true" : "false"); + + 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 */ + reversed = eel_preferences_get_boolean (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER); + } + else + { + reversed = caja_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); + } + } + + + reversed_attr = (reversed ? "true" : "false"); + caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED, + default_reversed_attr, reversed_attr); + + /* Make sure selected item(s) is visible after sort */ + fm_list_view_reveal_selection (FM_DIRECTORY_VIEW (view)); + + view->details->last_sort_attr = sort_attr; +} + +static void +cell_renderer_editing_started_cb (GtkCellRenderer *renderer, + GtkCellEditable *editable, + const gchar *path_str, + FMListView *list_view) +{ + GtkEntry *entry; + gint start_offset, end_offset; + + 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)); + eel_filename_get_rename_region (list_view->details->original_name, + &start_offset, &end_offset); + gtk_editable_select_region (GTK_EDITABLE (entry), start_offset, end_offset); + + caja_clipboard_set_up_editable + (GTK_EDITABLE (entry), + fm_directory_view_get_ui_manager (FM_DIRECTORY_VIEW (list_view)), + FALSE); +} + +static void +cell_renderer_editing_canceled (GtkCellRendererText *cell, + FMListView *view) +{ + view->details->editable_widget = NULL; + + fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (view)); +} + +static void +cell_renderer_edited (GtkCellRendererText *cell, + const char *path_str, + const char *new_text, + FMListView *view) +{ + GtkTreePath *path; + CajaFile *file; + GtkTreeIter iter; + + view->details->editable_widget = NULL; + + /* 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); + fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (view)); + return; + } + + 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, + FM_LIST_MODEL_FILE_COLUMN, &file, + -1); + + /* Only rename if name actually changed */ + if (strcmp (new_text, view->details->original_name) != 0) + { + view->details->renaming_file = caja_file_ref (file); + view->details->rename_done = FALSE; + fm_rename_file (file, new_text, fm_list_view_rename_callback, g_object_ref (view)); + g_free (view->details->original_name); + view->details->original_name = g_strdup (new_text); + } + + caja_file_unref (file); + + /*We're done editing - make the filename-cells readonly again.*/ + g_object_set (G_OBJECT (view->details->file_name_cell), + "editable", FALSE, + NULL); + + fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (view)); +} + +static char * +get_root_uri_callback (CajaTreeViewDragDest *dest, + gpointer user_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (user_data); + + return fm_directory_view_get_uri (FM_DIRECTORY_VIEW (view)); +} + +static CajaFile * +get_file_for_path_callback (CajaTreeViewDragDest *dest, + GtkTreePath *path, + gpointer user_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (user_data); + + return fm_list_model_file_for_path (view->details->model, path); +} + +/* Handles an URL received from Mozilla */ +static void +list_view_handle_netscape_url (CajaTreeViewDragDest *dest, const char *encoded_url, + const char *target_uri, GdkDragAction action, int x, int y, FMListView *view) +{ + fm_directory_view_handle_netscape_url_drop (FM_DIRECTORY_VIEW (view), + encoded_url, target_uri, action, x, y); +} + +static void +list_view_handle_uri_list (CajaTreeViewDragDest *dest, const char *item_uris, + const char *target_uri, + GdkDragAction action, int x, int y, FMListView *view) +{ + fm_directory_view_handle_uri_list_drop (FM_DIRECTORY_VIEW (view), + item_uris, target_uri, action, x, y); +} + +static void +list_view_handle_text (CajaTreeViewDragDest *dest, const char *text, + const char *target_uri, + GdkDragAction action, int x, int y, FMListView *view) +{ + fm_directory_view_handle_text_drop (FM_DIRECTORY_VIEW (view), + text, target_uri, action, x, y); +} + +static void +list_view_handle_raw (CajaTreeViewDragDest *dest, const char *raw_data, + int length, const char *target_uri, const char *direct_save_uri, + GdkDragAction action, int x, int y, FMListView *view) +{ + fm_directory_view_handle_raw_drop (FM_DIRECTORY_VIEW (view), + raw_data, length, target_uri, direct_save_uri, + action, x, y); +} + +static void +move_copy_items_callback (CajaTreeViewDragDest *dest, + const GList *item_uris, + const char *target_uri, + guint action, + int x, + int y, + gpointer user_data) + +{ + FMDirectoryView *view = user_data; + + caja_clipboard_clear_if_colliding_uris (GTK_WIDGET (view), + item_uris, + fm_directory_view_get_copied_files_atom (view)); + fm_directory_view_move_copy_items (item_uris, + NULL, + target_uri, + action, + x, y, + view); +} + +static void +apply_columns_settings (FMListView *list_view, + char **column_order, + char **visible_columns) +{ + GList *all_columns; + CajaFile *file; + GList *old_view_columns, *view_columns; + GHashTable *visible_columns_hash; + GtkTreeViewColumn *prev_view_column; + GList *l; + int i; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); + + /* prepare ordered list of view columns using column_order and visible_columns */ + view_columns = NULL; + + all_columns = caja_get_columns_for_file (file); + all_columns = caja_sort_columns (all_columns, column_order); + + /* hash table to lookup if a given column should be visible */ + visible_columns_hash = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + for (i = 0; visible_columns[i] != NULL; ++i) + { + g_hash_table_insert (visible_columns_hash, + g_ascii_strdown (visible_columns[i], -1), + g_ascii_strdown (visible_columns[i], -1)); + } + + for (l = all_columns; l != NULL; l = l->next) + { + char *name; + char *lowercase; + + g_object_get (G_OBJECT (l->data), "name", &name, NULL); + lowercase = g_ascii_strdown (name, -1); + + if (g_hash_table_lookup (visible_columns_hash, lowercase) != NULL) + { + GtkTreeViewColumn *view_column; + + view_column = g_hash_table_lookup (list_view->details->columns, name); + if (view_column != NULL) + { + view_columns = g_list_prepend (view_columns, view_column); + } + } + + g_free (name); + g_free (lowercase); + } + + g_hash_table_destroy (visible_columns_hash); + caja_column_list_free (all_columns); + + view_columns = g_list_reverse (view_columns); + + /* remove columns that are not present in the configuration */ + old_view_columns = gtk_tree_view_get_columns (list_view->details->tree_view); + for (l = old_view_columns; l != NULL; l = l->next) + { + if (g_list_find (view_columns, l->data) == NULL) + { + gtk_tree_view_remove_column (list_view->details->tree_view, l->data); + } + } + g_list_free (old_view_columns); + + /* append new columns from the configuration */ + old_view_columns = gtk_tree_view_get_columns (list_view->details->tree_view); + for (l = view_columns; l != NULL; l = l->next) + { + if (g_list_find (old_view_columns, l->data) == NULL) + { + gtk_tree_view_append_column (list_view->details->tree_view, l->data); + } + } + g_list_free (old_view_columns); + + /* place columns in the correct order */ + prev_view_column = NULL; + for (l = view_columns; l != NULL; l = l->next) + { + gtk_tree_view_move_column_after (list_view->details->tree_view, l->data, prev_view_column); + prev_view_column = l->data; + } + g_list_free (view_columns); +} + +static void +filename_cell_data_func (GtkTreeViewColumn *column, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + FMListView *view) +{ + char *text; + GtkTreePath *path; + PangoUnderline underline; + + gtk_tree_model_get (model, iter, + view->details->file_name_column_num, &text, + -1); + + if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) + { + path = gtk_tree_model_get_path (model, iter); + + if (view->details->hover_path == NULL || + gtk_tree_path_compare (path, view->details->hover_path)) + { + underline = PANGO_UNDERLINE_NONE; + } + else + { + underline = PANGO_UNDERLINE_SINGLE; + } + + gtk_tree_path_free (path); + } + else + { + underline = PANGO_UNDERLINE_NONE; + } + + g_object_set (G_OBJECT (renderer), + "text", text, + "underline", underline, + NULL); + g_free (text); +} + +static gboolean +focus_in_event_callback (GtkWidget *widget, GdkEventFocus *event, gpointer user_data) +{ + CajaWindowSlotInfo *slot_info; + FMListView *list_view = FM_LIST_VIEW (user_data); + + /* make the corresponding slot (and the pane that contains it) active */ + slot_info = fm_directory_view_get_caja_window_slot (FM_DIRECTORY_VIEW (list_view)); + caja_window_slot_info_make_hosting_pane_active (slot_info); + + return FALSE; +} + +static void +create_and_set_up_tree_view (FMListView *view) +{ + GtkCellRenderer *cell; + GtkTreeViewColumn *column; + GtkBindingSet *binding_set; + AtkObject *atk_obj; + GList *caja_columns; + GList *l; + + view->details->tree_view = GTK_TREE_VIEW (gtk_tree_view_new ()); + view->details->columns = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify)g_free, + (GDestroyNotify) g_object_unref); + gtk_tree_view_set_enable_search (view->details->tree_view, TRUE); + + /* Don't handle backspace key. It's used to open the parent folder. */ + binding_set = gtk_binding_set_by_class (GTK_WIDGET_GET_CLASS (view->details->tree_view)); + gtk_binding_entry_remove (binding_set, GDK_BackSpace, 0); + + view->details->drag_dest = + caja_tree_view_drag_dest_new (view->details->tree_view); + + g_signal_connect_object (view->details->drag_dest, + "get_root_uri", + G_CALLBACK (get_root_uri_callback), + view, 0); + g_signal_connect_object (view->details->drag_dest, + "get_file_for_path", + G_CALLBACK (get_file_for_path_callback), + view, 0); + g_signal_connect_object (view->details->drag_dest, + "move_copy_items", + G_CALLBACK (move_copy_items_callback), + view, 0); + g_signal_connect_object (view->details->drag_dest, "handle_netscape_url", + G_CALLBACK (list_view_handle_netscape_url), view, 0); + g_signal_connect_object (view->details->drag_dest, "handle_uri_list", + G_CALLBACK (list_view_handle_uri_list), view, 0); + g_signal_connect_object (view->details->drag_dest, "handle_text", + G_CALLBACK (list_view_handle_text), view, 0); + g_signal_connect_object (view->details->drag_dest, "handle_raw", + G_CALLBACK (list_view_handle_raw), view, 0); + + g_signal_connect_object (gtk_tree_view_get_selection (view->details->tree_view), + "changed", + G_CALLBACK (list_selection_changed_callback), view, 0); + + g_signal_connect_object (view->details->tree_view, "drag_begin", + G_CALLBACK (drag_begin_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "drag_data_get", + G_CALLBACK (drag_data_get_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "motion_notify_event", + G_CALLBACK (motion_notify_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "enter_notify_event", + G_CALLBACK (enter_notify_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "leave_notify_event", + G_CALLBACK (leave_notify_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "button_press_event", + G_CALLBACK (button_press_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "button_release_event", + G_CALLBACK (button_release_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "key_press_event", + 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); + + g_signal_connect_object (view->details->tree_view, "focus_in_event", + G_CALLBACK(focus_in_event_callback), view, 0); + + view->details->model = g_object_new (FM_TYPE_LIST_MODEL, NULL); + gtk_tree_view_set_model (view->details->tree_view, GTK_TREE_MODEL (view->details->model)); + /* Need the model for the dnd drop icon "accept" change */ + fm_list_model_set_drag_view (FM_LIST_MODEL (view->details->model), + view->details->tree_view, 0, 0); + + g_signal_connect_object (view->details->model, "sort_column_changed", + G_CALLBACK (sort_column_changed_callback), view, 0); + + g_signal_connect_object (view->details->model, "subdirectory_unloaded", + G_CALLBACK (subdirectory_unloaded_callback), view, 0); + + gtk_tree_selection_set_mode (gtk_tree_view_get_selection (view->details->tree_view), GTK_SELECTION_MULTIPLE); + gtk_tree_view_set_rules_hint (view->details->tree_view, TRUE); + + caja_columns = caja_get_all_columns (); + + for (l = caja_columns; l != NULL; l = l->next) + { + CajaColumn *caja_column; + int column_num; + char *name; + char *label; + float xalign; + + caja_column = CAJA_COLUMN (l->data); + + g_object_get (caja_column, + "name", &name, + "label", &label, + "xalign", &xalign, NULL); + + column_num = fm_list_model_add_column (view->details->model, + caja_column); + + /* Created the name column specially, because it + * has the icon in it.*/ + if (!strcmp (name, "name")) + { + /* Create the file name column */ + cell = caja_cell_renderer_pixbuf_emblem_new (); + view->details->pixbuf_cell = (GtkCellRendererPixbuf *)cell; + + view->details->file_name_column = gtk_tree_view_column_new (); + g_object_ref_sink (view->details->file_name_column); + view->details->file_name_column_num = column_num; + + g_hash_table_insert (view->details->columns, + g_strdup ("name"), + view->details->file_name_column); + + gtk_tree_view_set_search_column (view->details->tree_view, column_num); + + gtk_tree_view_column_set_sort_column_id (view->details->file_name_column, column_num); + gtk_tree_view_column_set_title (view->details->file_name_column, _("Name")); + gtk_tree_view_column_set_resizable (view->details->file_name_column, TRUE); + + gtk_tree_view_column_pack_start (view->details->file_name_column, cell, FALSE); + gtk_tree_view_column_set_attributes (view->details->file_name_column, + cell, + "pixbuf", FM_LIST_MODEL_SMALLEST_ICON_COLUMN, + "pixbuf_emblem", FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN, + NULL); + + cell = caja_cell_renderer_text_ellipsized_new (); + view->details->file_name_cell = (GtkCellRendererText *)cell; + g_signal_connect (cell, "edited", G_CALLBACK (cell_renderer_edited), view); + g_signal_connect (cell, "editing-canceled", G_CALLBACK (cell_renderer_editing_canceled), view); + g_signal_connect (cell, "editing-started", G_CALLBACK (cell_renderer_editing_started_cb), view); + + gtk_tree_view_column_pack_start (view->details->file_name_column, cell, TRUE); + gtk_tree_view_column_set_cell_data_func (view->details->file_name_column, cell, + (GtkTreeCellDataFunc) filename_cell_data_func, + view, NULL); + } + else + { + cell = gtk_cell_renderer_text_new (); + g_object_set (cell, "xalign", xalign, NULL); + view->details->cells = g_list_append (view->details->cells, + cell); + column = gtk_tree_view_column_new_with_attributes (label, + cell, + "text", column_num, + NULL); + g_object_ref_sink (column); + gtk_tree_view_column_set_sort_column_id (column, column_num); + g_hash_table_insert (view->details->columns, + g_strdup (name), + column); + + gtk_tree_view_column_set_resizable (column, TRUE); + gtk_tree_view_column_set_visible (column, TRUE); + } + g_free (name); + g_free (label); + } + caja_column_list_free (caja_columns); + + /* Apply the default column order and visible columns, to get it + * right most of the time. The metadata will be checked when a + * folder is loaded */ + apply_columns_settings (view, + default_column_order_auto_value, + default_visible_columns_auto_value); + + gtk_widget_show (GTK_WIDGET (view->details->tree_view)); + gtk_container_add (GTK_CONTAINER (view), GTK_WIDGET (view->details->tree_view)); + + + atk_obj = gtk_widget_get_accessible (GTK_WIDGET (view->details->tree_view)); + atk_object_set_name (atk_obj, _("List View")); +} + +static void +fm_list_view_add_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + FMListModel *model; + + model = FM_LIST_VIEW (view)->details->model; + fm_list_model_add_file (model, file, directory); +} + +static char ** +get_visible_columns (FMListView *list_view) +{ + CajaFile *file; + GList *visible_columns; + char **ret; + + ret = NULL; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); + + visible_columns = caja_file_get_metadata_list + (file, + CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS); + + if (visible_columns) + { + GPtrArray *res; + GList *l; + + res = g_ptr_array_new (); + for (l = visible_columns; l != NULL; l = l->next) + { + g_ptr_array_add (res, l->data); + } + g_ptr_array_add (res, NULL); + + ret = (char **) g_ptr_array_free (res, FALSE); + g_list_free (visible_columns); + } + + if (ret != NULL) + { + return ret; + } + + return caja_file_is_in_trash (file) ? + g_strdupv ((gchar **) default_trash_visible_columns) : + g_strdupv (default_visible_columns_auto_value); +} + +static char ** +get_column_order (FMListView *list_view) +{ + CajaFile *file; + GList *column_order; + char **ret; + + ret = NULL; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); + + column_order = caja_file_get_metadata_list + (file, + CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER); + + if (column_order) + { + GPtrArray *res; + GList *l; + + res = g_ptr_array_new (); + for (l = column_order; l != NULL; l = l->next) + { + g_ptr_array_add (res, l->data); + } + g_ptr_array_add (res, NULL); + + ret = (char **) g_ptr_array_free (res, FALSE); + g_list_free (column_order); + } + + if (ret != NULL) + { + return ret; + } + + return caja_file_is_in_trash (file) ? + g_strdupv ((gchar **) default_trash_columns_order) : + g_strdupv (default_column_order_auto_value); +} + +static void +set_columns_settings_from_metadata_and_preferences (FMListView *list_view) +{ + char **column_order; + char **visible_columns; + + column_order = get_column_order (list_view); + visible_columns = get_visible_columns (list_view); + + apply_columns_settings (list_view, column_order, visible_columns); + + g_strfreev (column_order); + g_strfreev (visible_columns); +} + +static void +set_sort_order_from_metadata_and_preferences (FMListView *list_view) +{ + char *sort_attribute; + int sort_column_id; + CajaFile *file; + gboolean sort_reversed, default_sort_reversed; + const gchar *default_sort_order; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); + sort_attribute = caja_file_get_metadata (file, + CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN, + NULL); + sort_column_id = fm_list_model_get_sort_column_id_from_attribute (list_view->details->model, + g_quark_from_string (sort_attribute)); + g_free (sort_attribute); + + default_sort_order = get_default_sort_order (file, &default_sort_reversed); + + if (sort_column_id == -1) + { + sort_column_id = + fm_list_model_get_sort_column_id_from_attribute (list_view->details->model, + g_quark_from_string (default_sort_order)); + } + + sort_reversed = caja_file_get_boolean_metadata (file, + CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED, + default_sort_reversed); + + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list_view->details->model), + sort_column_id, + sort_reversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING); +} + +static gboolean +list_view_changed_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + gtk_tree_model_row_changed (model, path, iter); + return FALSE; +} + +static CajaZoomLevel +get_default_zoom_level (void) +{ + CajaZoomLevel default_zoom_level; + + default_zoom_level = default_zoom_level_auto_value; + + if (default_zoom_level < CAJA_ZOOM_LEVEL_SMALLEST + || CAJA_ZOOM_LEVEL_LARGEST < default_zoom_level) + { + default_zoom_level = CAJA_ZOOM_LEVEL_SMALL; + } + + return default_zoom_level; +} + +static void +set_zoom_level_from_metadata_and_preferences (FMListView *list_view) +{ + CajaFile *file; + int level; + + if (fm_directory_view_supports_zooming (FM_DIRECTORY_VIEW (list_view))) + { + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); + level = caja_file_get_integer_metadata (file, + CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL, + get_default_zoom_level ()); + fm_list_view_set_zoom_level (list_view, level, TRUE); + + /* updated the rows after updating the font size */ + gtk_tree_model_foreach (GTK_TREE_MODEL (list_view->details->model), + list_view_changed_foreach, NULL); + } +} + +static void +fm_list_view_begin_loading (FMDirectoryView *view) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + + set_sort_order_from_metadata_and_preferences (list_view); + set_zoom_level_from_metadata_and_preferences (list_view); + set_columns_settings_from_metadata_and_preferences (list_view); +} + +static void +stop_cell_editing (FMListView *list_view) +{ + GtkTreeViewColumn *column; + + /* Stop an ongoing rename to commit the name changes when the user + * changes directories without exiting cell edit mode. It also prevents + * the edited handler from being called on the cleared list model. + */ + column = list_view->details->file_name_column; + if (column != NULL && list_view->details->editable_widget != NULL && + GTK_IS_CELL_EDITABLE (list_view->details->editable_widget)) + { + gtk_cell_editable_editing_done (list_view->details->editable_widget); + } +} + +static void +fm_list_view_clear (FMDirectoryView *view) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + + if (list_view->details->model != NULL) + { + stop_cell_editing (list_view); + fm_list_model_clear (list_view->details->model); + } +} + +static void +fm_list_view_rename_callback (CajaFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (callback_data); + + if (view->details->renaming_file) + { + view->details->rename_done = TRUE; + + if (error != NULL) + { + /* If the rename failed (or was cancelled), kill renaming_file. + * We won't get a change event for the rename, so otherwise + * it would stay around forever. + */ + caja_file_unref (view->details->renaming_file); + view->details->renaming_file = NULL; + } + } + + g_object_unref (view); +} + + +static void +fm_list_view_file_changed (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + FMListView *listview; + GtkTreeIter iter; + GtkTreePath *file_path; + + listview = FM_LIST_VIEW (view); + + fm_list_model_file_changed (listview->details->model, file, directory); + + if (listview->details->renaming_file != NULL && + file == listview->details->renaming_file && + listview->details->rename_done) + { + /* This is (probably) the result of the rename operation, and + * the tree-view changes above could have resorted the list, so + * scroll to the new position + */ + if (fm_list_model_get_tree_iter_from_file (listview->details->model, file, directory, &iter)) + { + file_path = gtk_tree_model_get_path (GTK_TREE_MODEL (listview->details->model), &iter); + gtk_tree_view_scroll_to_cell (listview->details->tree_view, + file_path, NULL, + FALSE, 0.0, 0.0); + gtk_tree_path_free (file_path); + } + + caja_file_unref (listview->details->renaming_file); + listview->details->renaming_file = NULL; + } +} + +static GtkWidget * +fm_list_view_get_background_widget (FMDirectoryView *view) +{ + return GTK_WIDGET (view); +} + +static void +fm_list_view_get_selection_foreach_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ + GList **list; + CajaFile *file; + + list = data; + + gtk_tree_model_get (model, iter, + FM_LIST_MODEL_FILE_COLUMN, &file, + -1); + + if (file != NULL) + { + (* list) = g_list_prepend ((* list), file); + } +} + +static GList * +fm_list_view_get_selection (FMDirectoryView *view) +{ + GList *list; + + list = NULL; + + gtk_tree_selection_selected_foreach (gtk_tree_view_get_selection (FM_LIST_VIEW (view)->details->tree_view), + fm_list_view_get_selection_foreach_func, &list); + + return g_list_reverse (list); +} + +static void +fm_list_view_get_selection_for_file_transfer_foreach_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ + CajaFile *file; + struct SelectionForeachData *selection_data; + GtkTreeIter parent, child; + + selection_data = data; + + gtk_tree_model_get (model, iter, + FM_LIST_MODEL_FILE_COLUMN, &file, + -1); + + if (file != NULL) + { + /* 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; + } + + caja_file_ref (file); + selection_data->list = g_list_prepend (selection_data->list, file); + } +} + + +static GList * +fm_list_view_get_selection_for_file_transfer (FMDirectoryView *view) +{ + struct SelectionForeachData selection_data; + + selection_data.list = NULL; + selection_data.selection = gtk_tree_view_get_selection (FM_LIST_VIEW (view)->details->tree_view); + + gtk_tree_selection_selected_foreach (selection_data.selection, + fm_list_view_get_selection_for_file_transfer_foreach_func, &selection_data); + + return g_list_reverse (selection_data.list); +} + + + + +static guint +fm_list_view_get_item_count (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_LIST_VIEW (view), 0); + + return fm_list_model_get_length (FM_LIST_VIEW (view)->details->model); +} + +static gboolean +fm_list_view_is_empty (FMDirectoryView *view) +{ + return fm_list_model_is_empty (FM_LIST_VIEW (view)->details->model); +} + +static void +fm_list_view_end_file_changes (FMDirectoryView *view) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + + if (list_view->details->new_selection_path) + { + gtk_tree_view_set_cursor (list_view->details->tree_view, + list_view->details->new_selection_path, + NULL, FALSE); + gtk_tree_path_free (list_view->details->new_selection_path); + list_view->details->new_selection_path = NULL; + } +} + +static void +fm_list_view_remove_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + GtkTreePath *path; + GtkTreePath *file_path; + GtkTreeIter iter; + GtkTreeIter temp_iter; + GtkTreeRowReference* row_reference; + FMListView *list_view; + GtkTreeModel* tree_model; + GtkTreeSelection *selection; + + path = NULL; + row_reference = NULL; + list_view = FM_LIST_VIEW (view); + tree_model = GTK_TREE_MODEL(list_view->details->model); + + if (fm_list_model_get_tree_iter_from_file (list_view->details->model, file, directory, &iter)) + { + selection = gtk_tree_view_get_selection (list_view->details->tree_view); + file_path = gtk_tree_model_get_path (tree_model, &iter); + + if (gtk_tree_selection_path_is_selected (selection, file_path)) + { + /* get reference for next element in the list view. If the element to be deleted is the + * last one, get reference to previous element. If there is only one element in view + * no need to select anything. + */ + temp_iter = iter; + + if (gtk_tree_model_iter_next (tree_model, &iter)) + { + path = gtk_tree_model_get_path (tree_model, &iter); + row_reference = gtk_tree_row_reference_new (tree_model, path); + } + else + { + path = gtk_tree_model_get_path (tree_model, &temp_iter); + if (gtk_tree_path_prev (path)) + { + row_reference = gtk_tree_row_reference_new (tree_model, path); + } + } + gtk_tree_path_free (path); + } + + gtk_tree_path_free (file_path); + + fm_list_model_remove_file (list_view->details->model, file, directory); + + if (gtk_tree_row_reference_valid (row_reference)) + { + if (list_view->details->new_selection_path) + { + gtk_tree_path_free (list_view->details->new_selection_path); + } + list_view->details->new_selection_path = gtk_tree_row_reference_get_path (row_reference); + } + + if (row_reference) + { + gtk_tree_row_reference_free (row_reference); + } + } + + +} + +static void +fm_list_view_set_selection (FMDirectoryView *view, GList *selection) +{ + FMListView *list_view; + GtkTreeSelection *tree_selection; + GList *node; + GList *iters, *l; + CajaFile *file; + + list_view = FM_LIST_VIEW (view); + tree_selection = gtk_tree_view_get_selection (list_view->details->tree_view); + + g_signal_handlers_block_by_func (tree_selection, list_selection_changed_callback, view); + + gtk_tree_selection_unselect_all (tree_selection); + for (node = selection; node != NULL; node = node->next) + { + file = node->data; + iters = fm_list_model_get_all_iters_for_file (list_view->details->model, file); + + for (l = iters; l != NULL; l = l->next) + { + gtk_tree_selection_select_iter (tree_selection, + (GtkTreeIter *)l->data); + } + eel_g_list_free_deep (iters); + } + + g_signal_handlers_unblock_by_func (tree_selection, list_selection_changed_callback, view); + fm_directory_view_notify_selection_changed (view); +} + +static void +fm_list_view_invert_selection (FMDirectoryView *view) +{ + FMListView *list_view; + GtkTreeSelection *tree_selection; + GList *node; + GList *iters, *l; + CajaFile *file; + GList *selection = NULL; + + list_view = FM_LIST_VIEW (view); + tree_selection = gtk_tree_view_get_selection (list_view->details->tree_view); + + g_signal_handlers_block_by_func (tree_selection, list_selection_changed_callback, view); + + gtk_tree_selection_selected_foreach (tree_selection, + fm_list_view_get_selection_foreach_func, &selection); + + gtk_tree_selection_select_all (tree_selection); + + for (node = selection; node != NULL; node = node->next) + { + file = node->data; + iters = fm_list_model_get_all_iters_for_file (list_view->details->model, file); + + for (l = iters; l != NULL; l = l->next) + { + gtk_tree_selection_unselect_iter (tree_selection, + (GtkTreeIter *)l->data); + } + eel_g_list_free_deep (iters); + } + + g_list_free (selection); + + g_signal_handlers_unblock_by_func (tree_selection, list_selection_changed_callback, view); + fm_directory_view_notify_selection_changed (view); +} + +static void +fm_list_view_select_all (FMDirectoryView *view) +{ + gtk_tree_selection_select_all (gtk_tree_view_get_selection (FM_LIST_VIEW (view)->details->tree_view)); +} + +static void +column_editor_response_callback (GtkWidget *dialog, + int response_id, + gpointer user_data) +{ + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +static void +column_chooser_changed_callback (CajaColumnChooser *chooser, + FMListView *view) +{ + CajaFile *file; + char **visible_columns; + char **column_order; + GList *list; + int i; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)); + + caja_column_chooser_get_settings (chooser, + &visible_columns, + &column_order); + + list = NULL; + for (i = 0; visible_columns[i] != NULL; ++i) + { + list = g_list_prepend (list, visible_columns[i]); + } + list = g_list_reverse (list); + caja_file_set_metadata_list (file, + CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, + list); + g_list_free (list); + + list = NULL; + for (i = 0; column_order[i] != NULL; ++i) + { + list = g_list_prepend (list, column_order[i]); + } + list = g_list_reverse (list); + caja_file_set_metadata_list (file, + CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, + list); + g_list_free (list); + + apply_columns_settings (view, column_order, visible_columns); + + g_strfreev (visible_columns); + g_strfreev (column_order); +} + +static void +column_chooser_set_from_arrays (CajaColumnChooser *chooser, + FMListView *view, + char **visible_columns, + char **column_order) +{ + g_signal_handlers_block_by_func + (chooser, G_CALLBACK (column_chooser_changed_callback), view); + + caja_column_chooser_set_settings (chooser, + visible_columns, + column_order); + + g_signal_handlers_unblock_by_func + (chooser, G_CALLBACK (column_chooser_changed_callback), view); +} + +static void +column_chooser_set_from_settings (CajaColumnChooser *chooser, + FMListView *view) +{ + char **visible_columns; + char **column_order; + + visible_columns = get_visible_columns (view); + column_order = get_column_order (view); + + column_chooser_set_from_arrays (chooser, view, + visible_columns, column_order); + + g_strfreev (visible_columns); + g_strfreev (column_order); +} + +static void +column_chooser_use_default_callback (CajaColumnChooser *chooser, + FMListView *view) +{ + CajaFile *file; + char **default_columns; + char **default_order; + + file = fm_directory_view_get_directory_as_file + (FM_DIRECTORY_VIEW (view)); + + caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, NULL); + caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, NULL); + + /* set view values ourselves, as new metadata could not have been + * updated yet. + */ + default_columns = caja_file_is_in_trash (file) ? + g_strdupv ((gchar **) default_trash_visible_columns) : + g_strdupv (default_visible_columns_auto_value); + + default_order = caja_file_is_in_trash (file) ? + g_strdupv ((gchar **) default_trash_columns_order) : + g_strdupv (default_column_order_auto_value); + + apply_columns_settings (view, default_order, default_columns); + column_chooser_set_from_arrays (chooser, view, + default_columns, default_order); + + g_strfreev (default_columns); + g_strfreev (default_order); +} + +static GtkWidget * +create_column_editor (FMListView *view) +{ + GtkWidget *window; + GtkWidget *label; + GtkWidget *box; + GtkWidget *column_chooser; + GtkWidget *alignment; + CajaFile *file; + char *str; + char *name; + const char *label_text; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)); + name = caja_file_get_display_name (file); + str = g_strdup_printf (_("%s Visible Columns"), name); + g_free (name); + + window = gtk_dialog_new_with_buttons (str, + GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, + NULL); + g_free (str); + g_signal_connect (window, "response", + G_CALLBACK (column_editor_response_callback), NULL); + + gtk_window_set_default_size (GTK_WINDOW (window), 300, 400); + + box = gtk_vbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (box), 12); + gtk_widget_show (box); + gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (window))), box); + + label_text = _("Choose the order of information to appear in this folder:"); + str = g_strconcat ("<b>", label_text, "</b>", NULL); + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (label), str); + gtk_label_set_line_wrap (GTK_LABEL (label), FALSE); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + + g_free (str); + + alignment = gtk_alignment_new (0.5, 0.5, 1, 1); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), + 0, 0, 12, 0); + gtk_widget_show (alignment); + gtk_box_pack_start (GTK_BOX (box), alignment, TRUE, TRUE, 0); + + column_chooser = caja_column_chooser_new (file); + gtk_widget_show (column_chooser); + gtk_container_add (GTK_CONTAINER (alignment), column_chooser); + + g_signal_connect (column_chooser, "changed", + G_CALLBACK (column_chooser_changed_callback), + view); + g_signal_connect (column_chooser, "use_default", + G_CALLBACK (column_chooser_use_default_callback), + view); + + column_chooser_set_from_settings + (CAJA_COLUMN_CHOOSER (column_chooser), view); + + return window; +} + +static void +action_visible_columns_callback (GtkAction *action, + gpointer callback_data) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (callback_data); + + if (list_view->details->column_editor) + { + gtk_window_present (GTK_WINDOW (list_view->details->column_editor)); + } + else + { + list_view->details->column_editor = create_column_editor (list_view); + eel_add_weak_pointer (&list_view->details->column_editor); + + gtk_widget_show (list_view->details->column_editor); + } +} + +static const GtkActionEntry list_view_entries[] = +{ + /* name, stock id */ { "Visible Columns", NULL, + /* label, accelerator */ N_("Visible _Columns..."), NULL, + /* tooltip */ N_("Select the columns visible in this folder"), + G_CALLBACK (action_visible_columns_callback) + }, +}; + +static void +fm_list_view_merge_menus (FMDirectoryView *view) +{ + FMListView *list_view; + GtkUIManager *ui_manager; + GtkActionGroup *action_group; + const char *ui; + + EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, merge_menus, (view)); + + list_view = FM_LIST_VIEW (view); + + ui_manager = fm_directory_view_get_ui_manager (view); + + action_group = gtk_action_group_new ("ListViewActions"); + gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE); + list_view->details->list_action_group = action_group; + gtk_action_group_add_actions (action_group, + list_view_entries, G_N_ELEMENTS (list_view_entries), + list_view); + + gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); + g_object_unref (action_group); /* owned by ui manager */ + + ui = caja_ui_string_get ("caja-list-view-ui.xml"); + list_view->details->list_merge_id = gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL); + + list_view->details->menus_ready = TRUE; +} + +static void +fm_list_view_unmerge_menus (FMDirectoryView *view) +{ + FMListView *list_view; + GtkUIManager *ui_manager; + + list_view = FM_LIST_VIEW (view); + + FM_DIRECTORY_VIEW_CLASS (fm_list_view_parent_class)->unmerge_menus (view); + + ui_manager = fm_directory_view_get_ui_manager (view); + if (ui_manager != NULL) + { + caja_ui_unmerge_ui (ui_manager, + &list_view->details->list_merge_id, + &list_view->details->list_action_group); + } +} + +static void +fm_list_view_update_menus (FMDirectoryView *view) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + + /* don't update if the menus aren't ready */ + if (!list_view->details->menus_ready) + { + return; + } + + EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, update_menus, (view)); +} + +/* Reset sort criteria and zoom level to match defaults */ +static void +fm_list_view_reset_to_defaults (FMDirectoryView *view) +{ + CajaFile *file; + const gchar *default_sort_order; + gboolean default_sort_reversed; + + file = fm_directory_view_get_directory_as_file (view); + + caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN, NULL, NULL); + caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED, NULL, NULL); + caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL, NULL, NULL); + caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, NULL); + caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, NULL); + + default_sort_order = get_default_sort_order (file, &default_sort_reversed); + + gtk_tree_sortable_set_sort_column_id + (GTK_TREE_SORTABLE (FM_LIST_VIEW (view)->details->model), + fm_list_model_get_sort_column_id_from_attribute (FM_LIST_VIEW (view)->details->model, + g_quark_from_string (default_sort_order)), + default_sort_reversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING); + + fm_list_view_set_zoom_level (FM_LIST_VIEW (view), get_default_zoom_level (), FALSE); + set_columns_settings_from_metadata_and_preferences (FM_LIST_VIEW (view)); +} + +static void +fm_list_view_scale_font_size (FMListView *view, + CajaZoomLevel new_level) +{ + GList *l; + static gboolean first_time = TRUE; + static double pango_scale[7]; + int medium; + int i; + + g_return_if_fail (new_level >= CAJA_ZOOM_LEVEL_SMALLEST && + new_level <= CAJA_ZOOM_LEVEL_LARGEST); + + if (first_time) + { + first_time = FALSE; + medium = CAJA_ZOOM_LEVEL_SMALLER; + pango_scale[medium] = PANGO_SCALE_MEDIUM; + for (i = medium; i > CAJA_ZOOM_LEVEL_SMALLEST; i--) + { + pango_scale[i - 1] = (1 / 1.2) * pango_scale[i]; + } + for (i = medium; i < CAJA_ZOOM_LEVEL_LARGEST; i++) + { + pango_scale[i + 1] = 1.2 * pango_scale[i]; + } + } + + g_object_set (G_OBJECT (view->details->file_name_cell), + "scale", pango_scale[new_level], + NULL); + for (l = view->details->cells; l != NULL; l = l->next) + { + g_object_set (G_OBJECT (l->data), + "scale", pango_scale[new_level], + NULL); + } +} + +static void +fm_list_view_set_zoom_level (FMListView *view, + CajaZoomLevel new_level, + gboolean always_emit) +{ + int icon_size; + int column, emblem_column; + + g_return_if_fail (FM_IS_LIST_VIEW (view)); + g_return_if_fail (new_level >= CAJA_ZOOM_LEVEL_SMALLEST && + new_level <= CAJA_ZOOM_LEVEL_LARGEST); + + if (view->details->zoom_level == new_level) + { + if (always_emit) + { + g_signal_emit_by_name (FM_DIRECTORY_VIEW(view), "zoom_level_changed"); + } + return; + } + + view->details->zoom_level = new_level; + g_signal_emit_by_name (FM_DIRECTORY_VIEW(view), "zoom_level_changed"); + + caja_file_set_integer_metadata + (fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)), + CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL, + get_default_zoom_level (), + new_level); + + /* Select correctly scaled icons. */ + column = fm_list_model_get_column_id_from_zoom_level (new_level); + emblem_column = fm_list_model_get_emblem_column_id_from_zoom_level (new_level); + gtk_tree_view_column_set_attributes (view->details->file_name_column, + GTK_CELL_RENDERER (view->details->pixbuf_cell), + "pixbuf", column, + "pixbuf_emblem", emblem_column, + NULL); + + /* Scale text. */ + fm_list_view_scale_font_size (view, new_level); + + /* Make all rows the same size. */ + icon_size = caja_get_icon_size_for_zoom_level (new_level); + gtk_cell_renderer_set_fixed_size (GTK_CELL_RENDERER (view->details->pixbuf_cell), + -1, icon_size); + + fm_directory_view_update_menus (FM_DIRECTORY_VIEW (view)); +} + +static void +fm_list_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment) +{ + FMListView *list_view; + gint new_level; + + g_return_if_fail (FM_IS_LIST_VIEW (view)); + + list_view = FM_LIST_VIEW (view); + new_level = list_view->details->zoom_level + zoom_increment; + + if (new_level >= CAJA_ZOOM_LEVEL_SMALLEST && + new_level <= CAJA_ZOOM_LEVEL_LARGEST) + { + fm_list_view_set_zoom_level (list_view, new_level, FALSE); + } +} + +static CajaZoomLevel +fm_list_view_get_zoom_level (FMDirectoryView *view) +{ + FMListView *list_view; + + g_return_val_if_fail (FM_IS_LIST_VIEW (view), CAJA_ZOOM_LEVEL_STANDARD); + + list_view = FM_LIST_VIEW (view); + + return list_view->details->zoom_level; +} + +static void +fm_list_view_zoom_to_level (FMDirectoryView *view, + CajaZoomLevel zoom_level) +{ + FMListView *list_view; + + g_return_if_fail (FM_IS_LIST_VIEW (view)); + + list_view = FM_LIST_VIEW (view); + + fm_list_view_set_zoom_level (list_view, zoom_level, FALSE); +} + +static void +fm_list_view_restore_default_zoom_level (FMDirectoryView *view) +{ + FMListView *list_view; + + g_return_if_fail (FM_IS_LIST_VIEW (view)); + + list_view = FM_LIST_VIEW (view); + + fm_list_view_set_zoom_level (list_view, get_default_zoom_level (), FALSE); +} + +static gboolean +fm_list_view_can_zoom_in (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_LIST_VIEW (view), FALSE); + + return FM_LIST_VIEW (view)->details->zoom_level < CAJA_ZOOM_LEVEL_LARGEST; +} + +static gboolean +fm_list_view_can_zoom_out (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_LIST_VIEW (view), FALSE); + + return FM_LIST_VIEW (view)->details->zoom_level > CAJA_ZOOM_LEVEL_SMALLEST; +} + +static void +fm_list_view_start_renaming_file (FMDirectoryView *view, + CajaFile *file, + gboolean select_all) +{ + FMListView *list_view; + GtkTreeIter iter; + GtkTreePath *path; + + list_view = FM_LIST_VIEW (view); + + /* Select all if we are in renaming mode already */ + if (list_view->details->file_name_column && list_view->details->editable_widget) + { + gtk_editable_select_region ( + GTK_EDITABLE (list_view->details->editable_widget), + 0, + -1); + return; + } + + if (!fm_list_model_get_first_iter_for_file (list_view->details->model, file, &iter)) + { + return; + } + + /* Freeze updates to the view to prevent losing rename focus when the tree view updates */ + fm_directory_view_freeze_updates (FM_DIRECTORY_VIEW (view)); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (list_view->details->model), &iter); + + /* Make filename-cells editable. */ + g_object_set (G_OBJECT (list_view->details->file_name_cell), + "editable", TRUE, + NULL); + + gtk_tree_view_scroll_to_cell (list_view->details->tree_view, + NULL, + list_view->details->file_name_column, + TRUE, 0.0, 0.0); + gtk_tree_view_set_cursor (list_view->details->tree_view, + path, + list_view->details->file_name_column, + TRUE); + + gtk_tree_path_free (path); +} + +static void +fm_list_view_click_policy_changed (FMDirectoryView *directory_view) +{ + GdkWindow *win; + GdkDisplay *display; + FMListView *view; + GtkTreeIter iter; + GtkTreeView *tree; + + view = FM_LIST_VIEW (directory_view); + + /* ensure that we unset the hand cursor and refresh underlined rows */ + if (click_policy_auto_value == CAJA_CLICK_POLICY_DOUBLE) + { + if (view->details->hover_path != NULL) + { + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->model), + &iter, view->details->hover_path)) + { + gtk_tree_model_row_changed (GTK_TREE_MODEL (view->details->model), + view->details->hover_path, &iter); + } + + gtk_tree_path_free (view->details->hover_path); + view->details->hover_path = NULL; + } + + tree = view->details->tree_view; + if (gtk_widget_get_realized (GTK_WIDGET (tree))) + { + win = gtk_widget_get_window (GTK_WIDGET (tree)); + gdk_window_set_cursor (win, NULL); + + display = gtk_widget_get_display (GTK_WIDGET (view)); + if (display != NULL) + { + gdk_display_flush (display); + } + } + + if (hand_cursor != NULL) + { + gdk_cursor_unref (hand_cursor); + hand_cursor = NULL; + } + } + else if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) + { + if (hand_cursor == NULL) + { + hand_cursor = gdk_cursor_new(GDK_HAND2); + } + } +} + +static void +default_sort_order_changed_callback (gpointer callback_data) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (callback_data); + + set_sort_order_from_metadata_and_preferences (list_view); +} + +static void +default_zoom_level_changed_callback (gpointer callback_data) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (callback_data); + + set_zoom_level_from_metadata_and_preferences (list_view); +} + +static void +default_visible_columns_changed_callback (gpointer callback_data) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (callback_data); + + set_columns_settings_from_metadata_and_preferences (list_view); +} + +static void +default_column_order_changed_callback (gpointer callback_data) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (callback_data); + + set_columns_settings_from_metadata_and_preferences (list_view); +} + +static void +fm_list_view_sort_directories_first_changed (FMDirectoryView *view) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + + fm_list_model_set_should_sort_directories_first (list_view->details->model, + fm_directory_view_should_sort_directories_first (view)); +} + +static int +fm_list_view_compare_files (FMDirectoryView *view, CajaFile *file1, CajaFile *file2) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + return fm_list_model_compare_func (list_view->details->model, file1, file2); +} + +static gboolean +fm_list_view_using_manual_layout (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_LIST_VIEW (view), FALSE); + + return FALSE; +} + +static void +fm_list_view_dispose (GObject *object) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (object); + + if (list_view->details->model) + { + stop_cell_editing (list_view); + g_object_unref (list_view->details->model); + list_view->details->model = NULL; + } + + if (list_view->details->drag_dest) + { + g_object_unref (list_view->details->drag_dest); + list_view->details->drag_dest = NULL; + } + + if (list_view->details->renaming_file_activate_timeout != 0) + { + g_source_remove (list_view->details->renaming_file_activate_timeout); + list_view->details->renaming_file_activate_timeout = 0; + } + + if (list_view->details->clipboard_handler_id != 0) + { + g_signal_handler_disconnect (caja_clipboard_monitor_get (), + list_view->details->clipboard_handler_id); + list_view->details->clipboard_handler_id = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +fm_list_view_finalize (GObject *object) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (object); + + g_free (list_view->details->original_name); + list_view->details->original_name = NULL; + + if (list_view->details->double_click_path[0]) + { + gtk_tree_path_free (list_view->details->double_click_path[0]); + } + if (list_view->details->double_click_path[1]) + { + gtk_tree_path_free (list_view->details->double_click_path[1]); + } + if (list_view->details->new_selection_path) + { + gtk_tree_path_free (list_view->details->new_selection_path); + } + + g_list_free (list_view->details->cells); + g_hash_table_destroy (list_view->details->columns); + + if (list_view->details->hover_path != NULL) + { + gtk_tree_path_free (list_view->details->hover_path); + } + + if (list_view->details->column_editor != NULL) + { + gtk_widget_destroy (list_view->details->column_editor); + } + + g_free (list_view->details); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +fm_list_view_emblems_changed (FMDirectoryView *directory_view) +{ + g_assert (FM_IS_LIST_VIEW (directory_view)); + + /* FIXME: This needs to update the emblems of the icons, since + * relative emblems may have changed. + */ +} + +static char * +fm_list_view_get_first_visible_file (CajaView *view) +{ + CajaFile *file; + GtkTreePath *path; + GtkTreeIter iter; + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + + if (gtk_tree_view_get_path_at_pos (list_view->details->tree_view, + 0, 0, + &path, NULL, NULL, NULL)) + { + gtk_tree_model_get_iter (GTK_TREE_MODEL (list_view->details->model), + &iter, path); + + gtk_tree_path_free (path); + + gtk_tree_model_get (GTK_TREE_MODEL (list_view->details->model), + &iter, + FM_LIST_MODEL_FILE_COLUMN, &file, + -1); + if (file) + { + char *uri; + + uri = caja_file_get_uri (file); + + caja_file_unref (file); + + return uri; + } + } + + return NULL; +} + +static void +fm_list_view_scroll_to_file (FMListView *view, + CajaFile *file) +{ + GtkTreePath *path; + GtkTreeIter iter; + + if (!fm_list_model_get_first_iter_for_file (view->details->model, file, &iter)) + { + return; + } + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->details->model), &iter); + + gtk_tree_view_scroll_to_cell (view->details->tree_view, + path, NULL, + TRUE, 0.0, 0.0); + + gtk_tree_path_free (path); +} + +static void +list_view_scroll_to_file (CajaView *view, + const char *uri) +{ + CajaFile *file; + + if (uri != NULL) + { + /* Only if existing, since we don't want to add the file to + the directory if it has been removed since then */ + file = caja_file_get_existing_by_uri (uri); + if (file != NULL) + { + fm_list_view_scroll_to_file (FM_LIST_VIEW (view), file); + caja_file_unref (file); + } + } +} + +static void +list_view_notify_clipboard_info (CajaClipboardMonitor *monitor, + CajaClipboardInfo *info, + FMListView *view) +{ + /* this could be called as a result of _end_loading() being + * called after _dispose(), where the model is cleared. + */ + if (view->details->model == NULL) + { + return; + } + + if (info != NULL && info->cut) + { + fm_list_model_set_highlight_for_files (view->details->model, info->files); + } + else + { + fm_list_model_set_highlight_for_files (view->details->model, NULL); + } +} + +static void +fm_list_view_end_loading (FMDirectoryView *view, + gboolean all_files_seen) +{ + CajaClipboardMonitor *monitor; + CajaClipboardInfo *info; + + monitor = caja_clipboard_monitor_get (); + info = caja_clipboard_monitor_get_clipboard_info (monitor); + + list_view_notify_clipboard_info (monitor, info, FM_LIST_VIEW (view)); +} + +static void +real_set_is_active (FMDirectoryView *view, + gboolean is_active) +{ + GtkWidget *tree_view; + GtkStyle *style; + GdkColor color; + + tree_view = GTK_WIDGET (fm_list_view_get_tree_view (FM_LIST_VIEW (view))); + + if (is_active) + { + gtk_widget_modify_base (tree_view, GTK_STATE_NORMAL, NULL); + } + else + { + style = gtk_widget_get_style (tree_view); + color = style->base[GTK_STATE_INSENSITIVE]; + gtk_widget_modify_base (tree_view, GTK_STATE_NORMAL, &color); + } + + EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, + set_is_active, (view, is_active)); +} + +static void +fm_list_view_class_init (FMListViewClass *class) +{ + FMDirectoryViewClass *fm_directory_view_class; + + fm_directory_view_class = FM_DIRECTORY_VIEW_CLASS (class); + + G_OBJECT_CLASS (class)->dispose = fm_list_view_dispose; + G_OBJECT_CLASS (class)->finalize = fm_list_view_finalize; + + fm_directory_view_class->add_file = fm_list_view_add_file; + fm_directory_view_class->begin_loading = fm_list_view_begin_loading; + fm_directory_view_class->end_loading = fm_list_view_end_loading; + fm_directory_view_class->bump_zoom_level = fm_list_view_bump_zoom_level; + fm_directory_view_class->can_zoom_in = fm_list_view_can_zoom_in; + fm_directory_view_class->can_zoom_out = fm_list_view_can_zoom_out; + fm_directory_view_class->click_policy_changed = fm_list_view_click_policy_changed; + fm_directory_view_class->clear = fm_list_view_clear; + fm_directory_view_class->file_changed = fm_list_view_file_changed; + fm_directory_view_class->get_background_widget = fm_list_view_get_background_widget; + fm_directory_view_class->get_selection = fm_list_view_get_selection; + fm_directory_view_class->get_selection_for_file_transfer = fm_list_view_get_selection_for_file_transfer; + fm_directory_view_class->get_item_count = fm_list_view_get_item_count; + fm_directory_view_class->is_empty = fm_list_view_is_empty; + fm_directory_view_class->remove_file = fm_list_view_remove_file; + fm_directory_view_class->merge_menus = fm_list_view_merge_menus; + fm_directory_view_class->unmerge_menus = fm_list_view_unmerge_menus; + fm_directory_view_class->update_menus = fm_list_view_update_menus; + fm_directory_view_class->reset_to_defaults = fm_list_view_reset_to_defaults; + fm_directory_view_class->restore_default_zoom_level = fm_list_view_restore_default_zoom_level; + fm_directory_view_class->reveal_selection = fm_list_view_reveal_selection; + fm_directory_view_class->select_all = fm_list_view_select_all; + fm_directory_view_class->set_selection = fm_list_view_set_selection; + fm_directory_view_class->invert_selection = fm_list_view_invert_selection; + fm_directory_view_class->compare_files = fm_list_view_compare_files; + fm_directory_view_class->sort_directories_first_changed = fm_list_view_sort_directories_first_changed; + fm_directory_view_class->start_renaming_file = fm_list_view_start_renaming_file; + fm_directory_view_class->get_zoom_level = fm_list_view_get_zoom_level; + fm_directory_view_class->zoom_to_level = fm_list_view_zoom_to_level; + fm_directory_view_class->emblems_changed = fm_list_view_emblems_changed; + fm_directory_view_class->end_file_changes = fm_list_view_end_file_changes; + fm_directory_view_class->using_manual_layout = fm_list_view_using_manual_layout; + fm_directory_view_class->set_is_active = real_set_is_active; + + eel_preferences_add_auto_enum (CAJA_PREFERENCES_CLICK_POLICY, + &click_policy_auto_value); + eel_preferences_add_auto_string (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_ORDER, + (const char **) &default_sort_order_auto_value); + eel_preferences_add_auto_boolean (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER, + &default_sort_reversed_auto_value); + eel_preferences_add_auto_enum (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL, + (int *) &default_zoom_level_auto_value); + eel_preferences_add_auto_string_array (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS, + &default_visible_columns_auto_value); + eel_preferences_add_auto_string_array (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER, + &default_column_order_auto_value); +} + +static const char * +fm_list_view_get_id (CajaView *view) +{ + return FM_LIST_VIEW_ID; +} + + +static void +fm_list_view_iface_init (CajaViewIface *iface) +{ + fm_directory_view_init_view_iface (iface); + + iface->get_view_id = fm_list_view_get_id; + iface->get_first_visible_file = fm_list_view_get_first_visible_file; + iface->scroll_to_file = list_view_scroll_to_file; + iface->get_title = NULL; +} + + +static void +fm_list_view_init (FMListView *list_view) +{ + list_view->details = g_new0 (FMListViewDetails, 1); + + create_and_set_up_tree_view (list_view); + + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_ORDER, + default_sort_order_changed_callback, + list_view, G_OBJECT (list_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER, + default_sort_order_changed_callback, + list_view, G_OBJECT (list_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL, + default_zoom_level_changed_callback, + list_view, G_OBJECT (list_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS, + default_visible_columns_changed_callback, + list_view, G_OBJECT (list_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER, + default_column_order_changed_callback, + list_view, G_OBJECT (list_view)); + + fm_list_view_click_policy_changed (FM_DIRECTORY_VIEW (list_view)); + + fm_list_view_sort_directories_first_changed (FM_DIRECTORY_VIEW (list_view)); + + /* ensure that the zoom level is always set in begin_loading */ + list_view->details->zoom_level = CAJA_ZOOM_LEVEL_SMALLEST - 1; + + list_view->details->hover_path = NULL; + list_view->details->clipboard_handler_id = + g_signal_connect (caja_clipboard_monitor_get (), + "clipboard_info", + G_CALLBACK (list_view_notify_clipboard_info), list_view); +} + +static CajaView * +fm_list_view_create (CajaWindowSlotInfo *slot) +{ + FMListView *view; + + view = g_object_new (FM_TYPE_LIST_VIEW, + "window-slot", slot, + NULL); + return CAJA_VIEW (view); +} + +static gboolean +fm_list_view_supports_uri (const char *uri, + GFileType file_type, + const char *mime_type) +{ + if (file_type == G_FILE_TYPE_DIRECTORY) + { + return TRUE; + } + if (strcmp (mime_type, CAJA_SAVED_SEARCH_MIMETYPE) == 0) + { + return TRUE; + } + if (g_str_has_prefix (uri, "trash:")) + { + return TRUE; + } + if (g_str_has_prefix (uri, EEL_SEARCH_URI)) + { + return TRUE; + } + + return FALSE; +} + +static CajaViewInfo fm_list_view = +{ + FM_LIST_VIEW_ID, + /* translators: this is used in the view selection dropdown + * of navigation windows and in the preferences dialog */ + N_("List View"), + /* translators: this is used in the view menu */ + N_("_List"), + N_("The list view encountered an error."), + N_("The list view encountered an error while starting up."), + N_("Display this location with the list view."), + fm_list_view_create, + fm_list_view_supports_uri +}; + +void +fm_list_view_register (void) +{ + fm_list_view.view_combo_label = _(fm_list_view.view_combo_label); + fm_list_view.view_menu_label_with_mnemonic = _(fm_list_view.view_menu_label_with_mnemonic); + fm_list_view.error_label = _(fm_list_view.error_label); + fm_list_view.startup_error_label = _(fm_list_view.startup_error_label); + fm_list_view.display_location_label = _(fm_list_view.display_location_label); + + caja_view_factory_register (&fm_list_view); +} + +GtkTreeView* +fm_list_view_get_tree_view (FMListView *list_view) +{ + return list_view->details->tree_view; +} |