flowbox: Use gestures for event management

A multipress gesture takes care of item selection/activation, and
a drag gesture is used to manage events when rubberbanding selection
is performed.
This commit is contained in:
Carlos Garnacho 2014-06-05 15:45:21 +02:00
parent 933ff62f66
commit 3ffb4fa699

View File

@ -778,15 +778,14 @@ struct _GtkFlowBoxPrivate {
gpointer sort_data; gpointer sort_data;
GDestroyNotify sort_destroy; GDestroyNotify sort_destroy;
gboolean track_motion; GtkGesture *multipress_gesture;
GtkGesture *drag_gesture;
gboolean rubberband_select; gboolean rubberband_select;
GtkFlowBoxChild *rubberband_first; GtkFlowBoxChild *rubberband_first;
GtkFlowBoxChild *rubberband_last; GtkFlowBoxChild *rubberband_last;
gint button_down_x;
gint button_down_y;
gboolean rubberband_modify; gboolean rubberband_modify;
gboolean rubberband_extend; gboolean rubberband_extend;
GdkDevice *rubberband_device;
GtkScrollType autoscroll_mode; GtkScrollType autoscroll_mode;
guint autoscroll_id; guint autoscroll_id;
@ -2686,12 +2685,12 @@ autoscroll_cb (GtkWidget *widget,
if (priv->rubberband_select) if (priv->rubberband_select)
{ {
gint x, y; GdkEventSequence *sequence;
gdouble x, y;
GtkFlowBoxChild *child; GtkFlowBoxChild *child;
gdk_window_get_device_position (gtk_widget_get_window (widget), sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (priv->drag_gesture));
priv->rubberband_device, gtk_gesture_get_point (priv->drag_gesture, sequence, &x, &y);
&x, &y, NULL);
child = gtk_flow_box_find_child_at_pos (box, x, y); child = gtk_flow_box_find_child_at_pos (box, x, y);
@ -2732,6 +2731,8 @@ get_view_rect (GtkFlowBox *box,
if (GTK_IS_VIEWPORT (parent)) if (GTK_IS_VIEWPORT (parent))
{ {
view = gtk_viewport_get_view_window (GTK_VIEWPORT (parent)); view = gtk_viewport_get_view_window (GTK_VIEWPORT (parent));
rect->x = rect->y = 0;
rect->x = gtk_adjustment_get_value (priv->hadjustment); rect->x = gtk_adjustment_get_value (priv->hadjustment);
rect->y = gtk_adjustment_get_value (priv->vadjustment); rect->y = gtk_adjustment_get_value (priv->vadjustment);
rect->width = gdk_window_get_width (view); rect->width = gdk_window_get_width (view);
@ -2823,12 +2824,49 @@ gtk_flow_box_leave_notify_event (GtkWidget *widget,
return FALSE; return FALSE;
} }
static void
gtk_flow_box_drag_gesture_update (GtkGestureDrag *gesture,
gdouble offset_x,
gdouble offset_y,
GtkFlowBox *box)
{
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
gdouble start_x, start_y;
GtkFlowBoxChild *child;
gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
if (!priv->rubberband_select &&
(offset_x * offset_x) + (offset_y * offset_y) > RUBBERBAND_START_DISTANCE * RUBBERBAND_START_DISTANCE)
{
priv->rubberband_select = TRUE;
priv->rubberband_first = gtk_flow_box_find_child_at_pos (box, start_x, start_y);
/* Grab focus here, so Escape-to-stop-rubberband works */
gtk_flow_box_update_cursor (box, priv->rubberband_first);
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
}
if (priv->rubberband_select)
{
child = gtk_flow_box_find_child_at_pos (box, start_x + offset_x,
start_y + offset_y);
if (priv->rubberband_first == NULL)
priv->rubberband_first = child;
if (child != NULL)
priv->rubberband_last = child;
update_autoscroll_mode (box, start_x + offset_x, start_y + offset_y);
gtk_widget_queue_draw (GTK_WIDGET (box));
}
}
static gboolean static gboolean
gtk_flow_box_motion_notify_event (GtkWidget *widget, gtk_flow_box_motion_notify_event (GtkWidget *widget,
GdkEventMotion *event) GdkEventMotion *event)
{ {
GtkFlowBox *box = GTK_FLOW_BOX (widget); GtkFlowBox *box = GTK_FLOW_BOX (widget);
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
GtkFlowBoxChild *child; GtkFlowBoxChild *child;
GdkWindow *window; GdkWindow *window;
GdkWindow *event_window; GdkWindow *event_window;
@ -2856,71 +2894,99 @@ gtk_flow_box_motion_notify_event (GtkWidget *widget,
gtk_flow_box_update_prelight (box, child); gtk_flow_box_update_prelight (box, child);
gtk_flow_box_update_active (box, child); gtk_flow_box_update_active (box, child);
if (priv->track_motion) return GTK_WIDGET_CLASS (gtk_flow_box_parent_class)->motion_notify_event (widget, event);
{
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 = gtk_flow_box_find_child_at_pos (box, priv->button_down_x, priv->button_down_y);
/* Grab focus here, so Escape-to-stop-rubberband works */
gtk_flow_box_update_cursor (box, priv->rubberband_first);
}
if (priv->rubberband_select)
{
if (priv->rubberband_first == NULL)
priv->rubberband_first = child;
if (child != NULL)
priv->rubberband_last = child;
update_autoscroll_mode (box, event->x, event->y);
}
}
return FALSE;
} }
static gboolean static void
gtk_flow_box_button_press_event (GtkWidget *widget, gtk_flow_box_multipress_gesture_pressed (GtkGestureMultiPress *gesture,
GdkEventButton *event) guint n_press,
gdouble x,
gdouble y,
GtkFlowBox *box)
{ {
GtkFlowBox *box = GTK_FLOW_BOX (widget);
GtkFlowBoxPrivate *priv = BOX_PRIV (box); GtkFlowBoxPrivate *priv = BOX_PRIV (box);
GtkFlowBoxChild *child; GtkFlowBoxChild *child;
if (event->button == GDK_BUTTON_PRIMARY) child = gtk_flow_box_find_child_at_pos (box, x, y);
{
child = gtk_flow_box_find_child_at_pos (box, event->x, event->y);
if (child != NULL)
{
priv->active_child = child;
priv->active_child_active = TRUE;
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);
return TRUE;
}
}
if (priv->selection_mode == GTK_SELECTION_MULTIPLE) if (child == NULL)
return;
/* The drag gesture is only triggered by first press */
if (n_press != 1)
gtk_gesture_set_state (priv->drag_gesture, GTK_EVENT_SEQUENCE_DENIED);
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
priv->active_child = child;
priv->active_child_active = TRUE;
gtk_widget_queue_draw (GTK_WIDGET (box));
if (n_press == 2 && !priv->activate_on_single_click)
g_signal_emit (box, signals[CHILD_ACTIVATED], 0, child);
}
static void
gtk_flow_box_multipress_gesture_released (GtkGestureMultiPress *gesture,
guint n_press,
gdouble x,
gdouble y,
GtkFlowBox *box)
{
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
if (priv->active_child != NULL && priv->active_child_active)
{
if (priv->activate_on_single_click)
gtk_flow_box_select_and_activate (box, priv->active_child);
else
{ {
priv->track_motion = TRUE; GdkEventSequence *sequence;
priv->rubberband_select = FALSE; GdkInputSource source;
priv->rubberband_first = NULL; const GdkEvent *event;
priv->rubberband_last = NULL; gboolean modify;
priv->button_down_x = event->x; gboolean extend;
priv->button_down_y = event->y;
priv->rubberband_device = gdk_event_get_device ((GdkEvent*)event); get_current_selection_modifiers (GTK_WIDGET (box), &modify, &extend);
get_current_selection_modifiers (widget, &priv->rubberband_modify, &priv->rubberband_extend);
/* With touch, we default to modifying the selection.
* You can still clear the selection and start over
* by holding Ctrl.
*/
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
source = gdk_device_get_source (gdk_event_get_source_device (event));
if (source == GDK_SOURCE_TOUCHSCREEN)
modify = !modify;
gtk_flow_box_update_selection (box, priv->active_child, modify, extend);
} }
} }
return FALSE; priv->active_child = NULL;
priv->active_child_active = FALSE;
gtk_widget_queue_draw (GTK_WIDGET (box));
}
static void
gtk_flow_box_drag_gesture_begin (GtkGestureDrag *gesture,
gdouble start_x,
gdouble start_y,
GtkWidget *widget)
{
GtkFlowBoxPrivate *priv = BOX_PRIV (widget);
if (priv->selection_mode != GTK_SELECTION_MULTIPLE)
{
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
return;
}
priv->rubberband_select = FALSE;
priv->rubberband_first = NULL;
priv->rubberband_last = NULL;
get_current_selection_modifiers (widget, &priv->rubberband_modify, &priv->rubberband_extend);
} }
static void static void
@ -2931,65 +2997,40 @@ gtk_flow_box_stop_rubberband (GtkFlowBox *box)
priv->rubberband_select = FALSE; priv->rubberband_select = FALSE;
priv->rubberband_first = NULL; priv->rubberband_first = NULL;
priv->rubberband_last = NULL; priv->rubberband_last = NULL;
priv->rubberband_device = NULL;
remove_autoscroll (box); remove_autoscroll (box);
gtk_widget_queue_draw (GTK_WIDGET (box)); gtk_widget_queue_draw (GTK_WIDGET (box));
} }
static gboolean static void
gtk_flow_box_button_release_event (GtkWidget *widget, gtk_flow_box_drag_gesture_end (GtkGestureDrag *gesture,
GdkEventButton *event) gdouble offset_x,
gdouble offset_y,
GtkFlowBox *box)
{ {
GtkFlowBox *box = GTK_FLOW_BOX (widget);
GtkFlowBoxPrivate *priv = BOX_PRIV (box); GtkFlowBoxPrivate *priv = BOX_PRIV (box);
GdkEventSequence *sequence;
if (event->button == GDK_BUTTON_PRIMARY) if (!priv->rubberband_select)
{ return;
if (priv->active_child != NULL && priv->active_child_active)
{
if (priv->activate_on_single_click)
gtk_flow_box_select_and_activate (box, priv->active_child);
else
{
gboolean modify;
gboolean extend;
GdkDevice *device;
get_current_selection_modifiers (widget, &modify, &extend); sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
/* With touch, we default to modifying the selection. if (gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
* You can still clear the selection and start over
* by holding Ctrl.
*/
device = gdk_event_get_source_device ((GdkEvent *)event);
if (gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN)
modify = !modify;
gtk_flow_box_update_selection (box, priv->active_child, modify, extend);
}
}
priv->active_child = NULL;
priv->active_child_active = FALSE;
gtk_widget_queue_draw (GTK_WIDGET (box));
}
priv->track_motion = FALSE;
if (priv->rubberband_select)
{ {
if (!priv->rubberband_extend && !priv->rubberband_modify) if (!priv->rubberband_extend && !priv->rubberband_modify)
gtk_flow_box_unselect_all_internal (box); gtk_flow_box_unselect_all_internal (box);
gtk_flow_box_select_all_between (box, priv->rubberband_first, priv->rubberband_last, priv->rubberband_modify);
gtk_flow_box_select_all_between (box, priv->rubberband_first, priv->rubberband_last, priv->rubberband_modify);
gtk_flow_box_stop_rubberband (box); gtk_flow_box_stop_rubberband (box);
g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0); g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
} }
else
gtk_flow_box_stop_rubberband (box);
return FALSE; gtk_widget_queue_draw (GTK_WIDGET (box));
} }
static gboolean static gboolean
@ -3011,20 +3052,6 @@ gtk_flow_box_key_press_event (GtkWidget *widget,
return GTK_WIDGET_CLASS (gtk_flow_box_parent_class)->key_press_event (widget, event); return GTK_WIDGET_CLASS (gtk_flow_box_parent_class)->key_press_event (widget, event);
} }
static void
gtk_flow_box_grab_notify (GtkWidget *widget,
gboolean was_grabbed)
{
GtkFlowBox *box = GTK_FLOW_BOX (widget);
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
if (!was_grabbed)
{
if (priv->rubberband_select)
gtk_flow_box_stop_rubberband (box);
}
}
/* Realize and map {{{3 */ /* Realize and map {{{3 */
static void static void
@ -3570,6 +3597,9 @@ gtk_flow_box_finalize (GObject *obj)
g_clear_object (&priv->hadjustment); g_clear_object (&priv->hadjustment);
g_clear_object (&priv->vadjustment); g_clear_object (&priv->vadjustment);
g_object_unref (priv->drag_gesture);
g_object_unref (priv->multipress_gesture);
G_OBJECT_CLASS (gtk_flow_box_parent_class)->finalize (obj); G_OBJECT_CLASS (gtk_flow_box_parent_class)->finalize (obj);
} }
@ -3593,10 +3623,7 @@ gtk_flow_box_class_init (GtkFlowBoxClass *class)
widget_class->unmap = gtk_flow_box_unmap; widget_class->unmap = gtk_flow_box_unmap;
widget_class->focus = gtk_flow_box_focus; widget_class->focus = gtk_flow_box_focus;
widget_class->draw = gtk_flow_box_draw; widget_class->draw = gtk_flow_box_draw;
widget_class->button_press_event = gtk_flow_box_button_press_event;
widget_class->button_release_event = gtk_flow_box_button_release_event;
widget_class->key_press_event = gtk_flow_box_key_press_event; widget_class->key_press_event = gtk_flow_box_key_press_event;
widget_class->grab_notify = gtk_flow_box_grab_notify;
widget_class->get_request_mode = gtk_flow_box_get_request_mode; widget_class->get_request_mode = gtk_flow_box_get_request_mode;
widget_class->get_preferred_width = gtk_flow_box_get_preferred_width; widget_class->get_preferred_width = gtk_flow_box_get_preferred_width;
widget_class->get_preferred_height = gtk_flow_box_get_preferred_height; widget_class->get_preferred_height = gtk_flow_box_get_preferred_height;
@ -3934,6 +3961,32 @@ gtk_flow_box_init (GtkFlowBox *box)
priv->activate_on_single_click = TRUE; priv->activate_on_single_click = TRUE;
priv->children = g_sequence_new (NULL); priv->children = g_sequence_new (NULL);
priv->multipress_gesture = gtk_gesture_multi_press_new (GTK_WIDGET (box));
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->multipress_gesture),
FALSE);
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (priv->multipress_gesture),
GDK_BUTTON_PRIMARY);
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->multipress_gesture),
GTK_PHASE_BUBBLE);
g_signal_connect (priv->multipress_gesture, "pressed",
G_CALLBACK (gtk_flow_box_multipress_gesture_pressed), box);
g_signal_connect (priv->multipress_gesture, "released",
G_CALLBACK (gtk_flow_box_multipress_gesture_released), box);
priv->drag_gesture = gtk_gesture_drag_new (GTK_WIDGET (box));
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->drag_gesture),
FALSE);
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (priv->drag_gesture),
GDK_BUTTON_PRIMARY);
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->drag_gesture),
GTK_PHASE_CAPTURE);
g_signal_connect (priv->drag_gesture, "drag-begin",
G_CALLBACK (gtk_flow_box_drag_gesture_begin), box);
g_signal_connect (priv->drag_gesture, "drag-update",
G_CALLBACK (gtk_flow_box_drag_gesture_update), box);
g_signal_connect (priv->drag_gesture, "drag-end",
G_CALLBACK (gtk_flow_box_drag_gesture_end), box);
} }
/* Public API {{{2 */ /* Public API {{{2 */