Index: ChangeLog =================================================================== --- ChangeLog (revision 20692) +++ ChangeLog (working copy) @@ -1,5 +1,18 @@ 2006-03-31 Benedikt Meurer + * exo/exo-icon-view.{c,h}, exo/exo-tree-view.{c,h}, exo/exo.symbols: + Add "single-click-timeout" properties to ExoIconView and ExoTreeView, + which control the delay after which the item under the mouse pointer + is automatically selected. Bug #1509. + * docs/reference/: Update API documentation. + +2006-03-31 Benedikt Meurer + + * exo/exo-icon-view.c(exo_icon_view_row_inserted): No need to invali- + date the prelit_item here. + +2006-03-31 Benedikt Meurer + * exo/exo-tree-view.c(exo_tree_view_button_release_event): Use button release to easily alter the selection even if all visible rows are selected in possible preparation for a drag operation. Index: docs/reference/tmpl/exo-icon-view.sgml =================================================================== --- docs/reference/tmpl/exo-icon-view.sgml (revision 20692) +++ docs/reference/tmpl/exo-icon-view.sgml (working copy) @@ -176,6 +176,11 @@ + + + + + @@ -459,6 +464,24 @@ @single_click: + + + + + +@icon_view: +@Returns: + + + + + + + +@icon_view: +@single_click_timeout: + + Index: docs/reference/tmpl/exo-tree-view.sgml =================================================================== --- docs/reference/tmpl/exo-tree-view.sgml (revision 20692) +++ docs/reference/tmpl/exo-tree-view.sgml (working copy) @@ -33,6 +33,11 @@ + + + + + @@ -59,3 +64,21 @@ @single_click: + + + + + +@tree_view: +@Returns: + + + + + + + +@tree_view: +@single_click_timeout: + + Index: docs/reference/exo-sections.txt =================================================================== --- docs/reference/exo-sections.txt (revision 20692) +++ docs/reference/exo-sections.txt (working copy) @@ -199,6 +199,8 @@ exo_icon_view_set_selection_mode exo_icon_view_get_single_click exo_icon_view_set_single_click +exo_icon_view_get_single_click_timeout +exo_icon_view_set_single_click_timeout exo_icon_view_widget_to_icon_coords exo_icon_view_icon_to_widget_coords exo_icon_view_get_path_at_pos @@ -256,6 +258,8 @@ exo_tree_view_new exo_tree_view_get_single_click exo_tree_view_set_single_click +exo_tree_view_get_single_click_timeout +exo_tree_view_set_single_click_timeout ExoTreeViewPrivate ExoTreeViewClass Index: exo/exo-icon-view.h =================================================================== --- exo/exo-icon-view.h (revision 20692) +++ exo/exo-icon-view.h (working copy) @@ -213,6 +213,10 @@ void exo_icon_view_set_single_click (ExoIconView *icon_view, gboolean single_click); +guint exo_icon_view_get_single_click_timeout (const ExoIconView *icon_view); +void exo_icon_view_set_single_click_timeout (ExoIconView *icon_view, + guint single_click_timeout); + void exo_icon_view_widget_to_icon_coords (const ExoIconView *icon_view, gint wx, gint wy, Index: exo/exo-tree-view.c =================================================================== --- exo/exo-tree-view.c (revision 20692) +++ exo/exo-tree-view.c (working copy) @@ -40,42 +40,51 @@ { PROP_0, PROP_SINGLE_CLICK, + PROP_SINGLE_CLICK_TIMEOUT, }; -static void exo_tree_view_class_init (ExoTreeViewClass *klass); -static void exo_tree_view_init (ExoTreeView *tree_view); -static void exo_tree_view_finalize (GObject *object); -static void exo_tree_view_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec); -static void exo_tree_view_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec); -static gboolean exo_tree_view_button_press_event (GtkWidget *widget, - GdkEventButton *event); -static gboolean exo_tree_view_button_release_event (GtkWidget *widget, - GdkEventButton *event); -static gboolean exo_tree_view_motion_notify_event (GtkWidget *widget, - GdkEventMotion *event); -static gboolean exo_tree_view_leave_notify_event (GtkWidget *widget, - GdkEventCrossing *event); -static void exo_tree_view_drag_begin (GtkWidget *widget, - GdkDragContext *context); +static void exo_tree_view_class_init (ExoTreeViewClass *klass); +static void exo_tree_view_init (ExoTreeView *tree_view); +static void exo_tree_view_finalize (GObject *object); +static void exo_tree_view_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void exo_tree_view_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static gboolean exo_tree_view_button_press_event (GtkWidget *widget, + GdkEventButton *event); +static gboolean exo_tree_view_button_release_event (GtkWidget *widget, + GdkEventButton *event); +static gboolean exo_tree_view_motion_notify_event (GtkWidget *widget, + GdkEventMotion *event); +static gboolean exo_tree_view_leave_notify_event (GtkWidget *widget, + GdkEventCrossing *event); +static void exo_tree_view_drag_begin (GtkWidget *widget, + GdkDragContext *context); +static gboolean exo_tree_view_move_cursor (GtkTreeView *view, + GtkMovementStep step, + gint count); +static gboolean exo_tree_view_single_click_timeout (gpointer user_data); +static void exo_tree_view_single_click_timeout_destroy (gpointer user_data); struct _ExoTreeViewPrivate { + /* whether the next button-release-event should emit "row-activate" */ + guint button_release_activates : 1; + /* single click mode */ guint single_click : 1; + guint single_click_timeout; + gint single_click_timeout_id; + guint single_click_timeout_state; - /* whether the next button-release-event should emit "row-activate" */ - guint button_release_activates : 1; - /* the path below the pointer or NULL */ GtkTreePath *hover_path; }; @@ -118,8 +127,9 @@ static void exo_tree_view_class_init (ExoTreeViewClass *klass) { - GtkWidgetClass *gtkwidget_class; - GObjectClass *gobject_class; + GtkTreeViewClass *gtktree_view_class; + GtkWidgetClass *gtkwidget_class; + GObjectClass *gobject_class; /* add our private data to the class */ g_type_class_add_private (klass, sizeof (ExoTreeViewPrivate)); @@ -139,11 +149,14 @@ gtkwidget_class->leave_notify_event = exo_tree_view_leave_notify_event; gtkwidget_class->drag_begin = exo_tree_view_drag_begin; + gtktree_view_class = GTK_TREE_VIEW_CLASS (klass); + gtktree_view_class->move_cursor = exo_tree_view_move_cursor; + /* initialize the library's i18n support */ _exo_i18n_init (); /** - * ExoIconView:single-click: + * ExoTreeView:single-click: * * %TRUE to activate items using a single click instead of a * double click. @@ -157,6 +170,23 @@ _("Whether the items in the view can be activated with single clicks"), FALSE, EXO_PARAM_READWRITE)); + + /** + * ExoTreeView:single-click-timeout: + * + * The amount of time in milliseconds after which the hover row (the row + * which is hovered by the mouse cursor) will be selected automatically + * in single-click mode. A value of %0 disables the automatic selection. + * + * Since: 0.3.1.5 + **/ + g_object_class_install_property (gobject_class, + PROP_SINGLE_CLICK_TIMEOUT, + g_param_spec_uint ("single-click-timeout", + _("Single Click Timeout"), + _("The amount of time after which the item under the mouse cursor will be selected automatically in single click mode"), + 0, G_MAXUINT, 0, + EXO_PARAM_READWRITE)); } @@ -166,6 +196,7 @@ { /* grab a pointer on the private data */ tree_view->priv = EXO_TREE_VIEW_GET_PRIVATE (tree_view); + tree_view->priv->single_click_timeout_id = -1; } @@ -175,6 +206,10 @@ { ExoTreeView *tree_view = EXO_TREE_VIEW (object); + /* be sure to cancel any single-click timeout */ + if (G_UNLIKELY (tree_view->priv->single_click_timeout_id >= 0)) + g_source_remove (tree_view->priv->single_click_timeout_id); + /* be sure to release the hover path */ if (G_UNLIKELY (tree_view->priv->hover_path == NULL)) gtk_tree_path_free (tree_view->priv->hover_path); @@ -198,6 +233,10 @@ g_value_set_boolean (value, exo_tree_view_get_single_click (tree_view)); break; + case PROP_SINGLE_CLICK_TIMEOUT: + g_value_set_uint (value, exo_tree_view_get_single_click_timeout (tree_view)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -220,6 +259,10 @@ exo_tree_view_set_single_click (tree_view, g_value_get_boolean (value)); break; + case PROP_SINGLE_CLICK_TIMEOUT: + exo_tree_view_set_single_click_timeout (tree_view, g_value_get_uint (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -245,6 +288,10 @@ /* grab the tree selection */ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)); + /* be sure to cancel any pending single-click timeout */ + if (G_UNLIKELY (tree_view->priv->single_click_timeout_id >= 0)) + g_source_remove (tree_view->priv->single_click_timeout_id); + /* check if the button press was on the internal tree view window */ if (G_LIKELY (event->window == gtk_tree_view_get_bin_window (GTK_TREE_VIEW (tree_view)))) { @@ -422,6 +469,22 @@ /* reset the cursor to its default */ gdk_window_set_cursor (event->window, NULL); } + + /* check if autoselection is enabled and the pointer is over a row */ + if (G_LIKELY (tree_view->priv->single_click_timeout > 0 && tree_view->priv->hover_path != NULL)) + { + /* cancel any previous single-click timeout */ + if (G_LIKELY (tree_view->priv->single_click_timeout_id >= 0)) + g_source_remove (tree_view->priv->single_click_timeout_id); + + /* remember the current event state */ + tree_view->priv->single_click_timeout_state = event->state; + + /* schedule a new single-click timeout */ + tree_view->priv->single_click_timeout_id = g_timeout_add_full (G_PRIORITY_LOW, tree_view->priv->single_click_timeout, + exo_tree_view_single_click_timeout, tree_view, + exo_tree_view_single_click_timeout_destroy); + } } else { @@ -443,6 +506,10 @@ { ExoTreeView *tree_view = EXO_TREE_VIEW (widget); + /* be sure to cancel any pending single-click timeout */ + if (G_UNLIKELY (tree_view->priv->single_click_timeout_id >= 0)) + g_source_remove (tree_view->priv->single_click_timeout_id); + /* release and reset the hover path (if any) */ if (tree_view->priv->hover_path != NULL) { @@ -478,6 +545,154 @@ +static gboolean +exo_tree_view_move_cursor (GtkTreeView *view, + GtkMovementStep step, + gint count) +{ + ExoTreeView *tree_view = EXO_TREE_VIEW (view); + + /* be sure to cancel any pending single-click timeout */ + if (G_UNLIKELY (tree_view->priv->single_click_timeout_id >= 0)) + g_source_remove (tree_view->priv->single_click_timeout_id); + + /* release and reset the hover path (if any) */ + if (tree_view->priv->hover_path != NULL) + { + gtk_tree_path_free (tree_view->priv->hover_path); + tree_view->priv->hover_path = NULL; + } + + /* reset the cursor for the tree view internal window */ + if (GTK_WIDGET_REALIZED (tree_view)) + gdk_window_set_cursor (gtk_tree_view_get_bin_window (GTK_TREE_VIEW (tree_view)), NULL); + + /* call the parent's handler */ + return (*GTK_TREE_VIEW_CLASS (exo_tree_view_parent_class)->move_cursor) (view, step, count); +} + + + +static gboolean +exo_tree_view_single_click_timeout (gpointer user_data) +{ + GtkTreeViewColumn *cursor_column; + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreePath *cursor_path; + GtkTreeIter iter; + ExoTreeView *tree_view = EXO_TREE_VIEW (user_data); + gboolean hover_path_selected; + GList *rows; + GList *lp; + + GDK_THREADS_ENTER (); + + /* verify that we are in single-click mode, have focus and a hover path */ + if (GTK_WIDGET_HAS_FOCUS (tree_view) && tree_view->priv->single_click && tree_view->priv->hover_path != NULL) + { + /* transform the hover_path to a tree iterator */ + model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view)); + if (model != NULL && gtk_tree_model_get_iter (model, &iter, tree_view->priv->hover_path)) + { + /* determine the current cursor path/column */ + gtk_tree_view_get_cursor (GTK_TREE_VIEW (tree_view), &cursor_path, &cursor_column); + + /* be sure the row is fully visible */ + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree_view), tree_view->priv->hover_path, cursor_column, FALSE, 0.0f, 0.0f); + + /* determine the selection and change it appropriately */ + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)); + if (gtk_tree_selection_get_mode (selection) == GTK_SELECTION_NONE) + { + /* just place the cursor on the row */ + gtk_tree_view_set_cursor (GTK_TREE_VIEW (tree_view), tree_view->priv->hover_path, cursor_column, FALSE); + } + else if ((tree_view->priv->single_click_timeout_state & GDK_SHIFT_MASK) != 0 + && gtk_tree_selection_get_mode (selection) == GTK_SELECTION_MULTIPLE) + { + /* check if the item is not already selected (otherwise do nothing) */ + if (!gtk_tree_selection_path_is_selected (selection, tree_view->priv->hover_path)) + { + /* unselect all previously selected items */ + gtk_tree_selection_unselect_all (selection); + + /* since we cannot access the anchor of a GtkTreeView, we + * use the cursor instead which is usually the same row. + */ + if (G_UNLIKELY (cursor_path == NULL)) + { + /* place the cursor on the new row */ + gtk_tree_view_set_cursor (GTK_TREE_VIEW (tree_view), tree_view->priv->hover_path, cursor_column, FALSE); + } + else + { + /* select all between the cursor and the current row */ + gtk_tree_selection_select_range (selection, tree_view->priv->hover_path, cursor_path); + } + } + } + else + { + /* remember the previously selected rows as set_cursor() clears the selection */ + rows = gtk_tree_selection_get_selected_rows (selection, NULL); + + /* check if the hover path is selected (as it will be selected after the set_cursor() call) */ + hover_path_selected = gtk_tree_selection_path_is_selected (selection, tree_view->priv->hover_path); + + /* place the cursor on the hover row */ + gtk_tree_view_set_cursor (GTK_TREE_VIEW (tree_view), tree_view->priv->hover_path, cursor_column, FALSE); + + /* restore the previous selection */ + for (lp = rows; lp != NULL; lp = lp->next) + { + gtk_tree_selection_select_path (selection, lp->data); + gtk_tree_path_free (lp->data); + } + g_list_free (rows); + + /* check what to do */ + if ((gtk_tree_selection_get_mode (selection) == GTK_SELECTION_MULTIPLE || + (gtk_tree_selection_get_mode (selection) == GTK_SELECTION_SINGLE && hover_path_selected)) + && (tree_view->priv->single_click_timeout_state & GDK_CONTROL_MASK) != 0) + { + /* toggle the selection state of the row */ + if (G_UNLIKELY (hover_path_selected)) + gtk_tree_selection_unselect_path (selection, tree_view->priv->hover_path); + else + gtk_tree_selection_select_path (selection, tree_view->priv->hover_path); + } + else if (G_UNLIKELY (!hover_path_selected)) + { + /* unselect all other rows */ + gtk_tree_selection_unselect_all (selection); + + /* select only the hover row */ + gtk_tree_selection_select_path (selection, tree_view->priv->hover_path); + } + } + + /* cleanup */ + if (G_LIKELY (cursor_path != NULL)) + gtk_tree_path_free (cursor_path); + } + } + + GDK_THREADS_LEAVE (); + + return FALSE; +} + + + +static void +exo_tree_view_single_click_timeout_destroy (gpointer user_data) +{ + EXO_TREE_VIEW (user_data)->priv->single_click_timeout_id = -1; +} + + + /** * exo_tree_view_new: * @@ -539,5 +754,67 @@ +/** + * exo_tree_view_get_single_click_timeout: + * @tree_view : a #ExoTreeView. + * + * Returns the amount of time in milliseconds after which the + * item under the mouse cursor will be selected automatically + * in single click mode. A value of %0 means that the behavior + * is disabled and the user must alter the selection manually. + * + * Return value: the single click autoselect timeout or %0 if + * the behavior is disabled. + * + * Since: 0.3.1.5 + **/ +guint +exo_tree_view_get_single_click_timeout (const ExoTreeView *tree_view) +{ + g_return_val_if_fail (EXO_IS_TREE_VIEW (tree_view), 0u); + return tree_view->priv->single_click_timeout; +} + + + +/** + * exo_tree_view_set_single_click_timeout: + * @tree_view : a #ExoTreeView. + * @single_click_timeout : the new timeout or %0 to disable. + * + * If @single_click_timeout is a value greater than zero, it specifies + * the amount of time in milliseconds after which the item under the + * mouse cursor will be selected automatically in single click mode. + * A value of %0 for @single_click_timeout disables the autoselection + * for @tree_view. + * + * This setting does not have any effect unless the @tree_view is in + * single-click mode, see exo_tree_view_set_single_click(). + * + * Since: 0.3.1.5 + **/ +void +exo_tree_view_set_single_click_timeout (ExoTreeView *tree_view, + guint single_click_timeout) +{ + g_return_if_fail (EXO_IS_TREE_VIEW (tree_view)); + + /* check if we have a new setting */ + if (tree_view->priv->single_click_timeout != single_click_timeout) + { + /* apply the new setting */ + tree_view->priv->single_click_timeout = single_click_timeout; + + /* be sure to cancel any pending single click timeout */ + if (G_UNLIKELY (tree_view->priv->single_click_timeout_id >= 0)) + g_source_remove (tree_view->priv->single_click_timeout_id); + + /* notify listeners */ + g_object_notify (G_OBJECT (tree_view), "single-click-timeout"); + } +} + + + #define __EXO_TREE_VIEW_C__ #include Index: exo/exo-tree-view.h =================================================================== --- exo/exo-tree-view.h (revision 20692) +++ exo/exo-tree-view.h (working copy) @@ -65,14 +65,18 @@ ExoTreeViewPrivate *priv; }; -GType exo_tree_view_get_type (void) G_GNUC_CONST; +GType exo_tree_view_get_type (void) G_GNUC_CONST; -GtkWidget *exo_tree_view_new (void) G_GNUC_MALLOC; +GtkWidget *exo_tree_view_new (void) G_GNUC_MALLOC; -gboolean exo_tree_view_get_single_click (const ExoTreeView *tree_view); -void exo_tree_view_set_single_click (ExoTreeView *tree_view, - gboolean single_click); +gboolean exo_tree_view_get_single_click (const ExoTreeView *tree_view); +void exo_tree_view_set_single_click (ExoTreeView *tree_view, + gboolean single_click); +guint exo_tree_view_get_single_click_timeout (const ExoTreeView *tree_view); +void exo_tree_view_set_single_click_timeout (ExoTreeView *tree_view, + guint single_click_timeout); + G_END_DECLS; #endif /* !__EXO_TREE_VIEW_H__ */ Index: exo/exo.symbols =================================================================== --- exo/exo.symbols (revision 20692) +++ exo/exo.symbols (working copy) @@ -1,6 +1,6 @@ /* $Id$ */ /*- - * Copyright (c) 2005 Benedikt Meurer . + * Copyright (c) 2005-2006 Benedikt Meurer . * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -176,6 +176,8 @@ exo_icon_view_set_selection_mode exo_icon_view_get_single_click exo_icon_view_set_single_click +exo_icon_view_get_single_click_timeout +exo_icon_view_set_single_click_timeout exo_icon_view_widget_to_icon_coords exo_icon_view_icon_to_widget_coords exo_icon_view_get_path_at_pos @@ -335,6 +337,8 @@ exo_tree_view_new G_GNUC_MALLOC exo_tree_view_get_single_click exo_tree_view_set_single_click +exo_tree_view_get_single_click_timeout +exo_tree_view_set_single_click_timeout #endif #endif Index: exo/exo-icon-view.c =================================================================== --- exo/exo-icon-view.c (revision 20692) +++ exo/exo-icon-view.c (working copy) @@ -71,6 +71,7 @@ PROP_MARGIN, PROP_REORDERABLE, PROP_SINGLE_CLICK, + PROP_SINGLE_CLICK_TIMEOUT, PROP_ENABLE_SEARCH, PROP_SEARCH_COLUMN, }; @@ -314,11 +315,15 @@ GtkSelectionData *selection_data, guint info, guint time); -static gboolean exo_icon_view_maybe_begin_drag (ExoIconView *icon_view, - GdkEventMotion *event); +static gboolean exo_icon_view_maybe_begin_drag (ExoIconView *icon_view, + GdkEventMotion *event); static void remove_scroll_timeout (ExoIconView *icon_view); +/* single-click autoselection support */ +static gboolean exo_icon_view_single_click_timeout (gpointer user_data); +static void exo_icon_view_single_click_timeout_destroy (gpointer user_data); + /* Interactive search support */ static void exo_icon_view_search_activate (GtkEntry *entry, ExoIconView *icon_view); @@ -493,12 +498,21 @@ guint source_set : 1; guint dest_set : 1; guint reorderable : 1; - guint single_click : 1; guint empty_view_drop :1; guint ctrl_pressed : 1; guint shift_pressed : 1; + /* Single-click support + * The single_click_timeout is the timeout after which the + * prelited item will be automatically selected in single + * click mode (0 to disable). + */ + guint single_click : 1; + guint single_click_timeout; + gint single_click_timeout_id; + guint single_click_timeout_state; + /* Interactive search support */ guint enable_search : 1; guint search_imcontext_changed : 1; @@ -843,6 +857,23 @@ EXO_PARAM_READWRITE)); /** + * ExoIconView:single-click-timeout: + * + * The amount of time in milliseconds after which a prelited item (an item + * which is hovered by the mouse cursor) will be selected automatically in + * single click mode. A value of %0 disables the automatic selection. + * + * Since: 0.3.1.5 + **/ + g_object_class_install_property (gobject_class, + PROP_SINGLE_CLICK_TIMEOUT, + g_param_spec_uint ("single-click-timeout", + _("Single Click Timeout"), + _("The amount of time after which the item under the mouse cursor will be selected automatically in single click mode"), + 0, G_MAXUINT, 0, + EXO_PARAM_READWRITE)); + + /** * ExoIconView:spacing: * * The spacing property specifies the space which is inserted between @@ -1121,6 +1152,8 @@ icon_view->priv->layout_idle_id = -1; + icon_view->priv->single_click_timeout_id = -1; + icon_view->priv->enable_search = TRUE; icon_view->priv->search_column = -1; icon_view->priv->search_timeout_id = -1; @@ -1189,6 +1222,10 @@ /* drop the items chunk */ g_mem_chunk_destroy (icon_view->priv->items_chunk); + /* be sure to cancel the single click timeout */ + if (G_UNLIKELY (icon_view->priv->single_click_timeout_id >= 0)) + g_source_remove (icon_view->priv->single_click_timeout_id); + /* kill the layout idle source (it's important to have this last!) */ if (G_UNLIKELY (icon_view->priv->layout_idle_id >= 0)) g_source_remove (icon_view->priv->layout_idle_id); @@ -1263,6 +1300,10 @@ g_value_set_boolean (value, icon_view->priv->single_click); break; + case PROP_SINGLE_CLICK_TIMEOUT: + g_value_set_uint (value, icon_view->priv->single_click_timeout); + break; + case PROP_SPACING: g_value_set_int (value, icon_view->priv->spacing); break; @@ -1345,6 +1386,10 @@ exo_icon_view_set_single_click (icon_view, g_value_get_boolean (value)); break; + case PROP_SINGLE_CLICK_TIMEOUT: + exo_icon_view_set_single_click_timeout (icon_view, g_value_get_uint (value)); + break; + case PROP_SPACING: exo_icon_view_set_spacing (icon_view, g_value_get_int (value)); break; @@ -1795,6 +1840,22 @@ /* reset the cursor */ gdk_window_set_cursor (event->window, NULL); } + + /* check if autoselection is enabled */ + if (G_LIKELY (icon_view->priv->single_click_timeout > 0)) + { + /* drop any running timeout */ + if (G_LIKELY (icon_view->priv->single_click_timeout_id >= 0)) + g_source_remove (icon_view->priv->single_click_timeout_id); + + /* remember the current event state */ + icon_view->priv->single_click_timeout_state = event->state; + + /* schedule a new timeout */ + icon_view->priv->single_click_timeout_id = g_timeout_add_full (G_PRIORITY_LOW, icon_view->priv->single_click_timeout, + exo_icon_view_single_click_timeout, icon_view, + exo_icon_view_single_click_timeout_destroy); + } } } } @@ -2047,6 +2108,10 @@ if (event->window != icon_view->priv->bin_window) return FALSE; + /* stop any pending "single-click-timeout" */ + if (G_UNLIKELY (icon_view->priv->single_click_timeout_id >= 0)) + g_source_remove (icon_view->priv->single_click_timeout_id); + if (G_UNLIKELY (!GTK_WIDGET_HAS_FOCUS (widget))) gtk_widget_grab_focus (widget); @@ -2319,6 +2384,14 @@ { ExoIconView *icon_view = EXO_ICON_VIEW (widget); + /* be sure to cancel any single-click timeout */ + if (G_UNLIKELY (icon_view->priv->single_click_timeout_id >= 0)) + g_source_remove (icon_view->priv->single_click_timeout_id); + + /* reset the cursor if we're still realized */ + if (G_LIKELY (icon_view->priv->bin_window != NULL)) + gdk_window_set_cursor (icon_view->priv->bin_window, NULL); + /* destroy the interactive search dialog */ if (G_UNLIKELY (icon_view->priv->search_window != NULL)) exo_icon_view_search_dialog_hide (icon_view->priv->search_window, icon_view); @@ -3484,9 +3557,6 @@ item->area.height = -1; icon_view->priv->items = g_list_insert (icon_view->priv->items, item, index); - /* invalidate the prelited item */ - icon_view->priv->prelit_item = NULL; - /* recalculate the layout */ exo_icon_view_queue_layout (icon_view); } @@ -3522,6 +3592,10 @@ /* reset the prelit item */ icon_view->priv->prelit_item = NULL; + /* cancel any pending single click timer */ + if (G_UNLIKELY (icon_view->priv->single_click_timeout_id >= 0)) + g_source_remove (icon_view->priv->single_click_timeout_id); + /* in single click mode, we also reset the cursor when realized */ if (G_UNLIKELY (icon_view->priv->single_click && GTK_WIDGET_REALIZED (icon_view))) gdk_window_set_cursor (icon_view->priv->bin_window, NULL); @@ -4710,55 +4784,6 @@ /** - * exo_icon_view_get_single_click: - * @icon_view : a #ExoIconView. - * - * Returns %TRUE if @icon_view is currently in single click mode, - * else %FALSE will be returned. - * - * Return value: whether @icon_view is currently in single click mode. - * - * Since: 0.3.1.3 - **/ -gboolean -exo_icon_view_get_single_click (const ExoIconView *icon_view) -{ - g_return_val_if_fail (EXO_IS_ICON_VIEW (icon_view), FALSE); - return icon_view->priv->single_click; -} - - - -/** - * exo_icon_view_set_single_click: - * @icon_view : a #ExoIconView. - * @single_click : %TRUE for single click, %FALSE for double click mode. - * - * If @single_click is %TRUE, @icon_view will be in single click mode - * afterwards, else @icon_view will be in double click mode. - * - * Since: 0.3.1.3 - **/ -void -exo_icon_view_set_single_click (ExoIconView *icon_view, - gboolean single_click) -{ - g_return_if_fail (EXO_IS_ICON_VIEW (icon_view)); - - /* normalize the value */ - single_click = !!single_click; - - /* check if we have a new setting here */ - if (icon_view->priv->single_click != single_click) - { - icon_view->priv->single_click = single_click; - g_object_notify (G_OBJECT (icon_view), "single-click"); - } -} - - - -/** * exo_icon_view_get_model: * @icon_view : a #ExoIconView * @@ -4851,6 +4876,10 @@ icon_view->priv->width = 0; icon_view->priv->height = 0; + /* cancel any pending single click timer */ + if (G_UNLIKELY (icon_view->priv->single_click_timeout_id >= 0)) + g_source_remove (icon_view->priv->single_click_timeout_id); + /* reset cursor when in single click mode and realized */ if (G_UNLIKELY (icon_view->priv->single_click && GTK_WIDGET_REALIZED (icon_view))) gdk_window_set_cursor (icon_view->priv->bin_window, NULL); @@ -7181,7 +7210,214 @@ -/*----------------------------- +/*----------------------* + * Single-click support * + *----------------------*/ + +/** + * exo_icon_view_get_single_click: + * @icon_view : a #ExoIconView. + * + * Returns %TRUE if @icon_view is currently in single click mode, + * else %FALSE will be returned. + * + * Return value: whether @icon_view is currently in single click mode. + * + * Since: 0.3.1.3 + **/ +gboolean +exo_icon_view_get_single_click (const ExoIconView *icon_view) +{ + g_return_val_if_fail (EXO_IS_ICON_VIEW (icon_view), FALSE); + return icon_view->priv->single_click; +} + + + +/** + * exo_icon_view_set_single_click: + * @icon_view : a #ExoIconView. + * @single_click : %TRUE for single click, %FALSE for double click mode. + * + * If @single_click is %TRUE, @icon_view will be in single click mode + * afterwards, else @icon_view will be in double click mode. + * + * Since: 0.3.1.3 + **/ +void +exo_icon_view_set_single_click (ExoIconView *icon_view, + gboolean single_click) +{ + g_return_if_fail (EXO_IS_ICON_VIEW (icon_view)); + + /* normalize the value */ + single_click = !!single_click; + + /* check if we have a new setting here */ + if (icon_view->priv->single_click != single_click) + { + icon_view->priv->single_click = single_click; + g_object_notify (G_OBJECT (icon_view), "single-click"); + } +} + + + +/** + * exo_icon_view_get_single_click_timeout: + * @icon_view : a #ExoIconView. + * + * Returns the amount of time in milliseconds after which the + * item under the mouse cursor will be selected automatically + * in single click mode. A value of %0 means that the behavior + * is disabled and the user must alter the selection manually. + * + * Return value: the single click autoselect timeout or %0 if + * the behavior is disabled. + * + * Since: 0.3.1.5 + **/ +guint +exo_icon_view_get_single_click_timeout (const ExoIconView *icon_view) +{ + g_return_val_if_fail (EXO_IS_ICON_VIEW (icon_view), 0u); + return icon_view->priv->single_click_timeout; +} + + + +/** + * exo_icon_view_set_single_click_timeout: + * @icon_view : a #ExoIconView. + * @single_click_timeout : the new timeout or %0 to disable. + * + * If @single_click_timeout is a value greater than zero, it specifies + * the amount of time in milliseconds after which the item under the + * mouse cursor will be selected automatically in single click mode. + * A value of %0 for @single_click_timeout disables the autoselection + * for @icon_view. + * + * This setting does not have any effect unless the @icon_view is in + * single-click mode, see exo_icon_view_set_single_click(). + * + * Since: 0.3.1.5 + **/ +void +exo_icon_view_set_single_click_timeout (ExoIconView *icon_view, + guint single_click_timeout) +{ + g_return_if_fail (EXO_IS_ICON_VIEW (icon_view)); + + /* check if we have a new setting */ + if (icon_view->priv->single_click_timeout != single_click_timeout) + { + /* apply the new setting */ + icon_view->priv->single_click_timeout = single_click_timeout; + + /* be sure to cancel any pending single click timeout */ + if (G_UNLIKELY (icon_view->priv->single_click_timeout_id >= 0)) + g_source_remove (icon_view->priv->single_click_timeout_id); + + /* notify listeners */ + g_object_notify (G_OBJECT (icon_view), "single-click-timeout"); + } +} + + + +static gboolean +exo_icon_view_single_click_timeout (gpointer user_data) +{ + ExoIconViewItem *item; + gboolean dirty = FALSE; + ExoIconView *icon_view = EXO_ICON_VIEW (user_data); + + GDK_THREADS_ENTER (); + + /* verify that we are in single-click mode, have focus and a prelit item */ + if (GTK_WIDGET_HAS_FOCUS (icon_view) && icon_view->priv->single_click && icon_view->priv->prelit_item != NULL) + { + /* work on the prelit item */ + item = icon_view->priv->prelit_item; + + /* be sure the item is fully visible */ + exo_icon_view_scroll_to_item (icon_view, item); + + /* change the selection appropriately */ + if (G_UNLIKELY (icon_view->priv->selection_mode == GTK_SELECTION_NONE)) + { + exo_icon_view_set_cursor_item (icon_view, item, -1); + } + else if ((icon_view->priv->single_click_timeout_state & GDK_SHIFT_MASK) != 0 + && icon_view->priv->selection_mode == GTK_SELECTION_MULTIPLE) + { + /* check if the item is not already selected (otherwise do nothing) */ + if (G_LIKELY (!item->selected)) + { + /* unselect all previously selected items */ + exo_icon_view_unselect_all_internal (icon_view); + + /* select all items between the anchor and the prelit item */ + exo_icon_view_set_cursor_item (icon_view, item, -1); + if (icon_view->priv->anchor_item == NULL) + icon_view->priv->anchor_item = item; + else + exo_icon_view_select_all_between (icon_view, icon_view->priv->anchor_item, item); + + /* selection was changed */ + dirty = TRUE; + } + } + else + { + if ((icon_view->priv->selection_mode == GTK_SELECTION_MULTIPLE || + ((icon_view->priv->selection_mode == GTK_SELECTION_SINGLE) && item->selected)) && + (icon_view->priv->single_click_timeout_state & GDK_CONTROL_MASK) != 0) + { + item->selected = !item->selected; + exo_icon_view_queue_draw_item (icon_view, item); + dirty = TRUE; + } + else if (!item->selected) + { + exo_icon_view_unselect_all_internal (icon_view); + exo_icon_view_queue_draw_item (icon_view, item); + item->selected = TRUE; + dirty = TRUE; + } + exo_icon_view_set_cursor_item (icon_view, item, -1); + icon_view->priv->anchor_item = item; + } + } + + /* emit "selection-changed" and stop drawing keyboard + * focus indicator if the selection was altered + */ + if (G_LIKELY (dirty)) + { + /* reset "draw keyfocus" flag */ + EXO_ICON_VIEW_UNSET_FLAG (icon_view, EXO_ICON_VIEW_DRAW_KEYFOCUS); + + /* emit "selection-changed" */ + g_signal_emit (G_OBJECT (icon_view), icon_view_signals[SELECTION_CHANGED], 0); + } + + GDK_THREADS_LEAVE (); + + return FALSE; +} + + + +static void +exo_icon_view_single_click_timeout_destroy (gpointer user_data) +{ + EXO_ICON_VIEW (user_data)->priv->single_click_timeout_id = -1; +} + + + +/*----------------------------* * Interactive search support * *----------------------------*/