[libgd] gd-main-view: Add rubberband selection
- From: Alexander Larsson <alexl src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libgd] gd-main-view: Add rubberband selection
- Date: Mon, 15 Apr 2013 09:19:16 +0000 (UTC)
commit 700f73ad9a4e802dcc9eec0a7abc792d8665b013
Author: Alexander Larsson <alexl redhat com>
Date: Thu Apr 11 22:34:00 2013 +0200
gd-main-view: Add rubberband selection
We support a rubberband-like drag-selection when in selection
mode.
https://bugzilla.gnome.org/show_bug.cgi?id=697645
libgd/gd-main-icon-view.c | 162 +++++++++++++++++++++++++++++++++++++++++
libgd/gd-main-list-view.c | 58 +++++++++++++++
libgd/gd-main-view-generic.c | 78 ++++++++++++++++++++
libgd/gd-main-view-generic.h | 6 ++
libgd/gd-main-view.c | 166 ++++++++++++++++++++++++++++++++++++++-----
5 files changed, 454 insertions(+), 16 deletions(-)
---
diff --git a/libgd/gd-main-icon-view.c b/libgd/gd-main-icon-view.c
index 2789c27..d96be6c 100644
--- a/libgd/gd-main-icon-view.c
+++ b/libgd/gd-main-icon-view.c
@@ -128,6 +128,167 @@ gd_main_icon_view_constructed (GObject *obj)
}
static void
+path_from_line_rects (cairo_t *cr,
+ GdkRectangle *lines,
+ int n_lines)
+{
+ int start_line, end_line;
+ GdkRectangle *r;
+ int i;
+
+ /* Join rows vertically by extending to the middle */
+ for (i = 0; i < n_lines - 1; i++)
+ {
+ GdkRectangle *r1 = &lines[i];
+ GdkRectangle *r2 = &lines[i+1];
+ int gap = r2->y - (r1->y + r1->height);
+ int old_y;
+
+ r1->height += gap / 2;
+ old_y = r2->y;
+ r2->y = r1->y + r1->height;
+ r2->height += old_y - r2->y;
+ }
+
+ cairo_new_path (cr);
+ start_line = 0;
+
+ do
+ {
+ for (i = start_line; i < n_lines; i++)
+ {
+ r = &lines[i];
+ if (i == start_line)
+ cairo_move_to (cr, r->x + r->width, r->y);
+ else
+ cairo_line_to (cr, r->x + r->width, r->y);
+ cairo_line_to (cr, r->x + r->width, r->y + r->height);
+
+ if (i < n_lines - 1 &&
+ (r->x + r->width < lines[i+1].x ||
+ r->x > lines[i+1].x + lines[i+1].width))
+ {
+ i++;
+ break;
+ }
+ }
+ end_line = i;
+ for (i = end_line - 1; i >= start_line; i--)
+ {
+ r = &lines[i];
+ cairo_line_to (cr, r->x, r->y + r->height);
+ cairo_line_to (cr, r->x, r->y);
+ }
+ cairo_close_path (cr);
+ start_line = end_line;
+ }
+ while (end_line < n_lines);
+}
+
+static gboolean
+gd_main_icon_view_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ GdMainIconView *self = GD_MAIN_ICON_VIEW (widget);
+ GtkAllocation allocation;
+ GtkStyleContext *context;
+ GdkRectangle line_rect;
+ GdkRectangle rect;
+ GtkTreePath *path;
+ GArray *lines;
+ GtkTreePath *rubberband_start, *rubberband_end;
+
+ GTK_WIDGET_CLASS (gd_main_icon_view_parent_class)->draw (widget, cr);
+
+ _gd_main_view_generic_get_rubberband_range (GD_MAIN_VIEW_GENERIC (self),
+ &rubberband_start, &rubberband_end);
+
+ if (rubberband_start)
+ {
+ cairo_save (cr);
+
+ context = gtk_widget_get_style_context (widget);
+
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_RUBBERBAND);
+
+ path = gtk_tree_path_copy (rubberband_start);
+
+ line_rect.width = 0;
+ lines = g_array_new (FALSE, FALSE, sizeof (GdkRectangle));
+
+ while (gtk_tree_path_compare (path, rubberband_end) <= 0)
+ {
+ if (gtk_icon_view_get_cell_rect (GTK_ICON_VIEW (widget),
+ path,
+ NULL, &rect))
+ {
+ if (line_rect.width == 0)
+ line_rect = rect;
+ else
+ {
+ if (rect.y == line_rect.y)
+ gdk_rectangle_union (&rect, &line_rect, &line_rect);
+ else
+ {
+ g_array_append_val (lines, line_rect);
+ line_rect = rect;
+ }
+ }
+ }
+ gtk_tree_path_next (path);
+ }
+
+ if (line_rect.width != 0)
+ g_array_append_val (lines, line_rect);
+
+ if (lines->len > 0)
+ {
+ GtkStateFlags state;
+ cairo_path_t *path;
+ GtkBorder border;
+ GdkRGBA border_color;
+
+ path_from_line_rects (cr, (GdkRectangle *)lines->data, lines->len);
+
+ /* For some reason we need to copy and reapply the path, or it gets
+ eaten by gtk_render_background() */
+ path = cairo_copy_path (cr);
+
+ cairo_save (cr);
+ cairo_clip (cr);
+ gtk_widget_get_allocation (widget, &allocation);
+ gtk_render_background (context, cr,
+ 0, 0,
+ allocation.width, allocation.height);
+ cairo_restore (cr);
+
+ cairo_append_path (cr, path);
+ cairo_path_destroy (path);
+
+ state = gtk_widget_get_state_flags (widget);
+ gtk_style_context_get_border_color (context,
+ state,
+ &border_color);
+ gtk_style_context_get_border (context, state,
+ &border);
+
+ cairo_set_line_width (cr, border.left);
+ gdk_cairo_set_source_rgba (cr, &border_color);
+ cairo_stroke (cr);
+ }
+ g_array_free (lines, TRUE);
+
+ gtk_tree_path_free (path);
+
+ gtk_style_context_restore (context);
+ cairo_restore (cr);
+ }
+
+ return FALSE;
+}
+
+static void
gd_main_icon_view_class_init (GdMainIconViewClass *klass)
{
GObjectClass *oclass = G_OBJECT_CLASS (klass);
@@ -135,6 +296,7 @@ gd_main_icon_view_class_init (GdMainIconViewClass *klass)
oclass->constructed = gd_main_icon_view_constructed;
wclass->drag_data_get = gd_main_icon_view_drag_data_get;
+ wclass->draw = gd_main_icon_view_draw;
gtk_widget_class_install_style_property (wclass,
g_param_spec_int ("check-icon-size",
diff --git a/libgd/gd-main-list-view.c b/libgd/gd-main-list-view.c
index 7a136b5..a1be3e6 100644
--- a/libgd/gd-main-list-view.c
+++ b/libgd/gd-main-list-view.c
@@ -37,6 +37,9 @@ G_DEFINE_TYPE_WITH_CODE (GdMainListView, gd_main_list_view, GTK_TYPE_TREE_VIEW,
G_IMPLEMENT_INTERFACE (GD_TYPE_MAIN_VIEW_GENERIC,
gd_main_view_generic_iface_init))
+static gboolean gd_main_list_view_draw (GtkWidget *widget,
+ cairo_t *cr);
+
static GtkTreePath*
get_source_row (GdkDragContext *context)
{
@@ -144,6 +147,7 @@ gd_main_list_view_class_init (GdMainListViewClass *klass)
oclass->constructed = gd_main_list_view_constructed;
wclass->drag_data_get = gd_main_list_view_drag_data_get;
+ wclass->draw = gd_main_list_view_draw;
g_type_class_add_private (klass, sizeof (GdMainListViewPrivate));
}
@@ -181,6 +185,60 @@ gd_main_list_view_set_selection_mode (GdMainViewGeneric *mv,
gtk_tree_view_column_queue_resize (self->priv->tree_col);
}
+static gboolean
+gd_main_list_view_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ GdMainListView *self = GD_MAIN_LIST_VIEW (widget);
+ GtkStyleContext *context;
+ GdkRectangle lines_rect;
+ GdkRectangle rect;
+ GtkTreePath *path;
+ GtkTreePath *rubberband_start, *rubberband_end;
+
+ GTK_WIDGET_CLASS (gd_main_list_view_parent_class)->draw (widget, cr);
+
+ _gd_main_view_generic_get_rubberband_range (GD_MAIN_VIEW_GENERIC (self),
+ &rubberband_start, &rubberband_end);
+
+ if (rubberband_start)
+ {
+ context = gtk_widget_get_style_context (widget);
+
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_RUBBERBAND);
+
+ path = gtk_tree_path_copy (rubberband_start);
+
+ lines_rect.width = 0;
+
+ while (gtk_tree_path_compare (path, rubberband_end) <= 0)
+ {
+ gtk_tree_view_get_cell_area (GTK_TREE_VIEW (self),
+ path, self->priv->tree_col, &rect);
+ if (lines_rect.width == 0)
+ lines_rect = rect;
+ else
+ gdk_rectangle_union (&rect, &lines_rect, &lines_rect);
+
+ gtk_tree_path_next (path);
+ }
+ gtk_tree_path_free (path);
+
+ gtk_render_background (context, cr,
+ lines_rect.x, lines_rect.y,
+ lines_rect.width, lines_rect.height);
+ gtk_render_frame (context, cr,
+ lines_rect.x, lines_rect.y,
+ lines_rect.width, lines_rect.height);
+
+
+ gtk_style_context_restore (context);
+ }
+
+ return FALSE;
+}
+
static void
gd_main_list_view_scroll_to_path (GdMainViewGeneric *mv,
GtkTreePath *path)
diff --git a/libgd/gd-main-view-generic.c b/libgd/gd-main-view-generic.c
index b396ec5..200153c 100644
--- a/libgd/gd-main-view-generic.c
+++ b/libgd/gd-main-view-generic.c
@@ -82,6 +82,84 @@ gd_main_view_generic_set_selection_mode (GdMainViewGeneric *self,
(* iface->set_selection_mode) (self, selection_mode);
}
+
+typedef struct {
+ GtkTreePath *rubberband_start;
+ GtkTreePath *rubberband_end;
+} RubberbandInfo;
+
+static void
+rubber_band_info_destroy (RubberbandInfo *info)
+{
+ g_clear_pointer (&info->rubberband_start,
+ gtk_tree_path_free);
+ g_clear_pointer (&info->rubberband_end,
+ gtk_tree_path_free);
+ g_slice_free (RubberbandInfo, info);
+}
+
+static RubberbandInfo*
+get_rubber_band_info (GdMainViewGeneric *self)
+{
+ RubberbandInfo *info;
+
+ info = g_object_get_data (G_OBJECT (self), "gd-main-view-generic-rubber-band");
+ if (info == NULL)
+ {
+ info = g_slice_new0 (RubberbandInfo);
+ g_object_set_data_full (G_OBJECT (self), "gd-main-view-generic-rubber-band",
+ info, (GDestroyNotify)rubber_band_info_destroy);
+ }
+
+ return info;
+}
+
+void
+gd_main_view_generic_set_rubberband_range (GdMainViewGeneric *self,
+ GtkTreePath *start,
+ GtkTreePath *end)
+{
+ RubberbandInfo *info;
+
+ info = get_rubber_band_info (self);
+
+ if (start == NULL || end == NULL)
+ {
+ g_clear_pointer (&info->rubberband_start,
+ gtk_tree_path_free);
+ g_clear_pointer (&info->rubberband_end,
+ gtk_tree_path_free);
+ }
+ else
+ {
+ if (gtk_tree_path_compare (start, end) < 0)
+ {
+ info->rubberband_start = gtk_tree_path_copy (start);
+ info->rubberband_end = gtk_tree_path_copy (end);
+ }
+ else
+ {
+ info->rubberband_start = gtk_tree_path_copy (end);
+ info->rubberband_end = gtk_tree_path_copy (start);
+ }
+ }
+
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+void
+_gd_main_view_generic_get_rubberband_range (GdMainViewGeneric *self,
+ GtkTreePath **start,
+ GtkTreePath **end)
+{
+ RubberbandInfo *info;
+
+ info = get_rubber_band_info (self);
+
+ *start = info->rubberband_start;
+ *end = info->rubberband_end;
+}
+
void
gd_main_view_generic_scroll_to_path (GdMainViewGeneric *self,
GtkTreePath *path)
diff --git a/libgd/gd-main-view-generic.h b/libgd/gd-main-view-generic.h
index ffaaa6d..4df84ff 100644
--- a/libgd/gd-main-view-generic.h
+++ b/libgd/gd-main-view-generic.h
@@ -98,12 +98,18 @@ GtkTreePath * gd_main_view_generic_get_path_at_pos (GdMainViewGeneric *self,
gint y);
void gd_main_view_generic_select_all (GdMainViewGeneric *self);
void gd_main_view_generic_unselect_all (GdMainViewGeneric *self);
+void gd_main_view_generic_set_rubberband_range (GdMainViewGeneric *self,
+ GtkTreePath *start,
+ GtkTreePath *end);
/* private */
void _gd_main_view_generic_dnd_common (GtkTreeModel *model,
gboolean selection_mode,
GtkTreePath *path,
GtkSelectionData *data);
+void _gd_main_view_generic_get_rubberband_range (GdMainViewGeneric *self,
+ GtkTreePath **start,
+ GtkTreePath **end);
G_END_DECLS
diff --git a/libgd/gd-main-view.c b/libgd/gd-main-view.c
index 8f3407a..6a9036b 100644
--- a/libgd/gd-main-view.c
+++ b/libgd/gd-main-view.c
@@ -27,6 +27,7 @@
#define MAIN_VIEW_TYPE_INITIAL -1
#define MAIN_VIEW_DND_ICON_OFFSET 20
+#define MAIN_VIEW_RUBBERBAND_SELECT_TRIGGER_LENGTH 32
struct _GdMainViewPrivate {
GdMainViewType current_type;
@@ -35,6 +36,13 @@ struct _GdMainViewPrivate {
GtkWidget *current_view;
GtkTreeModel *model;
+ gboolean track_motion;
+ gboolean rubberband_select;
+ GtkTreePath *rubberband_select_first_path;
+ GtkTreePath *rubberband_select_last_path;
+ int button_down_x;
+ int button_down_y;
+
gchar *button_press_item_path;
gchar *last_selected_id;
@@ -77,6 +85,12 @@ gd_main_view_finalize (GObject *obj)
g_free (self->priv->button_press_item_path);
g_free (self->priv->last_selected_id);
+ if (self->priv->rubberband_select_first_path)
+ gtk_tree_path_free (self->priv->rubberband_select_first_path);
+
+ if (self->priv->rubberband_select_last_path)
+ gtk_tree_path_free (self->priv->rubberband_select_last_path);
+
G_OBJECT_CLASS (gd_main_view_parent_class)->finalize (obj);
}
@@ -528,10 +542,12 @@ on_button_release_event (GtkWidget *view,
{
GdMainView *self = user_data;
GdMainViewGeneric *generic = get_generic (self);
- GtkTreePath *path;
+ GtkTreePath *path, *start_path, *end_path, *tmp_path;
+ GtkTreeIter iter;
gchar *button_release_item_path;
gboolean selection_mode;
gboolean res, same_item = FALSE;
+ gboolean is_selected;
/* eat double/triple click events */
if (event->type != GDK_BUTTON_RELEASE)
@@ -551,6 +567,53 @@ on_button_release_event (GtkWidget *view,
g_free (self->priv->button_press_item_path);
self->priv->button_press_item_path = NULL;
+ self->priv->track_motion = FALSE;
+ if (self->priv->rubberband_select)
+ {
+ gd_main_view_generic_set_rubberband_range (get_generic (self), NULL, NULL);
+ if (self->priv->rubberband_select_last_path)
+ {
+ if (!self->priv->selection_mode)
+ g_signal_emit (self, signals[SELECTION_MODE_REQUEST], 0);
+
+ start_path = gtk_tree_path_copy (self->priv->rubberband_select_first_path);
+ end_path = gtk_tree_path_copy (self->priv->rubberband_select_last_path);
+ if (gtk_tree_path_compare (start_path, end_path) > 0)
+ {
+ tmp_path = start_path;
+ start_path = end_path;
+ end_path = tmp_path;
+ }
+
+ while (gtk_tree_path_compare (start_path, end_path) <= 0)
+ {
+ if (gtk_tree_model_get_iter (self->priv->model,
+ &iter, start_path))
+ {
+ gtk_tree_model_get (self->priv->model, &iter,
+ GD_MAIN_COLUMN_SELECTED, &is_selected,
+ -1);
+ gtk_list_store_set (GTK_LIST_STORE (self->priv->model), &iter,
+ GD_MAIN_COLUMN_SELECTED, !is_selected,
+ -1);
+ }
+
+ gtk_tree_path_next (start_path);
+ }
+
+ gtk_tree_path_free (start_path);
+ gtk_tree_path_free (end_path);
+ }
+
+ g_clear_pointer (&self->priv->rubberband_select_first_path,
+ gtk_tree_path_free);
+ g_clear_pointer (&self->priv->rubberband_select_last_path,
+ gtk_tree_path_free);
+
+ res = TRUE;
+ goto out;
+ }
+
if (!same_item)
{
res = FALSE;
@@ -590,40 +653,109 @@ on_button_press_event (GtkWidget *view,
GList *selection, *l;
GtkTreePath *sel_path;
gboolean found = FALSE;
+ gboolean force_selection;
path = gd_main_view_generic_get_path_at_pos (generic, event->x, event->y);
if (path != NULL)
self->priv->button_press_item_path = gtk_tree_path_to_string (path);
- if (!self->priv->selection_mode ||
- path == NULL)
+ force_selection =
+ (event->button == 3) ||
+ ((event->button == 1) && (event->state & GDK_CONTROL_MASK));
+
+ if (!self->priv->selection_mode && !force_selection)
{
gtk_tree_path_free (path);
return FALSE;
}
- selection = gd_main_view_get_selection (self);
-
- for (l = selection; l != NULL; l = l->next)
+ if (path && !force_selection)
{
- sel_path = l->data;
- if (gtk_tree_path_compare (path, sel_path) == 0)
- {
- found = TRUE;
- break;
- }
- }
+ selection = gd_main_view_get_selection (self);
- if (selection != NULL)
- g_list_free_full (selection, (GDestroyNotify) gtk_tree_path_free);
+ for (l = selection; l != NULL; l = l->next)
+ {
+ sel_path = l->data;
+ if (gtk_tree_path_compare (path, sel_path) == 0)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (selection != NULL)
+ g_list_free_full (selection, (GDestroyNotify) gtk_tree_path_free);
+ }
/* if we did not find the item in the selection, block
* drag and drop, while in selection mode
*/
- return !found;
+ if (!found)
+ {
+ self->priv->track_motion = TRUE;
+ self->priv->rubberband_select = FALSE;
+ self->priv->rubberband_select_first_path = NULL;
+ self->priv->rubberband_select_last_path = NULL;
+ self->priv->button_down_x = event->x;
+ self->priv->button_down_y = event->y;
+ return TRUE;
+ }
+ else
+ return FALSE;
}
+static gboolean
+on_motion_event (GtkWidget *widget,
+ GdkEventMotion *event,
+ gpointer user_data)
+{
+ GdMainView *self = user_data;
+ GtkTreePath *path;
+
+ if (self->priv->track_motion)
+ {
+ if (!self->priv->rubberband_select &&
+ (event->x - self->priv->button_down_x) * (event->x - self->priv->button_down_x) +
+ (event->y - self->priv->button_down_y) * (event->y - self->priv->button_down_y) >
+ MAIN_VIEW_RUBBERBAND_SELECT_TRIGGER_LENGTH * MAIN_VIEW_RUBBERBAND_SELECT_TRIGGER_LENGTH)
+ {
+ self->priv->rubberband_select = TRUE;
+ if (self->priv->button_press_item_path)
+ {
+ self->priv->rubberband_select_first_path =
+ gtk_tree_path_new_from_string (self->priv->button_press_item_path);
+ }
+ }
+
+ if (self->priv->rubberband_select)
+ {
+ path = gd_main_view_generic_get_path_at_pos (get_generic (self), event->x, event->y);
+ if (path != NULL)
+ {
+ if (self->priv->rubberband_select_first_path == NULL)
+ self->priv->rubberband_select_first_path = gtk_tree_path_copy (path);
+
+ if (self->priv->rubberband_select_last_path == NULL ||
+ gtk_tree_path_compare (self->priv->rubberband_select_last_path, path) != 0)
+ {
+ if (self->priv->rubberband_select_last_path)
+ gtk_tree_path_free (self->priv->rubberband_select_last_path);
+ self->priv->rubberband_select_last_path = path;
+
+ gd_main_view_generic_set_rubberband_range (get_generic (self),
+ self->priv->rubberband_select_first_path,
+ self->priv->rubberband_select_last_path);
+ }
+ else
+ gtk_tree_path_free (path);
+ }
+ }
+ }
+ return FALSE;
+}
+
+
static void
on_drag_begin (GdMainViewGeneric *generic,
GdkDragContext *drag_context,
@@ -773,6 +905,8 @@ gd_main_view_rebuild (GdMainView *self)
G_CALLBACK (on_button_press_event), self);
g_signal_connect (self->priv->current_view, "button-release-event",
G_CALLBACK (on_button_release_event), self);
+ g_signal_connect (self->priv->current_view, "motion-notify-event",
+ G_CALLBACK (on_motion_event), self);
g_signal_connect_after (self->priv->current_view, "drag-begin",
G_CALLBACK (on_drag_begin), self);
g_signal_connect (self->priv->current_view, "view-selection-changed",
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]