[egg-list-box/flow-box-enhancements] Add rubberband selection
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [egg-list-box/flow-box-enhancements] Add rubberband selection
- Date: Sun, 29 Sep 2013 07:37:36 +0000 (UTC)
commit 2ac23a7b22d1550f3f5d35659bf497ae618d9144
Author: Matthias Clasen <mclasen redhat com>
Date: Sun Sep 29 02:05:29 2013 -0400
Add rubberband selection
egg-flow-box.c | 285 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 278 insertions(+), 7 deletions(-)
---
diff --git a/egg-flow-box.c b/egg-flow-box.c
index 518647d..92590e2 100644
--- a/egg-flow-box.c
+++ b/egg-flow-box.c
@@ -123,6 +123,7 @@ _egg_marshal_VOID__ENUM_INT (GClosure * closure,
#define P_(msgid) (msgid)
#define DEFAULT_MAX_CHILDREN_PER_LINE 7
+#define RUBBERBAND_START_DISTANCE 32
enum {
CHILD_ACTIVATED,
@@ -190,6 +191,13 @@ struct _EggFlowBoxPrivate {
EggFlowBoxSortFunc sort_func;
gpointer sort_data;
GDestroyNotify sort_destroy;
+
+ gboolean track_motion;
+ gboolean rubberband_select;
+ EggFlowBoxChild *rubberband_first;
+ EggFlowBoxChild *rubberband_last;
+ gint button_down_x;
+ gint button_down_y;
};
typedef struct _EggFlowBoxChildPrivate EggFlowBoxChildPrivate;
@@ -1058,8 +1066,9 @@ fit_aligned_item_requests (EggFlowBox *box,
n_children,
sizes);
- /* Try columnizing the whole thing and adding an item to the end of the line;
- * try to fit as many columns into the available size as possible
+ /* Try columnizing the whole thing and adding an item to the end of
+ * the line; try to fit as many columns into the available size as
+ * possible
*/
for (try_length = *line_length + 1; try_line_size < avail_size; try_length++)
{
@@ -2607,6 +2616,7 @@ egg_flow_box_motion_notify_event (GtkWidget *widget,
GdkEventMotion *event)
{
EggFlowBox *box = EGG_FLOW_BOX (widget);
+ EggFlowBoxPrivate *priv = BOX_PRIV (box);
EggFlowBoxChild *child;
GdkWindow *window;
GdkWindow *event_window;
@@ -2634,6 +2644,25 @@ egg_flow_box_motion_notify_event (GtkWidget *widget,
egg_flow_box_update_prelight (box, child);
egg_flow_box_update_active (box, child);
+ if (priv->track_motion)
+ {
+ if (!priv->rubberband_select &&
+ (event->x - priv->button_down_x) * (event->x - priv->button_down_x) +
+ (event->y - priv->button_down_y) * (event->y - priv->button_down_y) > RUBBERBAND_START_DISTANCE *
RUBBERBAND_START_DISTANCE)
+ {
+ priv->rubberband_select = TRUE;
+ priv->rubberband_first = egg_flow_box_find_child_at_pos (box, priv->button_down_x,
priv->button_down_y);
+ }
+
+ if (priv->rubberband_select)
+ {
+ if (priv->rubberband_first == NULL)
+ priv->rubberband_first = child;
+ if (child != NULL)
+ priv->rubberband_last = child;
+ }
+ }
+
return FALSE;
}
@@ -2655,7 +2684,20 @@ egg_flow_box_button_press_event (GtkWidget *widget,
gtk_widget_queue_draw (GTK_WIDGET (box));
if (event->type == GDK_2BUTTON_PRESS &&
!priv->activate_on_single_click)
- g_signal_emit (box, signals[CHILD_ACTIVATED], 0, child);
+ {
+ g_signal_emit (box, signals[CHILD_ACTIVATED], 0, child);
+ return TRUE;
+ }
+ }
+
+ if (priv->selection_mode == GTK_SELECTION_MULTIPLE)
+ {
+ priv->track_motion = TRUE;
+ priv->rubberband_select = FALSE;
+ priv->rubberband_first = NULL;
+ priv->rubberband_last = NULL;
+ priv->button_down_x = event->x;
+ priv->button_down_y = event->y;
}
}
@@ -2885,6 +2927,15 @@ egg_flow_box_button_release_event (GtkWidget *widget,
gtk_widget_queue_draw (GTK_WIDGET (box));
}
+ priv->track_motion = FALSE;
+ if (priv->rubberband_select)
+ {
+ egg_flow_box_select_all_between (box, priv->rubberband_first, priv->rubberband_last);
+ priv->rubberband_first = NULL;
+ priv->rubberband_last = NULL;
+ priv->rubberband_select = FALSE;
+ }
+
return FALSE;
}
@@ -3352,12 +3403,129 @@ egg_flow_box_move_cursor (EggFlowBox *box,
egg_flow_box_update_selection (box, child, modify_selection, extend_selection);
}
+static void
+path_from_horizontal_line_rects (cairo_t *cr,
+ GdkRectangle *lines,
+ gint n_lines)
+{
+ gint start_line, end_line;
+ GdkRectangle *r;
+ gint 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];
+ gint gap, old;
+
+ gap = r2->y - (r1->y + r1->height);
+ r1->height += gap / 2;
+ old = r2->y;
+ r2->y = r1->y + r1->height;
+ r2->height += old - 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 void
+path_from_vertical_line_rects (cairo_t *cr,
+ GdkRectangle *lines,
+ gint n_lines)
+{
+ gint start_line, end_line;
+ GdkRectangle *r;
+ gint i;
+
+ /* Join rows horizontall by extending to the middle */
+ for (i = 0; i < n_lines - 1; i++)
+ {
+ GdkRectangle *r1 = &lines[i];
+ GdkRectangle *r2 = &lines[i+1];
+ gint gap, old;
+
+ gap = r2->x - (r1->x + r1->width);
+ r1->width += gap / 2;
+ old = r2->x;
+ r2->x = r1->x + r1->width;
+ r2->width += old - r2->x;
+ }
+
+ 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->y + r->height);
+ else
+ cairo_line_to (cr, r->x, r->y + r->height);
+ cairo_line_to (cr, r->x + r->width, r->y + r->height);
+
+ if (i < n_lines - 1 &&
+ (r->y + r->height < lines[i+1].y ||
+ r->y > lines[i+1].y + lines[i+1].height))
+ {
+ i++;
+ break;
+ }
+ }
+ end_line = i;
+ for (i = end_line - 1; i >= start_line; i--)
+ {
+ r = &lines[i];
+ cairo_line_to (cr, r->x + r->width, r->y);
+ cairo_line_to (cr, r->x, r->y);
+ }
+ cairo_close_path (cr);
+ start_line = end_line;
+ }
+ while (end_line < n_lines);
+}
+
static gboolean
egg_flow_box_draw (GtkWidget *widget,
cairo_t *cr)
{
EggFlowBox *box = EGG_FLOW_BOX (widget);
- GtkAllocation allocation = {0};
+ EggFlowBoxPrivate *priv = BOX_PRIV (box);
+ GtkAllocation allocation = { 0, };
GtkStyleContext* context;
gtk_widget_get_allocation (GTK_WIDGET (box), &allocation);
@@ -3366,6 +3534,105 @@ egg_flow_box_draw (GtkWidget *widget,
GTK_WIDGET_CLASS (egg_flow_box_parent_class)->draw (widget, cr);
+ if (priv->rubberband_first && priv->rubberband_last)
+ {
+ GSequenceIter *iter, *iter1, *iter2;
+ GdkRectangle line_rect, rect;
+ GArray *lines;
+ gboolean vertical;
+
+ vertical = priv->orientation == GTK_ORIENTATION_VERTICAL;
+
+ 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);
+
+ iter1 = CHILD_PRIV (priv->rubberband_first)->iter;
+ iter2 = CHILD_PRIV (priv->rubberband_last)->iter;
+
+ if (g_sequence_iter_compare (iter2, iter1) < 0)
+ {
+ iter = iter1;
+ iter1 = iter2;
+ iter2 = iter;
+ }
+
+ line_rect.width = 0;
+ lines = g_array_new (FALSE, FALSE, sizeof (GdkRectangle));
+
+ for (iter = iter1;
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ GtkWidget *child;
+
+ child = g_sequence_get (iter);
+ gtk_widget_get_allocation (GTK_WIDGET (child), &rect);
+ if (line_rect.width == 0)
+ line_rect = rect;
+ else
+ {
+ if ((vertical && rect.x == line_rect.x) ||
+ (!vertical && rect.y == line_rect.y))
+ gdk_rectangle_union (&rect, &line_rect, &line_rect);
+ else
+ {
+ g_array_append_val (lines, line_rect);
+ line_rect = rect;
+ }
+ }
+
+ if (g_sequence_iter_compare (iter, iter2) == 0)
+ break;
+ }
+
+ 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;
+
+ if (vertical)
+ path_from_vertical_line_rects (cr, (GdkRectangle *)lines->data, lines->len);
+ else
+ path_from_horizontal_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_style_context_restore (context);
+ cairo_restore (cr);
+ }
+
return TRUE;
}
@@ -3385,9 +3652,13 @@ egg_flow_box_realize (GtkWidget *widget)
attributes.width = allocation.width;
attributes.height = allocation.height;
attributes.window_type = GDK_WINDOW_CHILD;
- attributes.event_mask = gtk_widget_get_events (GTK_WIDGET (box)) |
- GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_POINTER_MOTION_MASK |
- GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK;
+ attributes.event_mask = gtk_widget_get_events (GTK_WIDGET (box))
+ | GDK_ENTER_NOTIFY_MASK
+ | GDK_LEAVE_NOTIFY_MASK
+ | GDK_POINTER_MOTION_MASK
+ | GDK_EXPOSURE_MASK
+ | GDK_BUTTON_PRESS_MASK
+ | GDK_BUTTON_RELEASE_MASK;
attributes.wclass = GDK_INPUT_OUTPUT;
window = gdk_window_new (gtk_widget_get_parent_window (GTK_WIDGET (box)),
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]