/* * xed-file-browser-view.c - Xed plugin providing easy file access * from the sidepanel * * Copyright (C) 2006 - Jesse van den Kieboom * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "xed-file-browser-store.h" #include "xed-file-bookmarks-store.h" #include "xed-file-browser-view.h" #include "xed-file-browser-marshal.h" #include "xed-file-browser-enum-types.h" #define XED_FILE_BROWSER_VIEW_GET_PRIVATE(object)( \ G_TYPE_INSTANCE_GET_PRIVATE((object), \ XED_TYPE_FILE_BROWSER_VIEW, XedFileBrowserViewPrivate)) struct _XedFileBrowserViewPrivate { GtkTreeViewColumn *column; GtkCellRenderer *pixbuf_renderer; GtkCellRenderer *text_renderer; GtkTreeModel *model; GtkTreeRowReference *editable; GdkCursor *busy_cursor; /* CLick policy */ XedFileBrowserViewClickPolicy click_policy; GtkTreePath *double_click_path[2]; /* Both clicks in a double click need to be on the same row */ GtkTreePath *hover_path; GdkCursor *hand_cursor; gboolean ignore_release; gboolean selected_on_button_down; gint drag_button; gboolean drag_started; gboolean restore_expand_state; gboolean is_refresh; GHashTable * expand_state; }; /* Properties */ enum { PROP_0, PROP_CLICK_POLICY, PROP_RESTORE_EXPAND_STATE }; /* Signals */ enum { ERROR, FILE_ACTIVATED, DIRECTORY_ACTIVATED, BOOKMARK_ACTIVATED, NUM_SIGNALS }; static guint signals[NUM_SIGNALS] = { 0 }; static const GtkTargetEntry drag_source_targets[] = { { "text/uri-list", 0, 0 } }; G_DEFINE_DYNAMIC_TYPE (XedFileBrowserView, xed_file_browser_view, GTK_TYPE_TREE_VIEW) static void on_cell_edited (GtkCellRendererText *cell, gchar *path, gchar *new_text, XedFileBrowserView *tree_view); static void on_begin_refresh (XedFileBrowserStore *model, XedFileBrowserView *view); static void on_end_refresh (XedFileBrowserStore *model, XedFileBrowserView *view); static void on_unload (XedFileBrowserStore *model, GFile *location, XedFileBrowserView *view); static void on_row_inserted (XedFileBrowserStore *model, GtkTreePath *path, GtkTreeIter *iter, XedFileBrowserView *view); static void xed_file_browser_view_finalize (GObject *object) { XedFileBrowserView *obj = XED_FILE_BROWSER_VIEW(object); if (obj->priv->hand_cursor) { g_object_unref (obj->priv->hand_cursor); } if (obj->priv->hover_path) { gtk_tree_path_free (obj->priv->hover_path); } if (obj->priv->expand_state) { g_hash_table_destroy (obj->priv->expand_state); obj->priv->expand_state = NULL; } g_object_unref (obj->priv->busy_cursor); G_OBJECT_CLASS (xed_file_browser_view_parent_class)->finalize (object); } static void add_expand_state (XedFileBrowserView *view, GFile *location) { if (!location) { return; } if (view->priv->expand_state) { g_hash_table_insert (view->priv->expand_state, location, g_object_ref (location)); } } static void remove_expand_state (XedFileBrowserView *view, GFile *location) { if (!location) { return; } if (view->priv->expand_state) { g_hash_table_remove (view->priv->expand_state, location); } } static void row_expanded (GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path) { XedFileBrowserView *view = XED_FILE_BROWSER_VIEW (tree_view); GFile *location; if (GTK_TREE_VIEW_CLASS (xed_file_browser_view_parent_class)->row_expanded) { GTK_TREE_VIEW_CLASS (xed_file_browser_view_parent_class)->row_expanded (tree_view, iter, path); } if (!XED_IS_FILE_BROWSER_STORE (view->priv->model)) { return; } if (view->priv->restore_expand_state) { gtk_tree_model_get (view->priv->model, iter, XED_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, -1); add_expand_state (view, location); } _xed_file_browser_store_iter_expanded (XED_FILE_BROWSER_STORE (view->priv->model), iter); } static void row_collapsed (GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path) { XedFileBrowserView *view = XED_FILE_BROWSER_VIEW (tree_view); GFile *location; if (GTK_TREE_VIEW_CLASS (xed_file_browser_view_parent_class)->row_collapsed) { GTK_TREE_VIEW_CLASS (xed_file_browser_view_parent_class)->row_collapsed (tree_view, iter, path); } if (!XED_IS_FILE_BROWSER_STORE (view->priv->model)) { return; } if (view->priv->restore_expand_state) { gtk_tree_model_get (view->priv->model, iter, XED_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, -1); remove_expand_state (view, location); } _xed_file_browser_store_iter_collapsed (XED_FILE_BROWSER_STORE (view->priv->model), iter); } static gboolean leave_notify_event (GtkWidget *widget, GdkEventCrossing *event) { XedFileBrowserView *view = XED_FILE_BROWSER_VIEW (widget); if (view->priv->click_policy == XED_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE && view->priv->hover_path != NULL) { gtk_tree_path_free (view->priv->hover_path); view->priv->hover_path = NULL; } // Chainup return GTK_WIDGET_CLASS (xed_file_browser_view_parent_class)->leave_notify_event (widget, event); } static gboolean enter_notify_event (GtkWidget *widget, GdkEventCrossing *event) { XedFileBrowserView *view = XED_FILE_BROWSER_VIEW (widget); if (view->priv->click_policy == XED_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) { if (view->priv->hover_path != NULL) { gtk_tree_path_free (view->priv->hover_path); } gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), event->x, event->y, &view->priv->hover_path, NULL, NULL, NULL); if (view->priv->hover_path != NULL) { gdk_window_set_cursor (gtk_widget_get_window (widget), view->priv->hand_cursor); } } // Chainup return GTK_WIDGET_CLASS (xed_file_browser_view_parent_class)->enter_notify_event (widget, event); } static gboolean motion_notify_event (GtkWidget *widget, GdkEventMotion *event) { GtkTreePath *old_hover_path; XedFileBrowserView *view = XED_FILE_BROWSER_VIEW (widget); if (view->priv->click_policy == XED_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) { old_hover_path = view->priv->hover_path; gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), event->x, event->y, &view->priv->hover_path, NULL, NULL, NULL); if ((old_hover_path != NULL) != (view->priv->hover_path != NULL)) { if (view->priv->hover_path != NULL) { gdk_window_set_cursor (gtk_widget_get_window (widget), view->priv->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); } } // Chainup return GTK_WIDGET_CLASS (xed_file_browser_view_parent_class)->motion_notify_event (widget, event); } static void set_click_policy_property (XedFileBrowserView *obj, XedFileBrowserViewClickPolicy click_policy) { GtkTreeIter iter; GdkDisplay *display; GdkWindow *win; obj->priv->click_policy = click_policy; if (click_policy == XED_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) { if (obj->priv->hand_cursor == NULL) { obj->priv->hand_cursor = gdk_cursor_new(GDK_HAND2); } } else if (click_policy == XED_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE) { if (obj->priv->hover_path != NULL) { if (gtk_tree_model_get_iter (GTK_TREE_MODEL (obj->priv->model), &iter, obj->priv->hover_path)) { gtk_tree_model_row_changed (GTK_TREE_MODEL (obj->priv->model), obj->priv->hover_path, &iter); } gtk_tree_path_free (obj->priv->hover_path); obj->priv->hover_path = NULL; } if (gtk_widget_get_realized (GTK_WIDGET (obj))) { win = gtk_widget_get_window (GTK_WIDGET (obj)); gdk_window_set_cursor (win, NULL); display = gtk_widget_get_display (GTK_WIDGET (obj)); if (display != NULL) { gdk_display_flush (display); } } if (obj->priv->hand_cursor) { g_object_unref (obj->priv->hand_cursor); obj->priv->hand_cursor = NULL; } } } static void directory_activated (XedFileBrowserView *view, GtkTreeIter *iter) { xed_file_browser_store_set_virtual_root (XED_FILE_BROWSER_STORE (view->priv->model), iter); } static void activate_selected_files (XedFileBrowserView *view) { GtkTreeView *tree_view = GTK_TREE_VIEW (view); GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view); GList *rows, *row; GtkTreePath *directory = NULL; GtkTreePath *path; GtkTreeIter iter; XedFileBrowserStoreFlag flags; rows = gtk_tree_selection_get_selected_rows (selection, &view->priv->model); for (row = rows; row; row = row->next) { path = (GtkTreePath *)(row->data); /* Get iter from path */ if (!gtk_tree_model_get_iter (view->priv->model, &iter, path)) { continue; } gtk_tree_model_get (view->priv->model, &iter, XED_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, -1); if (FILE_IS_DIR (flags)) { if (directory == NULL) { directory = path; } } else if (!FILE_IS_DUMMY (flags)) { g_signal_emit (view, signals[FILE_ACTIVATED], 0, &iter); } } if (directory != NULL) { if (gtk_tree_model_get_iter (view->priv->model, &iter, directory)) { g_signal_emit (view, signals[DIRECTORY_ACTIVATED], 0, &iter); } } g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL); g_list_free (rows); } static void activate_selected_bookmark (XedFileBrowserView *view) { GtkTreeView *tree_view = GTK_TREE_VIEW (view); GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view); GtkTreeIter iter; if (gtk_tree_selection_get_selected (selection, &view->priv->model, &iter)) { g_signal_emit (view, signals[BOOKMARK_ACTIVATED], 0, &iter); } } static void activate_selected_items (XedFileBrowserView *view) { if (XED_IS_FILE_BROWSER_STORE (view->priv->model)) { activate_selected_files (view); } else if (XED_IS_FILE_BOOKMARKS_STORE (view->priv->model)) { activate_selected_bookmark (view); } } static void toggle_hidden_filter (XedFileBrowserView *view) { XedFileBrowserStoreFilterMode mode; if (XED_IS_FILE_BROWSER_STORE (view->priv->model)) { mode = xed_file_browser_store_get_filter_mode (XED_FILE_BROWSER_STORE (view->priv->model)); mode ^= XED_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN; xed_file_browser_store_set_filter_mode (XED_FILE_BROWSER_STORE (view->priv->model), mode); } } static gboolean button_event_modifies_selection (GdkEventButton *event) { return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0; } static void drag_begin (GtkWidget *widget, GdkDragContext *context) { XedFileBrowserView *view = XED_FILE_BROWSER_VIEW (widget); view->priv->drag_button = 0; view->priv->drag_started = TRUE; /* Chain up */ GTK_WIDGET_CLASS (xed_file_browser_view_parent_class)->drag_begin (widget, context); } static void did_not_drag (XedFileBrowserView *view, GdkEventButton *event) { GtkTreeView *tree_view; GtkTreeSelection *selection; GtkTreePath *path; tree_view = GTK_TREE_VIEW (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 ((view->priv->click_policy == XED_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) && !button_event_modifies_selection(event) && (event->button == 1 || event->button == 2)) { /* Activate all selected items, and leave them selected */ activate_selected_items (view); } else if ((event->button == 1 || event->button == 2) && ((event->state & GDK_CONTROL_MASK) != 0 || (event->state & GDK_SHIFT_MASK) == 0) && view->priv->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); } } gtk_tree_path_free (path); } } static gboolean button_release_event (GtkWidget *widget, GdkEventButton *event) { XedFileBrowserView *view = XED_FILE_BROWSER_VIEW (widget); if (event->button == view->priv->drag_button) { view->priv->drag_button = 0; if (!view->priv->drag_started && !view->priv->ignore_release) { did_not_drag (view, event); } } /* Chain up */ return GTK_WIDGET_CLASS (xed_file_browser_view_parent_class)->button_release_event (widget, event); } static gboolean button_press_event (GtkWidget *widget, GdkEventButton *event) { int double_click_time; static int click_count = 0; static guint32 last_click_time = 0; XedFileBrowserView *view; GtkTreeView *tree_view; GtkTreeSelection *selection; GtkTreePath *path; int expander_size; int horizontal_separator; gboolean on_expander; gboolean call_parent; gboolean selected; GtkWidgetClass *widget_parent = GTK_WIDGET_CLASS(xed_file_browser_view_parent_class); tree_view = GTK_TREE_VIEW (widget); view = XED_FILE_BROWSER_VIEW (widget); selection = gtk_tree_view_get_selection (tree_view); /* Get double click time */ g_object_get (G_OBJECT (gtk_widget_get_settings (widget)), "gtk-double-click-time", &double_click_time, NULL); /* Determine click count */ if (event->time - last_click_time < double_click_time) { click_count++; } else { click_count = 0; } last_click_time = event->time; /* Ignore double click if we are in single click mode */ if (view->priv->click_policy == XED_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE && click_count >= 2) { return TRUE; } view->priv->ignore_release = FALSE; call_parent = TRUE; if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, &path, NULL, NULL, NULL)) { /* Keep track of path of last click so double clicks only happen * on the same item */ if ((event->button == 1 || event->button == 2) && event->type == GDK_BUTTON_PRESS) { if (view->priv->double_click_path[1]) { gtk_tree_path_free (view->priv->double_click_path[1]); } view->priv->double_click_path[1] = view->priv->double_click_path[0]; view->priv->double_click_path[0] = gtk_tree_path_copy (path); } if (event->type == GDK_2BUTTON_PRESS) { /* Chain up, must be before activating the selected items because this will cause the view to grab focus */ widget_parent->button_press_event (widget, event); if (view->priv->double_click_path[1] && gtk_tree_path_compare (view->priv->double_click_path[0], view->priv->double_click_path[1]) == 0) { activate_selected_items (view); } } 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. */ selected = gtk_tree_selection_path_is_selected (selection, path); if (event->button == 3 && selected) { call_parent = FALSE; } if ((event->button == 1 || event->button == 2) && ((event->state & GDK_CONTROL_MASK) != 0 || (event->state & GDK_SHIFT_MASK) == 0)) { gtk_widget_style_get (widget, "expander-size", &expander_size, "horizontal-separator", &horizontal_separator, NULL); on_expander = (event->x <= horizontal_separator / 2 + gtk_tree_path_get_depth (path) * expander_size); view->priv->selected_on_button_down = selected; if (selected) { call_parent = on_expander || gtk_tree_selection_count_selected_rows (selection) == 1; view->priv->ignore_release = call_parent && view->priv->click_policy != XED_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE; } else if ((event->state & GDK_CONTROL_MASK) != 0) { call_parent = FALSE; gtk_tree_selection_select_path (selection, path); } else { view->priv->ignore_release = on_expander; } } if (call_parent) { /* Chain up */ widget_parent->button_press_event (widget, event); } else if (selected) { gtk_widget_grab_focus (widget); } if ((event->button == 1 || event->button == 2) && event->type == GDK_BUTTON_PRESS) { view->priv->drag_started = FALSE; view->priv->drag_button = event->button; } } gtk_tree_path_free (path); } else { if ((event->button == 1 || event->button == 2) && event->type == GDK_BUTTON_PRESS) { if (view->priv->double_click_path[1]) { gtk_tree_path_free (view->priv->double_click_path[1]); } view->priv->double_click_path[1] = view->priv->double_click_path[0]; view->priv->double_click_path[0] = NULL; } gtk_tree_selection_unselect_all (selection); /* Chain up */ widget_parent->button_press_event (widget, event); } /* We already chained up if nescessary, so just return TRUE */ return TRUE; } static gboolean key_press_event (GtkWidget *widget, GdkEventKey *event) { XedFileBrowserView *view; guint modifiers; gboolean handled; view = XED_FILE_BROWSER_VIEW (widget); handled = FALSE; modifiers = gtk_accelerator_get_default_mod_mask (); switch (event->keyval) { case GDK_KEY_space: if (event->state & GDK_CONTROL_MASK) { handled = FALSE; break; } if (!gtk_widget_has_focus (widget)) { handled = FALSE; break; } activate_selected_items (view); handled = TRUE; break; case GDK_KEY_Return: case GDK_KEY_KP_Enter: activate_selected_items (view); handled = TRUE; break; case GDK_KEY_h: if ((event->state & modifiers) == GDK_CONTROL_MASK) { toggle_hidden_filter (view); handled = TRUE; break; } default: handled = FALSE; } /* Chain up */ if (!handled) { return GTK_WIDGET_CLASS (xed_file_browser_view_parent_class)->key_press_event (widget, event); } return TRUE; } static void fill_expand_state (XedFileBrowserView *view, GtkTreeIter *iter) { GtkTreePath * path; GtkTreeIter child; GFile *location; if (!gtk_tree_model_iter_has_child (view->priv->model, iter)) { return; } path = gtk_tree_model_get_path (view->priv->model, iter); if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (view), path)) { gtk_tree_model_get (view->priv->model, iter, XED_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, -1); add_expand_state (view, location); } if (gtk_tree_model_iter_children (view->priv->model, &child, iter)) { do { fill_expand_state (view, &child); } while (gtk_tree_model_iter_next (view->priv->model, &child)); } gtk_tree_path_free (path); } static void uninstall_restore_signals (XedFileBrowserView *tree_view, GtkTreeModel *model) { g_signal_handlers_disconnect_by_func (model, on_begin_refresh, tree_view); g_signal_handlers_disconnect_by_func (model, on_end_refresh, tree_view); g_signal_handlers_disconnect_by_func (model, on_unload, tree_view); g_signal_handlers_disconnect_by_func (model, on_row_inserted, tree_view); } static void install_restore_signals (XedFileBrowserView *tree_view, GtkTreeModel *model) { g_signal_connect (model, "begin-refresh", G_CALLBACK (on_begin_refresh), tree_view); g_signal_connect (model, "end-refresh", G_CALLBACK (on_end_refresh), tree_view); g_signal_connect (model, "unload", G_CALLBACK (on_unload), tree_view); g_signal_connect_after (model, "row-inserted", G_CALLBACK (on_row_inserted), tree_view); } static void set_restore_expand_state (XedFileBrowserView *view, gboolean state) { if (state == view->priv->restore_expand_state) { return; } if (view->priv->expand_state) { g_hash_table_destroy (view->priv->expand_state); view->priv->expand_state = NULL; } if (state) { view->priv->expand_state = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL); if (view->priv->model && XED_IS_FILE_BROWSER_STORE (view->priv->model)) { fill_expand_state (view, NULL); install_restore_signals (view, view->priv->model); } } else if (view->priv->model && XED_IS_FILE_BROWSER_STORE (view->priv->model)) { uninstall_restore_signals (view, view->priv->model); } view->priv->restore_expand_state = state; } static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { XedFileBrowserView *obj = XED_FILE_BROWSER_VIEW (object); switch (prop_id) { case PROP_CLICK_POLICY: g_value_set_enum (value, obj->priv->click_policy); break; case PROP_RESTORE_EXPAND_STATE: g_value_set_boolean (value, obj->priv->restore_expand_state); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { XedFileBrowserView *obj = XED_FILE_BROWSER_VIEW (object); switch (prop_id) { case PROP_CLICK_POLICY: set_click_policy_property (obj, g_value_get_enum (value)); break; case PROP_RESTORE_EXPAND_STATE: set_restore_expand_state (obj, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void xed_file_browser_view_class_init (XedFileBrowserViewClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->finalize = xed_file_browser_view_finalize; object_class->get_property = get_property; object_class->set_property = set_property; /* Event handlers */ widget_class->motion_notify_event = motion_notify_event; widget_class->enter_notify_event = enter_notify_event; widget_class->leave_notify_event = leave_notify_event; widget_class->button_press_event = button_press_event; widget_class->button_release_event = button_release_event; widget_class->drag_begin = drag_begin; widget_class->key_press_event = key_press_event; /* Tree view handlers */ tree_view_class->row_expanded = row_expanded; tree_view_class->row_collapsed = row_collapsed; /* Default handlers */ klass->directory_activated = directory_activated; g_object_class_install_property (object_class, PROP_CLICK_POLICY, g_param_spec_enum ("click-policy", "Click Policy", "The click policy", XED_TYPE_FILE_BROWSER_VIEW_CLICK_POLICY, XED_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_RESTORE_EXPAND_STATE, g_param_spec_boolean ("restore-expand-state", "Restore Expand State", "Restore expanded state of loaded directories", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); signals[ERROR] = g_signal_new ("error", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (XedFileBrowserViewClass, error), NULL, NULL, xed_file_browser_marshal_VOID__UINT_STRING, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING); signals[FILE_ACTIVATED] = g_signal_new ("file-activated", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (XedFileBrowserViewClass, file_activated), NULL, NULL, g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER); signals[DIRECTORY_ACTIVATED] = g_signal_new ("directory-activated", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (XedFileBrowserViewClass, directory_activated), NULL, NULL, g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER); signals[BOOKMARK_ACTIVATED] = g_signal_new ("bookmark-activated", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (XedFileBrowserViewClass, bookmark_activated), NULL, NULL, g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER); g_type_class_add_private (object_class, sizeof (XedFileBrowserViewPrivate)); } static void xed_file_browser_view_class_finalize (XedFileBrowserViewClass *klass) { /* dummy function - used by G_DEFINE_DYNAMIC_TYPE */ } static void cell_data_cb (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, XedFileBrowserView *obj) { GtkTreePath *path; PangoUnderline underline = PANGO_UNDERLINE_NONE; gboolean editable = FALSE; path = gtk_tree_model_get_path (tree_model, iter); if (obj->priv->click_policy == XED_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) { if (obj->priv->hover_path != NULL && gtk_tree_path_compare (path, obj->priv->hover_path) == 0) { underline = PANGO_UNDERLINE_SINGLE; } } if (XED_IS_FILE_BROWSER_STORE (tree_model)) { if (obj->priv->editable != NULL && gtk_tree_row_reference_valid (obj->priv->editable)) { GtkTreePath *edpath = gtk_tree_row_reference_get_path (obj->priv->editable); editable = edpath && gtk_tree_path_compare (path, edpath) == 0; } } gtk_tree_path_free (path); g_object_set (cell, "editable", editable, "underline", underline, NULL); } static void xed_file_browser_view_init (XedFileBrowserView *obj) { obj->priv = XED_FILE_BROWSER_VIEW_GET_PRIVATE (obj); obj->priv->column = gtk_tree_view_column_new (); obj->priv->pixbuf_renderer = gtk_cell_renderer_pixbuf_new (); gtk_tree_view_column_pack_start (obj->priv->column, obj->priv->pixbuf_renderer, FALSE); gtk_tree_view_column_add_attribute (obj->priv->column, obj->priv->pixbuf_renderer, "pixbuf", XED_FILE_BROWSER_STORE_COLUMN_ICON); obj->priv->text_renderer = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start (obj->priv->column, obj->priv->text_renderer, TRUE); gtk_tree_view_column_add_attribute (obj->priv->column, obj->priv->text_renderer, "text", XED_FILE_BROWSER_STORE_COLUMN_NAME); g_signal_connect (obj->priv->text_renderer, "edited", G_CALLBACK (on_cell_edited), obj); gtk_tree_view_append_column (GTK_TREE_VIEW (obj), obj->priv->column); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (obj), FALSE); gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (obj), GDK_BUTTON1_MASK, drag_source_targets, G_N_ELEMENTS (drag_source_targets), GDK_ACTION_COPY); obj->priv->busy_cursor = gdk_cursor_new (GDK_WATCH); } static gboolean bookmarks_separator_func (GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data) { guint flags; gtk_tree_model_get (model, iter, XED_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &flags, -1); return (flags & XED_FILE_BOOKMARKS_STORE_IS_SEPARATOR); } /* Public */ GtkWidget * xed_file_browser_view_new (void) { XedFileBrowserView *obj = XED_FILE_BROWSER_VIEW (g_object_new (XED_TYPE_FILE_BROWSER_VIEW, NULL)); return GTK_WIDGET (obj); } void xed_file_browser_view_set_model (XedFileBrowserView *tree_view, GtkTreeModel *model) { GtkTreeSelection *selection; if (tree_view->priv->model == model) { return; } selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)); if (XED_IS_FILE_BOOKMARKS_STORE (model)) { gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (tree_view), bookmarks_separator_func, NULL, NULL); gtk_tree_view_column_set_cell_data_func (tree_view->priv-> column, tree_view->priv-> text_renderer, (GtkTreeCellDataFunc) cell_data_cb, tree_view, NULL); } else { gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (tree_view), NULL, NULL, NULL); gtk_tree_view_column_set_cell_data_func (tree_view->priv-> column, tree_view->priv-> text_renderer, (GtkTreeCellDataFunc) cell_data_cb, tree_view, NULL); if (tree_view->priv->restore_expand_state) { install_restore_signals (tree_view, model); } } if (tree_view->priv->hover_path != NULL) { gtk_tree_path_free (tree_view->priv->hover_path); tree_view->priv->hover_path = NULL; } if (XED_IS_FILE_BROWSER_STORE (tree_view->priv->model)) { if (tree_view->priv->restore_expand_state) { uninstall_restore_signals (tree_view, tree_view->priv->model); } } tree_view->priv->model = model; gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), model); } void xed_file_browser_view_start_rename (XedFileBrowserView *tree_view, GtkTreeIter *iter) { guint flags; GtkTreeRowReference *rowref; GtkTreePath *path; g_return_if_fail (XED_IS_FILE_BROWSER_VIEW (tree_view)); g_return_if_fail (XED_IS_FILE_BROWSER_STORE (tree_view->priv->model)); g_return_if_fail (iter != NULL); gtk_tree_model_get (tree_view->priv->model, iter, XED_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, -1); if (!(FILE_IS_DIR (flags) || !FILE_IS_DUMMY (flags))) { return; } path = gtk_tree_model_get_path (tree_view->priv->model, iter); rowref = gtk_tree_row_reference_new (tree_view->priv->model, path); /* Start editing */ gtk_widget_grab_focus (GTK_WIDGET (tree_view)); if (gtk_tree_path_up (path)) { gtk_tree_view_expand_to_path (GTK_TREE_VIEW (tree_view), path); } gtk_tree_path_free (path); tree_view->priv->editable = rowref; gtk_tree_view_set_cursor (GTK_TREE_VIEW (tree_view), gtk_tree_row_reference_get_path (tree_view->priv->editable), tree_view->priv->column, TRUE); gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree_view), gtk_tree_row_reference_get_path (tree_view->priv->editable), tree_view->priv->column, FALSE, 0.0, 0.0); } void xed_file_browser_view_set_click_policy (XedFileBrowserView *tree_view, XedFileBrowserViewClickPolicy policy) { g_return_if_fail (XED_IS_FILE_BROWSER_VIEW (tree_view)); set_click_policy_property (tree_view, policy); g_object_notify (G_OBJECT (tree_view), "click-policy"); } void xed_file_browser_view_set_restore_expand_state (XedFileBrowserView *tree_view, gboolean restore_expand_state) { g_return_if_fail (XED_IS_FILE_BROWSER_VIEW (tree_view)); set_restore_expand_state (tree_view, restore_expand_state); g_object_notify (G_OBJECT (tree_view), "restore-expand-state"); } /* Signal handlers */ static void on_cell_edited (GtkCellRendererText *cell, gchar *path, gchar *new_text, XedFileBrowserView *tree_view) { GtkTreePath * treepath; GtkTreeIter iter; gboolean ret; GError * error = NULL; gtk_tree_row_reference_free (tree_view->priv->editable); tree_view->priv->editable = NULL; if (new_text == NULL || *new_text == '\0') { return; } treepath = gtk_tree_path_new_from_string (path); ret = gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_view->priv->model), &iter, treepath); gtk_tree_path_free (treepath); if (ret) { if (xed_file_browser_store_rename (XED_FILE_BROWSER_STORE (tree_view->priv->model), &iter, new_text, &error)) { treepath = gtk_tree_model_get_path (GTK_TREE_MODEL (tree_view->priv->model), &iter); gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree_view), treepath, NULL, FALSE, 0.0, 0.0); gtk_tree_path_free (treepath); } else { if (error) { g_signal_emit (tree_view, signals[ERROR], 0, error->code, error->message); g_error_free (error); } } } } static void on_begin_refresh (XedFileBrowserStore *model, XedFileBrowserView *view) { /* Store the refresh state, so we can handle unloading of nodes while refreshing properly */ view->priv->is_refresh = TRUE; } static void on_end_refresh (XedFileBrowserStore *model, XedFileBrowserView *view) { /* Store the refresh state, so we can handle unloading of nodes while refreshing properly */ view->priv->is_refresh = FALSE; } static void on_unload (XedFileBrowserStore *model, GFile *location, XedFileBrowserView *view) { /* Don't remove the expand state if we are refreshing */ if (!view->priv->restore_expand_state || view->priv->is_refresh) { return; } remove_expand_state (view, location); } static void restore_expand_state (XedFileBrowserView *view, XedFileBrowserStore *model, GtkTreeIter *iter) { GFile *location; GtkTreePath *path; gtk_tree_model_get (GTK_TREE_MODEL (model), iter, XED_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, -1); if (!location) { return; } path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); if (g_hash_table_lookup (view->priv->expand_state, location)) { gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, FALSE); } gtk_tree_path_free (path); } static void on_row_inserted (XedFileBrowserStore *model, GtkTreePath *path, GtkTreeIter *iter, XedFileBrowserView *view) { GtkTreeIter parent; GtkTreePath * copy; if (gtk_tree_model_iter_has_child (GTK_TREE_MODEL (model), iter)) { restore_expand_state (view, model, iter); } copy = gtk_tree_path_copy (path); if (gtk_tree_path_up (copy) && (gtk_tree_path_get_depth (copy) != 0) && gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &parent, copy)) { restore_expand_state (view, model, &parent); } gtk_tree_path_free (copy); } void _xed_file_browser_view_register_type (GTypeModule *type_module) { xed_file_browser_view_register_type (type_module); } // ex:ts=8:noet: