diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c index c191ba35b4..2aa6a5f3d0 100644 --- a/gtk/gtktextview.c +++ b/gtk/gtktextview.c @@ -134,10 +134,8 @@ struct _GtkTextViewPrivate guint blink_time; /* time in msec the cursor has blinked since last user event */ guint im_spot_idle; gchar *im_module; - GdkDevice *grab_device; GdkDevice *dnd_device; - gulong selection_drag_handler; GtkTextHandle *text_handle; GtkWidget *selection_bubble; guint selection_bubble_timeout_id; @@ -192,16 +190,11 @@ struct _GtkTextViewPrivate guint first_validate_idle; /* Idle to revalidate onscreen portion, runs before resize */ guint incremental_validate_idle; /* Idle to revalidate offscreen portions, runs after redraw */ - gint pending_place_cursor_button; - GtkTextMark *dnd_mark; GtkIMContext *im_context; GtkWidget *popup_menu; - gint drag_start_x; - gint drag_start_y; - GSList *children; GtkTextPendingScroll *pending_scroll; @@ -209,6 +202,7 @@ struct _GtkTextViewPrivate GtkPixelCache *pixel_cache; GtkGesture *multipress_gesture; + GtkGesture *drag_gesture; /* Default style settings */ gint pixels_above_lines; @@ -315,6 +309,8 @@ enum PROP_POPULATE_ALL }; +static GQuark quark_text_selection_data = 0; + static void gtk_text_view_finalize (GObject *object); static void gtk_text_view_set_property (GObject *object, guint prop_id, @@ -342,8 +338,6 @@ static void gtk_text_view_unrealize (GtkWidget *widget); static void gtk_text_view_style_updated (GtkWidget *widget); static void gtk_text_view_direction_changed (GtkWidget *widget, GtkTextDirection previous_direction); -static void gtk_text_view_grab_notify (GtkWidget *widget, - gboolean was_grabbed); static void gtk_text_view_state_flags_changed (GtkWidget *widget, GtkStateFlags previous_state); @@ -352,6 +346,14 @@ static void gtk_text_view_multipress_gesture_pressed (GtkGestureMultiPress *gest gdouble x, gdouble y, GtkTextView *text_view); +static void gtk_text_view_drag_gesture_update (GtkGestureDrag *gesture, + gdouble offset_x, + gdouble offset_y, + GtkTextView *text_view); +static void gtk_text_view_drag_gesture_end (GtkGestureDrag *gesture, + gdouble offset_x, + gdouble offset_y, + GtkTextView *text_view); static gint gtk_text_view_event (GtkWidget *widget, GdkEvent *event); @@ -359,14 +361,10 @@ static gint gtk_text_view_key_press_event (GtkWidget *widget, GdkEventKey *event); static gint gtk_text_view_key_release_event (GtkWidget *widget, GdkEventKey *event); -static gint gtk_text_view_button_release_event (GtkWidget *widget, - GdkEventButton *event); static gint gtk_text_view_focus_in_event (GtkWidget *widget, GdkEventFocus *event); static gint gtk_text_view_focus_out_event (GtkWidget *widget, GdkEventFocus *event); -static gint gtk_text_view_motion_event (GtkWidget *widget, - GdkEventMotion *event); static gint gtk_text_view_draw (GtkWidget *widget, cairo_t *cr); static gboolean gtk_text_view_focus (GtkWidget *widget, @@ -458,7 +456,9 @@ static void gtk_text_view_start_selection_drag (GtkTextView *text static gboolean gtk_text_view_end_selection_drag (GtkTextView *text_view); static void gtk_text_view_start_selection_dnd (GtkTextView *text_view, const GtkTextIter *iter, - GdkEventMotion *event); + const GdkEvent *event, + gint x, + gint y); static void gtk_text_view_check_cursor_blink (GtkTextView *text_view); static void gtk_text_view_pend_cursor_blink (GtkTextView *text_view); static void gtk_text_view_stop_cursor_blink (GtkTextView *text_view); @@ -669,7 +669,6 @@ gtk_text_view_class_init (GtkTextViewClass *klass) widget_class->unrealize = gtk_text_view_unrealize; widget_class->style_updated = gtk_text_view_style_updated; widget_class->direction_changed = gtk_text_view_direction_changed; - widget_class->grab_notify = gtk_text_view_grab_notify; widget_class->state_flags_changed = gtk_text_view_state_flags_changed; widget_class->get_preferred_width = gtk_text_view_get_preferred_width; widget_class->get_preferred_height = gtk_text_view_get_preferred_height; @@ -677,10 +676,8 @@ gtk_text_view_class_init (GtkTextViewClass *klass) widget_class->event = gtk_text_view_event; widget_class->key_press_event = gtk_text_view_key_press_event; widget_class->key_release_event = gtk_text_view_key_release_event; - widget_class->button_release_event = gtk_text_view_button_release_event; widget_class->focus_in_event = gtk_text_view_focus_in_event; widget_class->focus_out_event = gtk_text_view_focus_out_event; - widget_class->motion_notify_event = gtk_text_view_motion_event; widget_class->draw = gtk_text_view_draw; widget_class->focus = gtk_text_view_focus; widget_class->drag_begin = gtk_text_view_drag_begin; @@ -1481,6 +1478,9 @@ G_GNUC_END_IGNORE_DEPRECATIONS gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_TEXT_VIEW_ACCESSIBLE); test_touchscreen = g_getenv ("GTK_TEST_TOUCHSCREEN") != NULL; + + quark_text_selection_data = + g_quark_from_static_string ("gtk-text-view-text-selection-data"); } static void @@ -1549,11 +1549,6 @@ gtk_text_view_init (GtkTextView *text_view) priv->text_window = text_window_new (GTK_TEXT_WINDOW_TEXT, widget, 200, 200); - priv->drag_start_x = -1; - priv->drag_start_y = -1; - - priv->pending_place_cursor_button = 0; - /* We handle all our own redrawing */ gtk_widget_set_redraw_on_allocate (widget, FALSE); @@ -1564,6 +1559,17 @@ gtk_text_view_init (GtkTextView *text_view) g_signal_connect (priv->multipress_gesture, "pressed", G_CALLBACK (gtk_text_view_multipress_gesture_pressed), widget); + + priv->drag_gesture = gtk_gesture_drag_new (widget); + gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->drag_gesture), + FALSE); + gtk_gesture_attach (priv->drag_gesture, GTK_PHASE_BUBBLE); + g_signal_connect (priv->drag_gesture, "drag-update", + G_CALLBACK (gtk_text_view_drag_gesture_update), + widget); + g_signal_connect (priv->drag_gesture, "drag-end", + G_CALLBACK (gtk_text_view_drag_gesture_end), + widget); } static void @@ -3255,6 +3261,9 @@ gtk_text_view_finalize (GObject *object) gtk_gesture_detach (priv->multipress_gesture); g_object_unref (priv->multipress_gesture); + gtk_gesture_detach (priv->drag_gesture); + g_object_unref (priv->drag_gesture); + if (priv->tabs) pango_tab_array_free (priv->tabs); @@ -4518,30 +4527,6 @@ gtk_text_view_unobscure_mouse_cursor (GtkTextView *text_view) } } -static void -gtk_text_view_grab_notify (GtkWidget *widget, - gboolean was_grabbed) -{ - GtkTextViewPrivate *priv; - - priv = GTK_TEXT_VIEW (widget)->priv; - - if (priv->grab_device && - gtk_widget_device_is_shadowed (widget, priv->grab_device)) - { - if (priv->drag_start_x >= 0) - { - priv->drag_start_x = -1; - priv->drag_start_y = -1; - } - - gtk_text_view_end_selection_drag (GTK_TEXT_VIEW (widget)); - gtk_text_view_unobscure_mouse_cursor (GTK_TEXT_VIEW (widget)); - priv->grab_device = NULL; - } -} - - /* * Events */ @@ -5123,16 +5108,14 @@ gtk_text_view_multipress_gesture_pressed (GtkGestureMultiPress *gesture, gtk_widget_get_modifier_mask (GTK_WIDGET (text_view), GDK_MODIFIER_INTENT_EXTEND_SELECTION))) { - priv->grab_device = gdk_event_get_device (event); - priv->drag_start_x = x; - priv->drag_start_y = y; - priv->pending_place_cursor_button = button; - } - else - { - gtk_text_view_start_selection_drag (text_view, &iter, - SELECT_CHARACTERS, event); + /* Claim the sequence on the drag gesture, but attach no selection data */ + gtk_gesture_set_state (priv->drag_gesture, + GTK_EVENT_SEQUENCE_CLAIMED); + break; } + + gtk_text_view_start_selection_drag (text_view, &iter, + SELECT_CHARACTERS, event); break; } case 2: @@ -5164,71 +5147,6 @@ gtk_text_view_multipress_gesture_pressed (GtkGestureMultiPress *gesture, gtk_event_controller_reset (GTK_EVENT_CONTROLLER (gesture)); } -static gint -gtk_text_view_button_release_event (GtkWidget *widget, GdkEventButton *event) -{ - GtkTextView *text_view; - GtkTextViewPrivate *priv; - GdkDevice *device; - - text_view = GTK_TEXT_VIEW (widget); - priv = text_view->priv; - device = gdk_event_get_source_device ((GdkEvent *) event); - - if (event->window != priv->text_window->bin_window) - return FALSE; - - if (event->button == GDK_BUTTON_PRIMARY) - { - if (priv->drag_start_x >= 0) - { - priv->drag_start_x = -1; - priv->drag_start_y = -1; - } - - if (gtk_text_view_end_selection_drag (GTK_TEXT_VIEW (widget))) - { - if (test_touchscreen || - gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN) - gtk_text_view_selection_bubble_popup_set (text_view); - return TRUE; - } - else if (priv->pending_place_cursor_button == event->button) - { - GtkTextHandleMode mode; - GtkTextIter iter; - - /* Unselect everything; we clicked inside selection, but - * didn't move by the drag threshold, so just clear selection - * and place cursor. - */ - gtk_text_layout_get_iter_at_pixel (priv->layout, - &iter, - event->x + priv->xoffset, - event->y + priv->yoffset); - - gtk_text_buffer_place_cursor (get_buffer (text_view), &iter); - gtk_text_view_check_cursor_blink (text_view); - - if (priv->text_handle) - { - if (test_touchscreen || gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN) - mode = GTK_TEXT_HANDLE_MODE_CURSOR; - else - mode = GTK_TEXT_HANDLE_MODE_NONE; - - gtk_text_view_update_handles (text_view, mode); - } - - priv->pending_place_cursor_button = 0; - - return FALSE; - } - } - - return FALSE; -} - static void keymap_direction_changed (GdkKeymap *keymap, GtkTextView *text_view) @@ -5310,52 +5228,6 @@ gtk_text_view_focus_out_event (GtkWidget *widget, GdkEventFocus *event) return FALSE; } -static gint -gtk_text_view_motion_event (GtkWidget *widget, GdkEventMotion *event) -{ - GtkTextView *text_view; - GtkTextViewPrivate *priv; - - text_view = GTK_TEXT_VIEW (widget); - priv = text_view->priv; - - gtk_text_view_unobscure_mouse_cursor (text_view); - - if (event->window == priv->text_window->bin_window && - priv->drag_start_x >= 0) - { - gint x = event->x; - gint y = event->y; - - gdk_event_request_motions (event); - - if (gtk_drag_check_threshold (widget, - priv->drag_start_x, - priv->drag_start_y, - x, y)) - { - GtkTextIter iter; - gint buffer_x, buffer_y; - - gtk_text_view_window_to_buffer_coords (text_view, - GTK_TEXT_WINDOW_TEXT, - priv->drag_start_x, - priv->drag_start_y, - &buffer_x, - &buffer_y); - - gtk_text_layout_get_iter_at_pixel (priv->layout, - &iter, - buffer_x, buffer_y); - - gtk_text_view_start_selection_dnd (text_view, &iter, event); - return TRUE; - } - } - - return FALSE; -} - static void gtk_text_view_paint (GtkWidget *widget, cairo_t *cr) @@ -6765,6 +6637,37 @@ gtk_text_view_unselect (GtkTextView *text_view) &insert); } +static gboolean +get_iter_from_gesture (GtkTextView *text_view, + GtkTextIter *iter, + gint *x, + gint *y) +{ + gdouble start_x, start_y, offset_x, offset_y; + GtkTextViewPrivate *priv; + gint xcoord, ycoord; + + priv = text_view->priv; + + if (!gtk_gesture_drag_get_start_point (GTK_GESTURE_DRAG (priv->drag_gesture), + &start_x, &start_y) || + !gtk_gesture_drag_get_offset (GTK_GESTURE_DRAG (priv->drag_gesture), + &offset_x, &offset_y)) + return FALSE; + + xcoord = start_x + offset_x + priv->xoffset; + ycoord = start_y + offset_y + priv->yoffset; + gtk_text_layout_get_iter_at_pixel (priv->layout, iter, xcoord, ycoord); + + if (x) + *x = xcoord; + + if (y) + *y = ycoord; + + return TRUE; +} + static void get_iter_at_pointer (GtkTextView *text_view, GdkDevice *device, @@ -6794,16 +6697,14 @@ get_iter_at_pointer (GtkTextView *text_view, static void move_mark_to_pointer_and_scroll (GtkTextView *text_view, - const gchar *mark_name, - GdkDevice *device, - GdkInputSource source) + const gchar *mark_name) { GtkTextIter newplace; GtkTextBuffer *buffer; GtkTextMark *mark; buffer = get_buffer (text_view); - get_iter_at_pointer (text_view, device, &newplace, NULL, NULL); + get_iter_from_gesture (text_view, &newplace, NULL, NULL); mark = gtk_text_buffer_get_mark (buffer, mark_name); @@ -6983,42 +6884,73 @@ selection_data_free (SelectionData *data) g_free (data); } -static gint -selection_motion_event_handler (GtkTextView *text_view, - GdkEventMotion *event, - SelectionData *data) +static void +gtk_text_view_drag_gesture_update (GtkGestureDrag *gesture, + gdouble offset_x, + gdouble offset_y, + GtkTextView *text_view) { - GtkTextViewPrivate *priv; - GdkInputSource input_source; + GdkEventSequence *sequence; + gdouble start_x, start_y; + gboolean is_touchscreen; + const GdkEvent *event; + SelectionData *data; GdkDevice *device; - priv = text_view->priv; - gdk_event_request_motions (event); + data = g_object_get_qdata (G_OBJECT (gesture), quark_text_selection_data); + sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); + event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence); + gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y); - device = gdk_event_get_source_device ((GdkEvent *) event); - input_source = gdk_device_get_source (device); - - if (priv->grab_device != event->device) - return FALSE; - - if (data->granularity == SELECT_CHARACTERS) + if (!data) { - move_mark_to_pointer_and_scroll (text_view, "insert", - event->device, input_source); + /* If no data is attached, the initial press happened within the current + * text selection, check for drag and drop to be initiated. + */ + if (gtk_drag_check_threshold (GTK_WIDGET (text_view), start_x, start_y, + start_x + offset_x, start_y + offset_y)) + { + GtkTextIter iter; + gint buffer_x, buffer_y; + + gtk_text_view_window_to_buffer_coords (text_view, + GTK_TEXT_WINDOW_TEXT, + start_x, start_y, + &buffer_x, + &buffer_y); + + gtk_text_layout_get_iter_at_pixel (text_view->priv->layout, + &iter, buffer_x, buffer_y); + + gtk_text_view_start_selection_dnd (text_view, &iter, event, + start_x, start_y); + } + + return; } - else + + device = gdk_event_get_source_device (event); + + is_touchscreen = test_touchscreen || + gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN; + + if (data->granularity == SELECT_CHARACTERS) + { + move_mark_to_pointer_and_scroll (text_view, "insert"); + } + else { GtkTextIter cursor, start, end; GtkTextIter orig_start, orig_end; GtkTextBuffer *buffer; - + buffer = get_buffer (text_view); gtk_text_buffer_get_iter_at_mark (buffer, &orig_start, data->orig_start); gtk_text_buffer_get_iter_at_mark (buffer, &orig_end, data->orig_end); - get_iter_at_pointer (text_view, event->device, &cursor, NULL, NULL); - + get_iter_from_gesture (text_view, &cursor, NULL, NULL); + start = cursor; extend_selection (text_view, data->granularity, &start, &end); @@ -7039,19 +6971,86 @@ selection_motion_event_handler (GtkTextView *text_view, */ if (text_view->priv->scroll_timeout != 0) /* reset on every motion event */ g_source_remove (text_view->priv->scroll_timeout); - + text_view->priv->scroll_timeout = gdk_threads_add_timeout (50, selection_scan_timeout, text_view); g_source_set_name_by_id (text_view->priv->scroll_timeout, "[gtk+] selection_scan_timeout"); - if (test_touchscreen || input_source == GDK_SOURCE_TOUCHSCREEN) + if (is_touchscreen) { _gtk_text_view_ensure_text_handles (text_view); gtk_text_view_update_handles (text_view, GTK_TEXT_HANDLE_MODE_SELECTION); - gtk_text_view_show_magnifier (text_view, event->x, event->y); + gtk_text_view_show_magnifier (text_view, start_x + offset_x, + start_y + offset_y); + } +} + +static void +gtk_text_view_drag_gesture_end (GtkGestureDrag *gesture, + gdouble offset_x, + gdouble offset_y, + GtkTextView *text_view) +{ + GdkEventSequence *sequence; + gdouble start_x, start_y; + GtkTextViewPrivate *priv; + gboolean is_touchscreen; + const GdkEvent *event; + GdkDevice *device; + + priv = text_view->priv; + sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); + gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y); + + g_object_set_qdata (G_OBJECT (gesture), quark_text_selection_data, NULL); + gtk_text_view_unobscure_mouse_cursor (text_view); + + if (priv->scroll_timeout != 0) + { + g_source_remove (priv->scroll_timeout); + priv->scroll_timeout = 0; } - return TRUE; + if (priv->magnifier_popover) + gtk_widget_hide (priv->magnifier_popover); + + /* Check whether the drag was cancelled rather than finished */ + if (!gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence)) + return; + + event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence); + device = gdk_event_get_source_device (event); + is_touchscreen = test_touchscreen || + gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN; + + if (is_touchscreen) + gtk_text_view_selection_bubble_popup_set (text_view); + + if (!gtk_drag_check_threshold (GTK_WIDGET (text_view), start_x, start_y, + start_x + offset_x, start_y + offset_y)) + { + GtkTextHandleMode mode = GTK_TEXT_HANDLE_MODE_NONE; + GtkTextIter iter; + + /* Unselect everything; we clicked inside selection, but + * didn't move by the drag threshold, so just clear selection + * and place cursor. + */ + gtk_text_layout_get_iter_at_pixel (priv->layout, &iter, + start_x + offset_x + priv->xoffset, + start_y + offset_y + priv->yoffset); + + gtk_text_buffer_place_cursor (get_buffer (text_view), &iter); + gtk_text_view_check_cursor_blink (text_view); + + if (priv->text_handle) + { + if (is_touchscreen) + mode = GTK_TEXT_HANDLE_MODE_CURSOR; + + gtk_text_view_update_handles (text_view, mode); + } + } } static void @@ -7067,14 +7066,10 @@ gtk_text_view_start_selection_drag (GtkTextView *text_view, SelectionData *data; GdkModifierType state; - if (text_view->priv->selection_drag_handler != 0) - return; - priv = text_view->priv; data = g_new0 (SelectionData, 1); data->granularity = granularity; - priv->grab_device = gdk_event_get_device (event); buffer = get_buffer (text_view); cursor = *iter; @@ -7128,11 +7123,11 @@ gtk_text_view_start_selection_drag (GtkTextView *text_view, &orig_end, TRUE); gtk_text_view_check_cursor_blink (text_view); - text_view->priv->selection_drag_handler = g_signal_connect_data (text_view, - "motion-notify-event", - G_CALLBACK (selection_motion_event_handler), - data, - (GClosureNotify) selection_data_free, 0); + g_object_set_qdata_full (G_OBJECT (priv->drag_gesture), + quark_text_selection_data, + data, (GDestroyNotify) selection_data_free); + gtk_gesture_set_state (priv->drag_gesture, + GTK_EVENT_SEQUENCE_CLAIMED); } /* returns whether we were really dragging */ @@ -7143,23 +7138,15 @@ gtk_text_view_end_selection_drag (GtkTextView *text_view) priv = text_view->priv; - if (!priv->grab_device) + if (!gtk_gesture_is_active (priv->drag_gesture)) return FALSE; - if (priv->selection_drag_handler == 0) - return FALSE; - - g_signal_handler_disconnect (text_view, priv->selection_drag_handler); - priv->selection_drag_handler = 0; - if (priv->scroll_timeout != 0) { g_source_remove (priv->scroll_timeout); priv->scroll_timeout = 0; } - priv->grab_device = NULL; - if (priv->magnifier_popover) gtk_widget_hide (priv->magnifier_popover); @@ -7499,7 +7486,9 @@ drag_begin_cb (GtkWidget *widget, static void gtk_text_view_start_selection_dnd (GtkTextView *text_view, const GtkTextIter *iter, - GdkEventMotion *event) + const GdkEvent *event, + gint x, + gint y) { GtkTargetList *target_list; @@ -7509,13 +7498,7 @@ gtk_text_view_start_selection_dnd (GtkTextView *text_view, G_CALLBACK (drag_begin_cb), NULL); gtk_drag_begin_with_coordinates (GTK_WIDGET (text_view), target_list, GDK_ACTION_COPY | GDK_ACTION_MOVE, - 1, (GdkEvent*)event, - text_view->priv->drag_start_x, - text_view->priv->drag_start_y); - - text_view->priv->drag_start_x = -1; - text_view->priv->drag_start_y = -1; - text_view->priv->pending_place_cursor_button = 0; + 1, (GdkEvent*) event, x, y); } static void