From 0264a64f291d429e9dfc587e132f55ad079e7851 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Mon, 3 Feb 2020 15:59:49 +0100 Subject: [PATCH] gtktexthandle: Refactor and use native surfaces Instead of being a GObject managing two GtkWidgets, make GtkTextHandle a GtkWidget subclass, representing a single handle. From the perspective of users (GtkText and GtkTextView), this is not a big leap since they have to be aware of a great deal of text handles' state. It actually makes things more direct and simple. With text handles being widgets, those can be actual children of the widget, and may have their own GdkSurface that we move around at will. This is the second major aspect of this refactor. --- gtk/gtktext.c | 382 ++++++-------- gtk/gtktexthandle.c | 1027 ++++++++++++++---------------------- gtk/gtktexthandleprivate.h | 69 +-- gtk/gtktextview.c | 348 ++++++------ 4 files changed, 723 insertions(+), 1103 deletions(-) diff --git a/gtk/gtktext.c b/gtk/gtktext.c index b052cebb62..f23878aec4 100644 --- a/gtk/gtktext.c +++ b/gtk/gtktext.c @@ -140,6 +140,13 @@ static GQuark quark_password_hint = 0; +enum +{ + TEXT_HANDLE_CURSOR, + TEXT_HANDLE_SELECTION_BOUND, + TEXT_HANDLE_N_HANDLES +}; + typedef struct _GtkTextPasswordHint GtkTextPasswordHint; typedef struct _GtkTextPrivate GtkTextPrivate; @@ -159,7 +166,7 @@ struct _GtkTextPrivate char *im_module; GtkWidget *emoji_completion; - GtkTextHandle *text_handle; + GtkTextHandle *text_handles[TEXT_HANDLE_N_HANDLES]; GtkWidget *selection_bubble; guint selection_bubble_timeout_id; @@ -229,6 +236,7 @@ struct _GtkTextPrivate guint selection_handle_dragged : 1; guint populate_all : 1; guint propagate_text_width : 1; + guint text_handles_enabled : 1; }; struct _GtkTextPasswordHint @@ -464,15 +472,12 @@ static gboolean gtk_text_key_controller_key_pressed (GtkEventControllerKey *c /* GtkTextHandle handlers */ static void gtk_text_handle_drag_started (GtkTextHandle *handle, - GtkTextHandlePosition pos, GtkText *self); static void gtk_text_handle_dragged (GtkTextHandle *handle, - GtkTextHandlePosition pos, int x, int y, GtkText *self); static void gtk_text_handle_drag_finished (GtkTextHandle *handle, - GtkTextHandlePosition pos, GtkText *self); /* Internal routines @@ -539,6 +544,7 @@ static void emit_changed (GtkText *self); static void gtk_text_update_clipboard_actions (GtkText *self); static void gtk_text_update_emoji_action (GtkText *self); +static void gtk_text_update_handles (GtkText *self); static void gtk_text_activate_clipboard_cut (GtkWidget *widget, const char *action_name, @@ -1701,6 +1707,26 @@ gtk_text_get_property (GObject *object, } } +static void +gtk_text_ensure_text_handles (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + int i; + + for (i = 0; i < TEXT_HANDLE_N_HANDLES; i++) + { + if (priv->text_handles[i]) + continue; + priv->text_handles[i] = gtk_text_handle_new (GTK_WIDGET (self)); + g_signal_connect (priv->text_handles[i], "drag-started", + G_CALLBACK (gtk_text_handle_drag_started), self); + g_signal_connect (priv->text_handles[i], "handle-dragged", + G_CALLBACK (gtk_text_handle_dragged), self); + g_signal_connect (priv->text_handles[i], "drag-finished", + G_CALLBACK (gtk_text_handle_drag_finished), self); + } +} + static void gtk_text_init (GtkText *self) { @@ -1852,6 +1878,8 @@ gtk_text_dispose (GObject *object) g_clear_pointer (&priv->selection_bubble, gtk_widget_unparent); g_clear_pointer (&priv->popup_menu, gtk_widget_unparent); + g_clear_pointer ((GtkWidget **) &priv->text_handles[TEXT_HANDLE_CURSOR], gtk_widget_unparent); + g_clear_pointer ((GtkWidget **) &priv->text_handles[TEXT_HANDLE_SELECTION_BOUND], gtk_widget_unparent); g_clear_object (&priv->extra_menu); G_OBJECT_CLASS (gtk_text_parent_class)->dispose (object); @@ -1869,7 +1897,6 @@ gtk_text_finalize (GObject *object) g_clear_object (&priv->cached_layout); g_clear_object (&priv->im_context); g_clear_pointer (&priv->magnifier_popover, gtk_widget_destroy); - g_clear_object (&priv->text_handle); g_free (priv->im_module); g_clear_pointer (&priv->placeholder, gtk_widget_unparent); @@ -1904,23 +1931,6 @@ gtk_text_ensure_magnifier (GtkText *self) gtk_widget_show (priv->magnifier); } -static void -gtk_text_ensure_text_handles (GtkText *self) -{ - GtkTextPrivate *priv = gtk_text_get_instance_private (self); - - if (priv->text_handle) - return; - - priv->text_handle = _gtk_text_handle_new (GTK_WIDGET (self)); - g_signal_connect (priv->text_handle, "drag-started", - G_CALLBACK (gtk_text_handle_drag_started), self); - g_signal_connect (priv->text_handle, "handle-dragged", - G_CALLBACK (gtk_text_handle_dragged), self); - g_signal_connect (priv->text_handle, "drag-finished", - G_CALLBACK (gtk_text_handle_drag_finished), self); -} - static void begin_change (GtkText *self) { @@ -2051,10 +2061,8 @@ gtk_text_unmap (GtkWidget *widget) GtkText *self = GTK_TEXT (widget); GtkTextPrivate *priv = gtk_text_get_instance_private (self); - if (priv->text_handle) - _gtk_text_handle_set_mode (priv->text_handle, - GTK_TEXT_HANDLE_MODE_NONE); - + priv->text_handles_enabled = FALSE; + gtk_text_update_handles (self); priv->cursor_alpha = 1.0; GTK_WIDGET_CLASS (gtk_text_parent_class)->unmap (widget); @@ -2118,21 +2126,21 @@ update_im_cursor_location (GtkText *self) } static void -gtk_text_move_handle (GtkText *self, - GtkTextHandlePosition pos, - int x, - int y, - int height) +gtk_text_move_handle (GtkText *self, + GtkTextHandle *handle, + int x, + int y, + int height) { GtkTextPrivate *priv = gtk_text_get_instance_private (self); - if (!_gtk_text_handle_get_is_dragged (priv->text_handle, pos) && + if (!gtk_text_handle_get_is_dragged (handle) && (x < 0 || x > gtk_widget_get_width (GTK_WIDGET (self)))) { /* Hide the handle if it's not being manipulated * and fell outside of the visible text area. */ - _gtk_text_handle_set_visible (priv->text_handle, pos, FALSE); + gtk_widget_hide (GTK_WIDGET (handle)); } else { @@ -2143,9 +2151,9 @@ gtk_text_move_handle (GtkText *self, rect.width = 1; rect.height = height; - _gtk_text_handle_set_visible (priv->text_handle, pos, TRUE); - _gtk_text_handle_set_position (priv->text_handle, pos, &rect); - _gtk_text_handle_set_direction (priv->text_handle, pos, priv->resolved_dir); + gtk_text_handle_set_position (handle, &rect); + gtk_widget_set_direction (GTK_WIDGET (handle), priv->resolved_dir); + gtk_widget_show (GTK_WIDGET (handle)); } } @@ -2173,45 +2181,65 @@ gtk_text_get_selection_bound_location (GtkText *self) } static void -gtk_text_update_handles (GtkText *self, - GtkTextHandleMode mode) +gtk_text_update_handles (GtkText *self) { GtkTextPrivate *priv = gtk_text_get_instance_private (self); const int text_height = gtk_widget_get_height (GTK_WIDGET (self)); int strong_x; int cursor, bound; - _gtk_text_handle_set_mode (priv->text_handle, mode); - - gtk_text_get_cursor_locations (self, &strong_x, NULL); - cursor = strong_x - priv->scroll_offset; - - if (mode == GTK_TEXT_HANDLE_MODE_SELECTION) + if (!priv->text_handles_enabled) { - int start, end; + if (priv->text_handles[TEXT_HANDLE_CURSOR]) + gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_CURSOR])); + if (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]) + gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])); + } + else + { + gtk_text_ensure_text_handles (self); + gtk_text_get_cursor_locations (self, &strong_x, NULL); + cursor = strong_x - priv->scroll_offset; - bound = gtk_text_get_selection_bound_location (self) - priv->scroll_offset; - - if (priv->selection_bound > priv->current_pos) + if (priv->selection_bound != priv->current_pos) { - start = cursor; - end = bound; + int start, end; + + bound = gtk_text_get_selection_bound_location (self) - priv->scroll_offset; + + if (priv->selection_bound > priv->current_pos) + { + start = cursor; + end = bound; + } + else + { + start = bound; + end = cursor; + } + + /* Update start selection bound */ + gtk_text_handle_set_role (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND], + GTK_TEXT_HANDLE_ROLE_SELECTION_END); + gtk_text_move_handle (self, + priv->text_handles[TEXT_HANDLE_SELECTION_BOUND], + end, 0, text_height); + gtk_text_handle_set_role (priv->text_handles[TEXT_HANDLE_CURSOR], + GTK_TEXT_HANDLE_ROLE_SELECTION_START); + gtk_text_move_handle (self, + priv->text_handles[TEXT_HANDLE_CURSOR], + start, 0, text_height); } else { - start = bound; - end = cursor; + gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])); + gtk_text_handle_set_role (priv->text_handles[TEXT_HANDLE_CURSOR], + GTK_TEXT_HANDLE_ROLE_CURSOR); + gtk_text_move_handle (self, + priv->text_handles[TEXT_HANDLE_CURSOR], + cursor, 0, text_height); } - - /* Update start selection bound */ - gtk_text_move_handle (self, GTK_TEXT_HANDLE_POSITION_SELECTION_START, - start, 0, text_height); - gtk_text_move_handle (self, GTK_TEXT_HANDLE_POSITION_SELECTION_END, - end, 0, text_height); } - else - gtk_text_move_handle (self, GTK_TEXT_HANDLE_POSITION_CURSOR, - cursor, 0, text_height); } static void @@ -2342,13 +2370,7 @@ gtk_text_size_allocate (GtkWidget *widget, if (chooser) gtk_native_check_resize (GTK_NATIVE (chooser)); - if (priv->text_handle) - { - GtkTextHandleMode handle_mode = _gtk_text_handle_get_mode (priv->text_handle); - - if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE) - gtk_text_update_handles (self, handle_mode); - } + gtk_text_update_handles (self); if (priv->emoji_completion) gtk_native_check_resize (GTK_NATIVE (priv->emoji_completion)); @@ -2361,6 +2383,12 @@ gtk_text_size_allocate (GtkWidget *widget, if (priv->selection_bubble) gtk_native_check_resize (GTK_NATIVE (priv->selection_bubble)); + + if (priv->text_handles[TEXT_HANDLE_CURSOR]) + gtk_native_check_resize (GTK_NATIVE (priv->text_handles[TEXT_HANDLE_CURSOR])); + + if (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]) + gtk_native_check_resize (GTK_NATIVE (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])); } static void @@ -2599,7 +2627,6 @@ gtk_text_click_gesture_pressed (GtkGestureClick *gesture, else if (button == GDK_BUTTON_PRIMARY) { gboolean have_selection; - GtkTextHandleMode mode; gboolean is_touchscreen, extend_selection; GdkDevice *source; guint state; @@ -2612,15 +2639,7 @@ gtk_text_click_gesture_pressed (GtkGestureClick *gesture, is_touchscreen = gtk_simulate_touchscreen () || gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN; - if (!is_touchscreen) - mode = GTK_TEXT_HANDLE_MODE_NONE; - else if (have_selection) - mode = GTK_TEXT_HANDLE_MODE_SELECTION; - else - mode = GTK_TEXT_HANDLE_MODE_CURSOR; - - if (is_touchscreen) - gtk_text_ensure_text_handles (self); + priv->text_handles_enabled = is_touchscreen; priv->in_drag = FALSE; priv->select_words = FALSE; @@ -2693,15 +2712,11 @@ gtk_text_click_gesture_pressed (GtkGestureClick *gesture, case 2: priv->select_words = TRUE; gtk_text_select_word (self); - if (is_touchscreen) - mode = GTK_TEXT_HANDLE_MODE_SELECTION; break; case 3: priv->select_lines = TRUE; gtk_text_select_line (self); - if (is_touchscreen) - mode = GTK_TEXT_HANDLE_MODE_SELECTION; break; default: @@ -2733,8 +2748,7 @@ gtk_text_click_gesture_pressed (GtkGestureClick *gesture, gtk_gesture_set_state (priv->drag_gesture, GTK_EVENT_SEQUENCE_CLAIMED); - if (priv->text_handle) - gtk_text_update_handles (self, mode); + gtk_text_update_handles (self); } if (n_press >= 3) @@ -2950,11 +2964,8 @@ gtk_text_drag_gesture_update (GtkGestureDrag *gesture, if (gtk_simulate_touchscreen () || input_source == GDK_SOURCE_TOUCHSCREEN) { - gtk_text_ensure_text_handles (self); - gtk_text_update_handles (self, - (priv->current_pos == priv->selection_bound) ? - GTK_TEXT_HANDLE_MODE_CURSOR : - GTK_TEXT_HANDLE_MODE_SELECTION); + priv->text_handles_enabled = TRUE; + gtk_text_update_handles (self); gtk_text_show_magnifier (self, x - priv->scroll_offset, y); } } @@ -2967,10 +2978,8 @@ gtk_text_drag_gesture_end (GtkGestureDrag *gesture, GtkText *self) { GtkTextPrivate *priv = gtk_text_get_instance_private (self); - gboolean in_drag, is_touchscreen; + gboolean in_drag; GdkEventSequence *sequence; - GdkEvent *event; - GdkDevice *source; sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); in_drag = priv->in_drag; @@ -2983,11 +2992,6 @@ gtk_text_drag_gesture_end (GtkGestureDrag *gesture, if (!gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence)) return; - event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence); - source = gdk_event_get_source_device (event); - is_touchscreen = gtk_simulate_touchscreen () || - gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN; - if (in_drag) { int tmp_pos = gtk_text_find_position (self, priv->drag_start_x); @@ -2995,8 +2999,7 @@ gtk_text_drag_gesture_end (GtkGestureDrag *gesture, gtk_text_set_selection_bounds (self, tmp_pos, tmp_pos); } - if (is_touchscreen && priv->selection_bound != priv->current_pos) - gtk_text_update_handles (self, GTK_TEXT_HANDLE_MODE_CURSOR); + gtk_text_update_handles (self); gtk_text_update_primary_selection (self); } @@ -3029,9 +3032,8 @@ gtk_text_key_controller_key_pressed (GtkEventControllerKey *controller, gtk_text_selection_bubble_popup_unset (self); - if (priv->text_handle) - _gtk_text_handle_set_mode (priv->text_handle, - GTK_TEXT_HANDLE_MODE_NONE); + priv->text_handles_enabled = FALSE; + gtk_text_update_handles (self); if (keyval == GDK_KEY_Return || keyval == GDK_KEY_KP_Enter || @@ -3082,9 +3084,8 @@ gtk_text_focus_out (GtkWidget *widget) gtk_text_selection_bubble_popup_unset (self); - if (priv->text_handle) - _gtk_text_handle_set_mode (priv->text_handle, - GTK_TEXT_HANDLE_MODE_NONE); + priv->text_handles_enabled = FALSE; + gtk_text_update_handles (self); gtk_widget_queue_draw (widget); @@ -3957,15 +3958,7 @@ gtk_text_cut_clipboard (GtkText *self) gtk_text_selection_bubble_popup_unset (self); - if (priv->text_handle) - { - GtkTextHandleMode handle_mode; - - handle_mode = _gtk_text_handle_get_mode (priv->text_handle); - - if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE) - gtk_text_update_handles (self, GTK_TEXT_HANDLE_MODE_CURSOR); - } + gtk_text_update_handles (self); } static void @@ -3978,15 +3971,7 @@ gtk_text_paste_clipboard (GtkText *self) else gtk_widget_error_bell (GTK_WIDGET (self)); - if (priv->text_handle) - { - GtkTextHandleMode handle_mode; - - handle_mode = _gtk_text_handle_get_mode (priv->text_handle); - - if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE) - gtk_text_update_handles (self, GTK_TEXT_HANDLE_MODE_CURSOR); - } + gtk_text_update_handles (self); } static void @@ -4243,9 +4228,6 @@ gtk_text_reset_layout (GtkText *self) static void gtk_text_recompute (GtkText *self) { - GtkTextPrivate *priv = gtk_text_get_instance_private (self); - GtkTextHandleMode handle_mode; - gtk_text_reset_layout (self); gtk_text_check_cursor_blink (self); @@ -4253,13 +4235,7 @@ gtk_text_recompute (GtkText *self) update_im_cursor_location (self); - if (priv->text_handle) - { - handle_mode = _gtk_text_handle_get_mode (priv->text_handle); - - if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE) - gtk_text_update_handles (self, handle_mode); - } + gtk_text_update_handles (self); gtk_widget_queue_draw (GTK_WIDGET (self)); } @@ -4542,84 +4518,67 @@ gtk_text_draw_cursor (GtkText *self, } static void -gtk_text_handle_dragged (GtkTextHandle *handle, - GtkTextHandlePosition pos, - int x, - int y, - GtkText *self) +gtk_text_handle_dragged (GtkTextHandle *handle, + int x, + int y, + GtkText *self) { - int cursor_pos, selection_bound_pos, tmp_pos; + int cursor_pos, selection_bound_pos, tmp_pos, *old_pos; GtkTextPrivate *priv = gtk_text_get_instance_private (self); - GtkTextHandleMode mode; - int *min, *max; gtk_text_selection_bubble_popup_unset (self); cursor_pos = priv->current_pos; selection_bound_pos = priv->selection_bound; - mode = _gtk_text_handle_get_mode (handle); tmp_pos = gtk_text_find_position (self, x + priv->scroll_offset); - if (mode == GTK_TEXT_HANDLE_MODE_CURSOR || - cursor_pos >= selection_bound_pos) + if (handle == priv->text_handles[TEXT_HANDLE_CURSOR]) { - max = &cursor_pos; - min = &selection_bound_pos; + /* Avoid running past the other handle in selection mode */ + if (tmp_pos >= selection_bound_pos && + gtk_widget_is_visible (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]))) + { + tmp_pos = selection_bound_pos - 1; + } + + old_pos = &cursor_pos; + } + else if (handle == priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]) + { + /* Avoid running past the other handle */ + if (tmp_pos <= cursor_pos) + tmp_pos = cursor_pos + 1; + + old_pos = &selection_bound_pos; } else + g_assert_not_reached (); + + if (tmp_pos != *old_pos) { - max = &selection_bound_pos; - min = &cursor_pos; - } + *old_pos = tmp_pos; - if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_END) - { - if (mode == GTK_TEXT_HANDLE_MODE_SELECTION) - { - int min_pos; - - min_pos = MAX (*min + 1, 0); - tmp_pos = MAX (tmp_pos, min_pos); - } - - *max = tmp_pos; - } - else - { - if (mode == GTK_TEXT_HANDLE_MODE_SELECTION) - { - int max_pos; - - max_pos = *max - 1; - *min = MIN (tmp_pos, max_pos); - } - } - - if (cursor_pos != priv->current_pos || - selection_bound_pos != priv->selection_bound) - { - if (mode == GTK_TEXT_HANDLE_MODE_CURSOR) - { - priv->cursor_handle_dragged = TRUE; - gtk_text_set_positions (self, cursor_pos, cursor_pos); - } + if (handle == priv->text_handles[TEXT_HANDLE_CURSOR] && + !gtk_widget_is_visible (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]))) + gtk_text_set_positions (self, cursor_pos, cursor_pos); else - { - priv->selection_handle_dragged = TRUE; - gtk_text_set_positions (self, cursor_pos, selection_bound_pos); - } + gtk_text_set_positions (self, cursor_pos, selection_bound_pos); - gtk_text_update_handles (self, mode); + if (handle == priv->text_handles[TEXT_HANDLE_CURSOR]) + priv->cursor_handle_dragged = TRUE; + else if (handle == priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]) + priv->selection_handle_dragged = TRUE; + + gtk_text_update_handles (self); } gtk_text_show_magnifier (self, x, y); } static void -gtk_text_handle_drag_started (GtkTextHandle *handle, - GtkTextHandlePosition pos, - GtkText *self) +gtk_text_handle_drag_started (GtkTextHandle *handle, + GtkText *self) { GtkTextPrivate *priv = gtk_text_get_instance_private (self); @@ -4628,9 +4587,8 @@ gtk_text_handle_drag_started (GtkTextHandle *handle, } static void -gtk_text_handle_drag_finished (GtkTextHandle *handle, - GtkTextHandlePosition pos, - GtkText *self) +gtk_text_handle_drag_finished (GtkTextHandle *handle, + GtkText *self) { GtkTextPrivate *priv = gtk_text_get_instance_private (self); @@ -4644,7 +4602,7 @@ gtk_text_handle_drag_finished (GtkTextHandle *handle, if (g_get_monotonic_time() - priv->handle_place_time < double_click_time * 1000) { gtk_text_select_word (self); - gtk_text_update_handles (self, GTK_TEXT_HANDLE_MODE_SELECTION); + gtk_text_update_handles (self); } else gtk_text_selection_bubble_popup_set (self); @@ -4755,20 +4713,14 @@ static gboolean gtk_text_get_is_selection_handle_dragged (GtkText *self) { GtkTextPrivate *priv = gtk_text_get_instance_private (self); - GtkTextHandlePosition pos; - - if (!priv->text_handle) - return FALSE; - - if (_gtk_text_handle_get_mode (priv->text_handle) != GTK_TEXT_HANDLE_MODE_SELECTION) - return FALSE; + GtkTextHandle *handle; if (priv->current_pos >= priv->selection_bound) - pos = GTK_TEXT_HANDLE_POSITION_SELECTION_START; + handle = priv->text_handles[TEXT_HANDLE_CURSOR]; else - pos = GTK_TEXT_HANDLE_POSITION_SELECTION_END; + handle = priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]; - return _gtk_text_handle_get_is_dragged (priv->text_handle, pos); + return handle && gtk_text_handle_get_is_dragged (handle); } static void @@ -4818,7 +4770,6 @@ gtk_text_adjust_scroll (GtkText *self) int min_offset, max_offset; int strong_x, weak_x; int strong_xoffset, weak_xoffset; - GtkTextHandleMode handle_mode; if (!gtk_widget_get_realized (GTK_WIDGET (self))) return; @@ -4881,13 +4832,7 @@ gtk_text_adjust_scroll (GtkText *self) g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_SCROLL_OFFSET]); - if (priv->text_handle) - { - handle_mode = _gtk_text_handle_get_mode (priv->text_handle); - - if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE) - gtk_text_update_handles (self, handle_mode); - } + gtk_text_update_handles (self); } static int @@ -5926,23 +5871,10 @@ show_or_hide_handles (GtkWidget *popover, { GtkTextPrivate *priv = gtk_text_get_instance_private (self); gboolean visible; - GtkTextHandle *handle; - GtkTextHandleMode mode; visible = gtk_widget_get_visible (popover); - - handle = priv->text_handle; - mode = _gtk_text_handle_get_mode (handle); - - if (mode == GTK_TEXT_HANDLE_MODE_CURSOR) - { - _gtk_text_handle_set_visible (handle, GTK_TEXT_HANDLE_POSITION_CURSOR, !visible); - } - else if (mode == GTK_TEXT_HANDLE_MODE_SELECTION) - { - _gtk_text_handle_set_visible (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START, !visible); - _gtk_text_handle_set_visible (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END, !visible); - } + priv->text_handles_enabled = !visible; + gtk_text_update_handles (self); } static void diff --git a/gtk/gtktexthandle.c b/gtk/gtktexthandle.c index c22a928731..173965aace 100644 --- a/gtk/gtktexthandle.c +++ b/gtk/gtktexthandle.c @@ -25,7 +25,6 @@ #include "gtkwindowprivate.h" #include "gtkcssnodeprivate.h" #include "gtkwidgetprivate.h" -#include "gtkgizmoprivate.h" #include "gtkrendericonprivate.h" #include "gtkstylecontextprivate.h" #include "gtkintl.h" @@ -33,7 +32,6 @@ #include typedef struct _GtkTextHandlePrivate GtkTextHandlePrivate; -typedef struct _HandleWindow HandleWindow; enum { DRAG_STARTED, @@ -42,73 +40,332 @@ enum { LAST_SIGNAL }; -enum { - PROP_0, - PROP_PARENT -}; - -struct _HandleWindow +struct _GtkTextHandlePrivate { - GtkWidget *widget; + GtkWidget *parent; + GdkSurface *surface; + GskRenderer *renderer; + GdkRectangle pointing_to; GtkBorder border; gint dx; gint dy; - GtkTextDirection dir; + guint role : 2; guint dragged : 1; guint mode_visible : 1; guint user_visible : 1; guint has_point : 1; }; -struct _GtkTextHandlePrivate +struct _GtkTextHandle { - HandleWindow windows[2]; - GtkWidget *toplevel; - GtkWidget *parent; - GtkScrollable *parent_scrollable; - GtkAdjustment *vadj; - GtkAdjustment *hadj; - guint hierarchy_changed_id; - guint scrollable_notify_id; - guint mode : 2; + GtkWidget parent_instance; }; -G_DEFINE_TYPE_WITH_PRIVATE (GtkTextHandle, _gtk_text_handle, G_TYPE_OBJECT) +static void gtk_text_handle_native_interface_init (GtkNativeInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (GtkTextHandle, gtk_text_handle, GTK_TYPE_WIDGET, + G_ADD_PRIVATE (GtkTextHandle) + G_IMPLEMENT_INTERFACE (GTK_TYPE_NATIVE, + gtk_text_handle_native_interface_init)) static guint signals[LAST_SIGNAL] = { 0 }; -static void _gtk_text_handle_update (GtkTextHandle *handle, - GtkTextHandlePosition pos); +static GdkSurface * +gtk_text_handle_native_get_surface (GtkNative *native) +{ + GtkTextHandle *handle = GTK_TEXT_HANDLE (native); + GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle); + + return priv->surface; +} + +static GskRenderer * +gtk_text_handle_native_get_renderer (GtkNative *native) +{ + GtkTextHandle *handle = GTK_TEXT_HANDLE (native); + GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle); + + return priv->renderer; +} static void -_gtk_text_handle_get_size (GtkTextHandle *handle, - GtkTextHandlePosition pos, - gint *width, - gint *height) +gtk_text_handle_native_get_surface_transform (GtkNative *native, + int *x, + int *y) { - GtkTextHandlePrivate *priv = handle->priv; - GtkWidget *widget = priv->windows[pos].widget; + GtkCssStyle *style; + + style = gtk_css_node_get_style (gtk_widget_get_css_node (GTK_WIDGET (native))); + *x = _gtk_css_number_value_get (style->size->margin_left, 100) + + _gtk_css_number_value_get (style->border->border_left_width, 100) + + _gtk_css_number_value_get (style->size->padding_left, 100); + *y = _gtk_css_number_value_get (style->size->margin_top, 100) + + _gtk_css_number_value_get (style->border->border_top_width, 100) + + _gtk_css_number_value_get (style->size->padding_top, 100); +} + +static void +gtk_text_handle_get_size (GtkTextHandle *handle, + gint *width, + gint *height) +{ + GtkWidget *widget = GTK_WIDGET (handle); GtkStyleContext *context; context = gtk_widget_get_style_context (widget); - - *width = _gtk_css_number_value_get (_gtk_style_context_peek_property (context, GTK_CSS_PROPERTY_MIN_WIDTH), 100); - *height = _gtk_css_number_value_get (_gtk_style_context_peek_property (context, GTK_CSS_PROPERTY_MIN_HEIGHT), 100); + + if (width) + *width = _gtk_css_number_value_get (_gtk_style_context_peek_property (context, GTK_CSS_PROPERTY_MIN_WIDTH), 100); + if (height) + *height = _gtk_css_number_value_get (_gtk_style_context_peek_property (context, GTK_CSS_PROPERTY_MIN_HEIGHT), 100); } -static gint -_text_handle_pos_from_widget (GtkTextHandle *handle, - GtkWidget *widget) +static void +gtk_text_handle_get_padding (GtkTextHandle *handle, + GtkBorder *border) { - GtkTextHandlePrivate *priv = handle->priv; + GtkWidget *widget = GTK_WIDGET (handle); + GtkStyleContext *context; - if (widget == priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget) - return GTK_TEXT_HANDLE_POSITION_SELECTION_START; - else if (widget == priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget) - return GTK_TEXT_HANDLE_POSITION_SELECTION_END; + context = gtk_widget_get_style_context (widget); + + border->left = _gtk_css_number_value_get (_gtk_style_context_peek_property (context, GTK_CSS_PROPERTY_PADDING_LEFT), 100); + border->right = _gtk_css_number_value_get (_gtk_style_context_peek_property (context, GTK_CSS_PROPERTY_PADDING_RIGHT), 100); + border->top = _gtk_css_number_value_get (_gtk_style_context_peek_property (context, GTK_CSS_PROPERTY_PADDING_TOP), 100); + border->bottom = _gtk_css_number_value_get (_gtk_style_context_peek_property (context, GTK_CSS_PROPERTY_PADDING_BOTTOM), 100); +} + +static void +gtk_text_handle_present_surface (GtkTextHandle *handle) +{ + GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle); + GtkWidget *widget = GTK_WIDGET (handle); + GdkPopupLayout *layout; + GdkRectangle rect; + GtkRequisition req; + + gtk_widget_get_preferred_size (widget, NULL, &req); + gtk_text_handle_get_padding (handle, &priv->border); + + rect.x = priv->pointing_to.x; + rect.y = priv->pointing_to.y + priv->pointing_to.height - priv->border.top; + rect.width = req.width - priv->border.left - priv->border.right; + rect.height = 1; + + gtk_widget_translate_coordinates (gtk_widget_get_parent (widget), + gtk_widget_get_ancestor (widget, GTK_TYPE_WINDOW), + rect.x, rect.y, &rect.x, &rect.y); + + if (priv->role == GTK_TEXT_HANDLE_ROLE_CURSOR) + rect.x -= rect.width / 2; + else if ((priv->role == GTK_TEXT_HANDLE_ROLE_SELECTION_END && + gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) || + (priv->role == GTK_TEXT_HANDLE_ROLE_SELECTION_START && + gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL)) + rect.x -= rect.width; + + layout = gdk_popup_layout_new (&rect, + GDK_GRAVITY_SOUTH, + GDK_GRAVITY_NORTH); + gdk_popup_layout_set_anchor_hints (layout, + GDK_ANCHOR_FLIP_Y | GDK_ANCHOR_SLIDE_X); + + gdk_popup_present (GDK_POPUP (priv->surface), + MAX (req.width, 1), + MAX (req.height, 1), + layout); + gdk_popup_layout_unref (layout); + + gtk_widget_allocate (widget, + gdk_surface_get_width (priv->surface), + gdk_surface_get_height (priv->surface), + -1, NULL); +} + +static void +gtk_text_handle_native_check_resize (GtkNative *native) +{ + GtkTextHandle *handle = GTK_TEXT_HANDLE (native); + GtkWidget *widget = GTK_WIDGET (native); + + if (!_gtk_widget_get_alloc_needed (widget)) + gtk_widget_ensure_allocate (widget); + else if (gtk_widget_get_visible (widget)) + gtk_text_handle_present_surface (handle); +} + +static void +gtk_text_handle_native_interface_init (GtkNativeInterface *iface) +{ + iface->get_surface = gtk_text_handle_native_get_surface; + iface->get_renderer = gtk_text_handle_native_get_renderer; + iface->get_surface_transform = gtk_text_handle_native_get_surface_transform; + iface->check_resize = gtk_text_handle_native_check_resize; +} + +static gboolean +surface_render (GdkSurface *surface, + cairo_region_t *region, + GtkTextHandle *handle) +{ + gtk_widget_render (GTK_WIDGET (handle), surface, region); + return TRUE; +} + +static gboolean +surface_event (GdkSurface *surface, + GdkEvent *event, + GtkTextHandle *handle) +{ + gtk_main_do_event (event); + return TRUE; +} + +static void +surface_mapped_changed (GtkWidget *widget) +{ + GtkTextHandle *handle = GTK_TEXT_HANDLE (widget); + GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle); + + gtk_widget_set_visible (widget, gdk_surface_get_mapped (priv->surface)); +} + +static void +gtk_text_handle_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + GtkCssStyle *style = gtk_css_node_get_style (gtk_widget_get_css_node (widget)); + + gtk_css_style_snapshot_icon (style, + snapshot, + gtk_widget_get_width (widget), + gtk_widget_get_height (widget)); +} + +static void +gtk_text_handle_realize (GtkWidget *widget) +{ + GtkTextHandle *handle = GTK_TEXT_HANDLE (widget); + GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle); + GdkSurface *parent_surface; + GtkWidget *parent; + + parent = gtk_widget_get_parent (widget); + parent_surface = gtk_native_get_surface (gtk_widget_get_native (parent)); + + priv->surface = gdk_surface_new_popup (parent_surface, FALSE); + gdk_surface_set_widget (priv->surface, widget); + + g_signal_connect_swapped (priv->surface, "notify::mapped", + G_CALLBACK (surface_mapped_changed), widget); + g_signal_connect (priv->surface, "render", G_CALLBACK (surface_render), widget); + g_signal_connect (priv->surface, "event", G_CALLBACK (surface_event), widget); + + GTK_WIDGET_CLASS (gtk_text_handle_parent_class)->realize (widget); + + priv->renderer = gsk_renderer_new_for_surface (priv->surface); +} + +static void +gtk_text_handle_unrealize (GtkWidget *widget) +{ + GtkTextHandle *handle = GTK_TEXT_HANDLE (widget); + GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle); + + GTK_WIDGET_CLASS (gtk_text_handle_parent_class)->unrealize (widget); + + gsk_renderer_unrealize (priv->renderer); + g_clear_object (&priv->renderer); + + g_signal_handlers_disconnect_by_func (priv->surface, surface_render, widget); + g_signal_handlers_disconnect_by_func (priv->surface, surface_event, widget); + g_signal_handlers_disconnect_by_func (priv->surface, surface_mapped_changed, widget); + + gdk_surface_set_widget (priv->surface, NULL); + gdk_surface_destroy (priv->surface); + g_clear_object (&priv->surface); +} + +static void +gtk_text_handle_map (GtkWidget *widget) +{ + GtkTextHandle *handle = GTK_TEXT_HANDLE (widget); + GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle); + + GTK_WIDGET_CLASS (gtk_text_handle_parent_class)->map (widget); + + if (priv->has_point) + gtk_text_handle_present_surface (handle); +} + +static void +gtk_text_handle_unmap (GtkWidget *widget) +{ + GtkTextHandle *handle = GTK_TEXT_HANDLE (widget); + GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle); + + GTK_WIDGET_CLASS (gtk_text_handle_parent_class)->unmap (widget); + gdk_surface_hide (priv->surface); +} + +static void +gtk_text_handle_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + gint size; + + if (orientation == GTK_ORIENTATION_VERTICAL) + gtk_text_handle_get_size (GTK_TEXT_HANDLE (widget), NULL, &size); else - return -1; + gtk_text_handle_get_size (GTK_TEXT_HANDLE (widget), &size, NULL); + + *natural = size; + *minimum = size; + *minimum_baseline = -1; + *natural_baseline = -1; +} + +static void +gtk_text_handle_class_init (GtkTextHandleClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + widget_class->snapshot = gtk_text_handle_snapshot; + widget_class->realize = gtk_text_handle_realize; + widget_class->unrealize = gtk_text_handle_unrealize; + widget_class->map = gtk_text_handle_map; + widget_class->unmap = gtk_text_handle_unmap; + widget_class->measure = gtk_text_handle_measure; + + signals[HANDLE_DRAGGED] = + g_signal_new (I_("handle-dragged"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, 0, + NULL, NULL, + _gtk_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, + G_TYPE_INT, G_TYPE_INT); + signals[DRAG_STARTED] = + g_signal_new (I_("drag-started"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, 0, + NULL, NULL, + NULL, + G_TYPE_NONE, 0, G_TYPE_NONE); + signals[DRAG_FINISHED] = + g_signal_new (I_("drag-finished"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, 0, + NULL, NULL, + NULL, + G_TYPE_NONE, 0, G_TYPE_NONE); } static void @@ -117,49 +374,54 @@ handle_drag_begin (GtkGestureDrag *gesture, gdouble y, GtkTextHandle *handle) { - GtkTextHandlePrivate *priv = handle->priv; + GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle); GtkWidget *widget; - gint pos; widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); - pos = _text_handle_pos_from_widget (handle, widget); - if (pos == GTK_TEXT_HANDLE_POSITION_CURSOR && - priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR) + if (priv->role == GTK_TEXT_HANDLE_ROLE_CURSOR) x -= gtk_widget_get_width (widget) / 2; - else if ((pos == GTK_TEXT_HANDLE_POSITION_CURSOR && - priv->windows[pos].dir == GTK_TEXT_DIR_RTL) || - (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_START && - priv->windows[pos].dir != GTK_TEXT_DIR_RTL)) + else if ((priv->role == GTK_TEXT_HANDLE_ROLE_SELECTION_END && + gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) || + (priv->role == GTK_TEXT_HANDLE_ROLE_SELECTION_START && + gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL)) x -= gtk_widget_get_width (widget); - y += priv->windows[pos].border.top / 2; + y += priv->border.top / 2; - priv->windows[pos].dx = x; - priv->windows[pos].dy = y; - priv->windows[pos].dragged = TRUE; - g_signal_emit (handle, signals[DRAG_STARTED], 0, pos); + priv->dx = x; + priv->dy = y; + priv->dragged = TRUE; + g_signal_emit (handle, signals[DRAG_STARTED], 0); } static void handle_drag_update (GtkGestureDrag *gesture, gdouble offset_x, gdouble offset_y, - GtkTextHandle *handle) + GtkWidget *widget) { - GtkTextHandlePrivate *priv = handle->priv; + GtkTextHandle *text_handle = GTK_TEXT_HANDLE (widget); + GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (text_handle); gdouble start_x, start_y; - gint pos, x, y; + gint x, y; - pos = _text_handle_pos_from_widget (handle, - gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture))); gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y); - gtk_widget_translate_coordinates (priv->windows[pos].widget, priv->parent, - start_x + offset_x - priv->windows[pos].dx, - start_y + offset_y - priv->windows[pos].dy, - &x, &y); - g_signal_emit (handle, signals[HANDLE_DRAGGED], 0, pos, x, y); + x = priv->pointing_to.x + priv->pointing_to.width / 2 + + start_x + offset_x - priv->dx; + y = priv->pointing_to.y + priv->pointing_to.height + + start_y + offset_y - priv->dy; + + if (priv->role == GTK_TEXT_HANDLE_ROLE_CURSOR) + x -= gtk_widget_get_width (widget) / 2; + else if ((priv->role == GTK_TEXT_HANDLE_ROLE_SELECTION_END && + gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) || + (priv->role == GTK_TEXT_HANDLE_ROLE_SELECTION_START && + gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL)) + x -= gtk_widget_get_width (widget); + + g_signal_emit (widget, signals[HANDLE_DRAGGED], 0, x, y); } static void @@ -168,629 +430,126 @@ handle_drag_end (GtkGestureDrag *gesture, gdouble offset_y, GtkTextHandle *handle) { - GtkTextHandlePrivate *priv = handle->priv; - gint pos; + GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle); - pos = _text_handle_pos_from_widget (handle, - gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture))); - g_signal_emit (handle, signals[DRAG_FINISHED], 0, pos); - priv->windows[pos].dragged = FALSE; + g_signal_emit (handle, signals[DRAG_FINISHED], 0); + priv->dragged = FALSE; } static void -snapshot_func (GtkGizmo *gizmo, - GtkSnapshot *snapshot) +gtk_text_handle_update_for_role (GtkTextHandle *handle) { - GtkCssStyle *style = gtk_css_node_get_style (gtk_widget_get_css_node (GTK_WIDGET (gizmo))); + GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle); + GtkWidget *widget = GTK_WIDGET (handle); - gtk_css_style_snapshot_icon (style, - snapshot, - gtk_widget_get_width (GTK_WIDGET (gizmo)), - gtk_widget_get_height (GTK_WIDGET (gizmo))); -} - -static GtkWidget * -_gtk_text_handle_ensure_widget (GtkTextHandle *handle, - GtkTextHandlePosition pos) -{ - GtkTextHandlePrivate *priv; - - priv = handle->priv; - - if (!priv->windows[pos].widget) + if (priv->role == GTK_TEXT_HANDLE_ROLE_CURSOR) { - GtkWidget *widget, *window; - GtkEventController *controller; - - widget = gtk_gizmo_new (I_("cursor-handle"), NULL, NULL, snapshot_func, NULL); - - gtk_widget_set_direction (widget, priv->windows[pos].dir); - - controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ()); - g_signal_connect (controller, "drag-begin", - G_CALLBACK (handle_drag_begin), handle); - g_signal_connect (controller, "drag-update", - G_CALLBACK (handle_drag_update), handle); - g_signal_connect (controller, "drag-end", - G_CALLBACK (handle_drag_end), handle); - gtk_widget_add_controller (widget, controller); - - priv->windows[pos].widget = g_object_ref_sink (widget); - priv->toplevel = window = gtk_widget_get_ancestor (priv->parent, GTK_TYPE_WINDOW); - _gtk_window_add_popover (GTK_WINDOW (window), widget, priv->parent, FALSE); - - if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_END) - { - gtk_widget_add_css_class (widget, GTK_STYLE_CLASS_BOTTOM); - if (priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR) - gtk_widget_add_css_class (widget, GTK_STYLE_CLASS_INSERTION_CURSOR); - } - else - gtk_widget_add_css_class (widget, GTK_STYLE_CLASS_TOP); + gtk_widget_remove_css_class (widget, GTK_STYLE_CLASS_TOP); + gtk_widget_add_css_class (widget, GTK_STYLE_CLASS_BOTTOM); + gtk_widget_add_css_class (widget, GTK_STYLE_CLASS_INSERTION_CURSOR); } - - return priv->windows[pos].widget; -} - -static void -_handle_update_child_visible (GtkTextHandle *handle, - GtkTextHandlePosition pos) -{ - HandleWindow *handle_window; - GtkTextHandlePrivate *priv; - cairo_rectangle_int_t rect; - GtkAllocation allocation; - GtkWidget *parent; - - priv = handle->priv; - handle_window = &priv->windows[pos]; - - if (!priv->parent_scrollable) + else if (priv->role == GTK_TEXT_HANDLE_ROLE_SELECTION_END) { - gtk_widget_set_child_visible (handle_window->widget, TRUE); - return; + gtk_widget_remove_css_class (widget, GTK_STYLE_CLASS_TOP); + gtk_widget_add_css_class (widget, GTK_STYLE_CLASS_BOTTOM); + gtk_widget_remove_css_class (widget, GTK_STYLE_CLASS_INSERTION_CURSOR); } - - parent = gtk_widget_get_parent (GTK_WIDGET (priv->parent_scrollable)); - rect = handle_window->pointing_to; - - gtk_widget_translate_coordinates (priv->parent, parent, - rect.x, rect.y, &rect.x, &rect.y); - - gtk_widget_get_allocation (GTK_WIDGET (parent), &allocation); - - if (rect.x < 0 || rect.x + rect.width > allocation.width || - rect.y < 0 || rect.y + rect.height > allocation.height) - gtk_widget_set_child_visible (handle_window->widget, FALSE); - else - gtk_widget_set_child_visible (handle_window->widget, TRUE); -} - -static void -_gtk_text_handle_update (GtkTextHandle *handle, - GtkTextHandlePosition pos) -{ - GtkTextHandlePrivate *priv; - HandleWindow *handle_window; - GtkBorder *border; - - priv = handle->priv; - handle_window = &priv->windows[pos]; - border = &handle_window->border; - - if (!priv->parent || !gtk_widget_is_drawable (priv->parent)) - return; - - if (handle_window->has_point && - handle_window->mode_visible && handle_window->user_visible) + else if (priv->role == GTK_TEXT_HANDLE_ROLE_SELECTION_START) { - cairo_rectangle_int_t rect; - gint width, height; - GtkWidget *window; - GtkAllocation alloc; - gint w, h; - - _gtk_text_handle_ensure_widget (handle, pos); - _gtk_text_handle_get_size (handle, pos, &width, &height); - - border->top = height; - border->bottom = height; - border->left = width; - border->right = width; - - rect.x = handle_window->pointing_to.x; - rect.y = handle_window->pointing_to.y + handle_window->pointing_to.height - handle_window->border.top; - rect.width = width; - rect.height = 0; - - _handle_update_child_visible (handle, pos); - - window = gtk_widget_get_parent (handle_window->widget); - gtk_widget_translate_coordinates (priv->parent, window, - rect.x, rect.y, &rect.x, &rect.y); - - if (pos == GTK_TEXT_HANDLE_POSITION_CURSOR && - priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR) - rect.x -= rect.width / 2; - else if ((pos == GTK_TEXT_HANDLE_POSITION_CURSOR && - handle_window->dir == GTK_TEXT_DIR_RTL) || - (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_START && - handle_window->dir != GTK_TEXT_DIR_RTL)) - rect.x -= rect.width; - - /* The goal is to make the window 3 times as wide and high. The handle - * will be rendered in the center, making the rest an invisible border. - * If we hit the edge of the toplevel, we shrink the border to avoid - * mispositioning the handle, if at all possible. This calculation uses - * knowledge about how popover_get_rect() works. - */ - - gtk_widget_get_allocation (window, &alloc); - - w = width + border->left + border->right; - h = height + border->top + border->bottom; - - if (rect.x + rect.width/2 - w/2 < alloc.x) - border->left = MAX (0, border->left - (alloc.x - (rect.x + rect.width/2 - w/2))); - if (rect.y + rect.height/2 - h/2 < alloc.y) - border->top = MAX (0, border->top - (alloc.y - (rect.y + rect.height/2 - h/2))); - if (rect.x + rect.width/2 + w/2 > alloc.x + alloc.width) - border->right = MAX (0, border->right - (rect.x + rect.width/2 + w/2 - (alloc.x + alloc.width))); - if (rect.y + rect.height/2 + h/2 > alloc.y + alloc.height) - border->bottom = MAX (0, border->bottom - (rect.y + rect.height/2 + h/2 - (alloc.y + alloc.height))); - - width += border->left + border->right; - height += border->top + border->bottom; - - gtk_widget_set_size_request (handle_window->widget, width, height); - gtk_widget_show (handle_window->widget); - _gtk_window_raise_popover (GTK_WINDOW (window), handle_window->widget); - _gtk_window_set_popover_position (GTK_WINDOW (window), - handle_window->widget, - GTK_POS_BOTTOM, &rect); - } - else if (handle_window->widget) - gtk_widget_hide (handle_window->widget); -} - -static void -adjustment_changed_cb (GtkAdjustment *adjustment, - GtkTextHandle *handle) -{ - _gtk_text_handle_update (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START); - _gtk_text_handle_update (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END); -} - -static void -_gtk_text_handle_set_scrollable (GtkTextHandle *handle, - GtkScrollable *scrollable) -{ - GtkTextHandlePrivate *priv; - - priv = handle->priv; - - if (priv->vadj) - { - g_signal_handlers_disconnect_by_data (priv->vadj, handle); - g_clear_object (&priv->vadj); - } - - if (priv->hadj) - { - g_signal_handlers_disconnect_by_data (priv->hadj, handle); - g_clear_object (&priv->hadj); - } - - if (priv->parent_scrollable) - g_object_remove_weak_pointer (G_OBJECT (priv->parent_scrollable), (gpointer *) &priv->parent_scrollable); - - priv->parent_scrollable = scrollable; - - if (scrollable) - { - g_object_add_weak_pointer (G_OBJECT (priv->parent_scrollable), (gpointer *) &priv->parent_scrollable); - - priv->vadj = gtk_scrollable_get_vadjustment (scrollable); - priv->hadj = gtk_scrollable_get_hadjustment (scrollable); - - if (priv->vadj) - { - g_object_ref (priv->vadj); - g_signal_connect (priv->vadj, "changed", - G_CALLBACK (adjustment_changed_cb), handle); - g_signal_connect (priv->vadj, "value-changed", - G_CALLBACK (adjustment_changed_cb), handle); - } - - if (priv->hadj) - { - g_object_ref (priv->hadj); - g_signal_connect (priv->hadj, "changed", - G_CALLBACK (adjustment_changed_cb), handle); - g_signal_connect (priv->hadj, "value-changed", - G_CALLBACK (adjustment_changed_cb), handle); - } + gtk_widget_add_css_class (widget, GTK_STYLE_CLASS_TOP); + gtk_widget_remove_css_class (widget, GTK_STYLE_CLASS_BOTTOM); + gtk_widget_remove_css_class (widget, GTK_STYLE_CLASS_INSERTION_CURSOR); } } static void -_gtk_text_handle_scrollable_notify (GObject *object, - GParamSpec *pspec, - GtkTextHandle *handle) +gtk_text_handle_init (GtkTextHandle *widget) { - if (pspec->value_type == GTK_TYPE_ADJUSTMENT) - _gtk_text_handle_set_scrollable (handle, GTK_SCROLLABLE (object)); -} + GtkEventController *controller; -static void -_gtk_text_handle_update_scrollable (GtkTextHandle *handle, - GtkScrollable *scrollable) -{ - GtkTextHandlePrivate *priv; + controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ()); + g_signal_connect (controller, "drag-begin", + G_CALLBACK (handle_drag_begin), widget); + g_signal_connect (controller, "drag-update", + G_CALLBACK (handle_drag_update), widget); + g_signal_connect (controller, "drag-end", + G_CALLBACK (handle_drag_end), widget); + gtk_widget_add_controller (GTK_WIDGET (widget), controller); - priv = handle->priv; - - if (priv->parent_scrollable == scrollable) - return; - - if (priv->parent_scrollable && priv->scrollable_notify_id && - g_signal_handler_is_connected (priv->parent_scrollable, - priv->scrollable_notify_id)) - g_signal_handler_disconnect (priv->parent_scrollable, - priv->scrollable_notify_id); - - _gtk_text_handle_set_scrollable (handle, scrollable); - - if (priv->parent_scrollable) - priv->scrollable_notify_id = - g_signal_connect (priv->parent_scrollable, "notify", - G_CALLBACK (_gtk_text_handle_scrollable_notify), - handle); -} - -static GtkWidget * -gtk_text_handle_lookup_scrollable (GtkTextHandle *handle) -{ - GtkTextHandlePrivate *priv; - GtkWidget *scrolled_window; - - priv = handle->priv; - scrolled_window = gtk_widget_get_ancestor (priv->parent, - GTK_TYPE_SCROLLED_WINDOW); - if (!scrolled_window) - return NULL; - - return gtk_bin_get_child (GTK_BIN (scrolled_window)); -} - -static void -_gtk_text_handle_parent_hierarchy_changed (GtkWidget *widget, - GParamSpec *pspec, - GtkTextHandle *handle) -{ - GtkWidget *toplevel, *scrollable; - GtkTextHandlePrivate *priv; - - priv = handle->priv; - toplevel = gtk_widget_get_ancestor (widget, GTK_TYPE_WINDOW); - - if (priv->toplevel && !toplevel) - { - if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget) - { - _gtk_window_remove_popover (GTK_WINDOW (priv->toplevel), - priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget); - g_object_unref (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget); - priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget = NULL; - } - - if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget) - { - _gtk_window_remove_popover (GTK_WINDOW (priv->toplevel), - priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget); - g_object_unref (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget); - priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget = NULL; - } - - priv->toplevel = NULL; - } - - scrollable = gtk_text_handle_lookup_scrollable (handle); - _gtk_text_handle_update_scrollable (handle, GTK_SCROLLABLE (scrollable)); -} - -static void -_gtk_text_handle_set_parent (GtkTextHandle *handle, - GtkWidget *parent) -{ - GtkTextHandlePrivate *priv; - GtkWidget *scrollable = NULL; - - priv = handle->priv; - - if (priv->parent == parent) - return; - - if (priv->parent && priv->hierarchy_changed_id && - g_signal_handler_is_connected (priv->parent, priv->hierarchy_changed_id)) - g_signal_handler_disconnect (priv->parent, priv->hierarchy_changed_id); - - priv->parent = parent; - - if (parent) - { - priv->hierarchy_changed_id = - g_signal_connect (parent, "notify::root", - G_CALLBACK (_gtk_text_handle_parent_hierarchy_changed), - handle); - - scrollable = gtk_text_handle_lookup_scrollable (handle); - } - - _gtk_text_handle_update_scrollable (handle, GTK_SCROLLABLE (scrollable)); -} - -static void -gtk_text_handle_finalize (GObject *object) -{ - GtkTextHandlePrivate *priv; - - priv = GTK_TEXT_HANDLE (object)->priv; - - _gtk_text_handle_set_parent (GTK_TEXT_HANDLE (object), NULL); - - /* We sank the references, unref here */ - if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget) - g_object_unref (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget); - - if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget) - g_object_unref (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget); - - G_OBJECT_CLASS (_gtk_text_handle_parent_class)->finalize (object); -} - -static void -gtk_text_handle_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - GtkTextHandle *handle; - - handle = GTK_TEXT_HANDLE (object); - - switch (prop_id) - { - case PROP_PARENT: - _gtk_text_handle_set_parent (handle, g_value_get_object (value)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -gtk_text_handle_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - GtkTextHandlePrivate *priv; - - priv = GTK_TEXT_HANDLE (object)->priv; - - switch (prop_id) - { - case PROP_PARENT: - g_value_set_object (value, priv->parent); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -_gtk_text_handle_class_init (GtkTextHandleClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = gtk_text_handle_finalize; - object_class->set_property = gtk_text_handle_set_property; - object_class->get_property = gtk_text_handle_get_property; - - signals[HANDLE_DRAGGED] = - g_signal_new (I_("handle-dragged"), - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GtkTextHandleClass, handle_dragged), - NULL, NULL, - _gtk_marshal_VOID__ENUM_INT_INT, - G_TYPE_NONE, 3, - GTK_TYPE_TEXT_HANDLE_POSITION, - G_TYPE_INT, G_TYPE_INT); - signals[DRAG_STARTED] = - g_signal_new (I_("drag-started"), - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_LAST, 0, - NULL, NULL, - NULL, - G_TYPE_NONE, 1, - GTK_TYPE_TEXT_HANDLE_POSITION); - signals[DRAG_FINISHED] = - g_signal_new (I_("drag-finished"), - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_LAST, 0, - NULL, NULL, - NULL, - G_TYPE_NONE, 1, - GTK_TYPE_TEXT_HANDLE_POSITION); - - g_object_class_install_property (object_class, - PROP_PARENT, - g_param_spec_object ("parent", - P_("Parent widget"), - P_("Parent widget"), - GTK_TYPE_WIDGET, - GTK_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY)); -} - -static void -_gtk_text_handle_init (GtkTextHandle *handle) -{ - handle->priv = _gtk_text_handle_get_instance_private (handle); + gtk_text_handle_update_for_role (GTK_TEXT_HANDLE (widget)); } GtkTextHandle * -_gtk_text_handle_new (GtkWidget *parent) +gtk_text_handle_new (GtkWidget *parent) { - return g_object_new (GTK_TYPE_TEXT_HANDLE, - "parent", parent, - NULL); + GtkWidget *handle; + + handle = g_object_new (GTK_TYPE_TEXT_HANDLE, + "css-name", I_("cursor-handle"), + NULL); + gtk_widget_set_parent (handle, parent); + + return GTK_TEXT_HANDLE (handle); } void -_gtk_text_handle_set_mode (GtkTextHandle *handle, - GtkTextHandleMode mode) +gtk_text_handle_set_role (GtkTextHandle *handle, + GtkTextHandleRole role) { - GtkTextHandlePrivate *priv; - HandleWindow *start, *end; + GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle); g_return_if_fail (GTK_IS_TEXT_HANDLE (handle)); - priv = handle->priv; - - if (priv->mode == mode) + if (priv->role == role) return; - priv->mode = mode; - start = &priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START]; - end = &priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END]; + priv->role = role; + gtk_text_handle_update_for_role (handle); - switch (mode) + if (gtk_widget_get_visible (GTK_WIDGET (handle))) { - case GTK_TEXT_HANDLE_MODE_CURSOR: - start->mode_visible = FALSE; - /* end = cursor */ - end->mode_visible = TRUE; - break; - case GTK_TEXT_HANDLE_MODE_SELECTION: - start->mode_visible = TRUE; - end->mode_visible = TRUE; - break; - case GTK_TEXT_HANDLE_MODE_NONE: - default: - start->mode_visible = FALSE; - end->mode_visible = FALSE; - break; + if (priv->has_point) + gtk_text_handle_present_surface (handle); } - - if (end->widget) - { - if (mode == GTK_TEXT_HANDLE_MODE_CURSOR) - gtk_style_context_add_class (gtk_widget_get_style_context (end->widget), GTK_STYLE_CLASS_INSERTION_CURSOR); - else - gtk_style_context_remove_class (gtk_widget_get_style_context (end->widget), GTK_STYLE_CLASS_INSERTION_CURSOR); - } - - _gtk_text_handle_update (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START); - _gtk_text_handle_update (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END); - - if (start->widget && start->mode_visible) - gtk_widget_queue_draw (start->widget); - if (end->widget && end->mode_visible) - gtk_widget_queue_draw (end->widget); } -GtkTextHandleMode -_gtk_text_handle_get_mode (GtkTextHandle *handle) +GtkTextHandleRole +gtk_text_handle_get_role (GtkTextHandle *handle) { - GtkTextHandlePrivate *priv; + GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle); - g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), GTK_TEXT_HANDLE_MODE_NONE); + g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), GTK_TEXT_HANDLE_ROLE_CURSOR); - priv = handle->priv; - return priv->mode; + return priv->role; } void -_gtk_text_handle_set_position (GtkTextHandle *handle, - GtkTextHandlePosition pos, - GdkRectangle *rect) +gtk_text_handle_set_position (GtkTextHandle *handle, + const GdkRectangle *rect) { - GtkTextHandlePrivate *priv; - HandleWindow *handle_window; + GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle); g_return_if_fail (GTK_IS_TEXT_HANDLE (handle)); - priv = handle->priv; - pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR, - GTK_TEXT_HANDLE_POSITION_SELECTION_START); - handle_window = &priv->windows[pos]; - - if (priv->mode == GTK_TEXT_HANDLE_MODE_NONE || - (priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR && - pos != GTK_TEXT_HANDLE_POSITION_CURSOR)) + if (priv->pointing_to.x == rect->x && + priv->pointing_to.y == rect->y && + priv->pointing_to.width == rect->width && + priv->pointing_to.height == rect->height) return; - handle_window->pointing_to = *rect; - handle_window->has_point = TRUE; + priv->pointing_to = *rect; + priv->has_point = TRUE; - if (gtk_widget_is_visible (priv->parent)) - _gtk_text_handle_update (handle, pos); -} - -void -_gtk_text_handle_set_visible (GtkTextHandle *handle, - GtkTextHandlePosition pos, - gboolean visible) -{ - GtkTextHandlePrivate *priv; - - g_return_if_fail (GTK_IS_TEXT_HANDLE (handle)); - - priv = handle->priv; - pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR, - GTK_TEXT_HANDLE_POSITION_SELECTION_START); - - priv->windows[pos].user_visible = visible; - - if (gtk_widget_is_visible (priv->parent)) - _gtk_text_handle_update (handle, pos); + if (gtk_widget_is_visible (GTK_WIDGET (handle))) + gtk_text_handle_present_surface (handle); } gboolean -_gtk_text_handle_get_is_dragged (GtkTextHandle *handle, - GtkTextHandlePosition pos) +gtk_text_handle_get_is_dragged (GtkTextHandle *handle) { - GtkTextHandlePrivate *priv; + GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle); g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), FALSE); - priv = handle->priv; - pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR, - GTK_TEXT_HANDLE_POSITION_SELECTION_START); - - return priv->windows[pos].dragged; -} - -void -_gtk_text_handle_set_direction (GtkTextHandle *handle, - GtkTextHandlePosition pos, - GtkTextDirection dir) -{ - GtkTextHandlePrivate *priv; - - g_return_if_fail (GTK_IS_TEXT_HANDLE (handle)); - - priv = handle->priv; - pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR, - GTK_TEXT_HANDLE_POSITION_SELECTION_START); - priv->windows[pos].dir = dir; - - if (priv->windows[pos].widget) - { - gtk_widget_set_direction (priv->windows[pos].widget, dir); - _gtk_text_handle_update (handle, pos); - } + return priv->dragged; } diff --git a/gtk/gtktexthandleprivate.h b/gtk/gtktexthandleprivate.h index c79aa04cab..0a02eac12e 100644 --- a/gtk/gtktexthandleprivate.h +++ b/gtk/gtktexthandleprivate.h @@ -22,68 +22,27 @@ G_BEGIN_DECLS -#define GTK_TYPE_TEXT_HANDLE (_gtk_text_handle_get_type ()) -#define GTK_TEXT_HANDLE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_TEXT_HANDLE, GtkTextHandle)) -#define GTK_TEXT_HANDLE_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), GTK_TYPE_TEXT_HANDLE, GtkTextHandleClass)) -#define GTK_IS_TEXT_HANDLE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_TEXT_HANDLE)) -#define GTK_IS_TEXT_HANDLE_CLASS(o) (G_TYPE_CHECK_CLASS_TYPE ((o), GTK_TYPE_TEXT_HANDLE)) -#define GTK_TEXT_HANDLE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_TEXT_HANDLE, GtkTextHandleClass)) - -typedef struct _GtkTextHandle GtkTextHandle; -typedef struct _GtkTextHandleClass GtkTextHandleClass; +#define GTK_TYPE_TEXT_HANDLE (gtk_text_handle_get_type ()) +G_DECLARE_FINAL_TYPE (GtkTextHandle, gtk_text_handle, + GTK, TEXT_HANDLE, GtkWidget) typedef enum { - GTK_TEXT_HANDLE_POSITION_CURSOR, - GTK_TEXT_HANDLE_POSITION_SELECTION_START, - GTK_TEXT_HANDLE_POSITION_SELECTION_END = GTK_TEXT_HANDLE_POSITION_CURSOR -} GtkTextHandlePosition; + GTK_TEXT_HANDLE_ROLE_CURSOR, + GTK_TEXT_HANDLE_ROLE_SELECTION_START, + GTK_TEXT_HANDLE_ROLE_SELECTION_END, +} GtkTextHandleRole; -typedef enum -{ - GTK_TEXT_HANDLE_MODE_NONE, - GTK_TEXT_HANDLE_MODE_CURSOR, - GTK_TEXT_HANDLE_MODE_SELECTION -} GtkTextHandleMode; +GtkTextHandle * gtk_text_handle_new (GtkWidget *parent); -struct _GtkTextHandle -{ - GObject parent_instance; - gpointer priv; -}; +void gtk_text_handle_set_role (GtkTextHandle *handle, + GtkTextHandleRole role); +GtkTextHandleRole gtk_text_handle_get_role (GtkTextHandle *handle); -struct _GtkTextHandleClass -{ - GObjectClass parent_class; +void gtk_text_handle_set_position (GtkTextHandle *handle, + const GdkRectangle *rect); - void (* handle_dragged) (GtkTextHandle *handle, - GtkTextHandlePosition pos, - gint x, - gint y); - void (* drag_finished) (GtkTextHandle *handle, - GtkTextHandlePosition pos); -}; - -GType _gtk_text_handle_get_type (void) G_GNUC_CONST; - -GtkTextHandle * _gtk_text_handle_new (GtkWidget *parent); - -void _gtk_text_handle_set_mode (GtkTextHandle *handle, - GtkTextHandleMode mode); -GtkTextHandleMode - _gtk_text_handle_get_mode (GtkTextHandle *handle); -void _gtk_text_handle_set_position (GtkTextHandle *handle, - GtkTextHandlePosition pos, - GdkRectangle *rect); -void _gtk_text_handle_set_visible (GtkTextHandle *handle, - GtkTextHandlePosition pos, - gboolean visible); - -gboolean _gtk_text_handle_get_is_dragged (GtkTextHandle *handle, - GtkTextHandlePosition pos); -void _gtk_text_handle_set_direction (GtkTextHandle *handle, - GtkTextHandlePosition pos, - GtkTextDirection dir); +gboolean gtk_text_handle_get_is_dragged (GtkTextHandle *handle); G_END_DECLS diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c index 62687d0f29..827b0c2b45 100644 --- a/gtk/gtktextview.c +++ b/gtk/gtktextview.c @@ -149,6 +149,13 @@ typedef struct _GtkTextWindow GtkTextWindow; typedef struct _GtkTextPendingScroll GtkTextPendingScroll; +enum +{ + TEXT_HANDLE_CURSOR, + TEXT_HANDLE_SELECTION_BOUND, + TEXT_HANDLE_N_HANDLES +}; + struct _GtkTextViewPrivate { GtkTextLayout *layout; @@ -161,7 +168,7 @@ struct _GtkTextViewPrivate gint dnd_x; gint dnd_y; - GtkTextHandle *text_handle; + GtkTextHandle *text_handles[TEXT_HANDLE_N_HANDLES]; GtkWidget *selection_bubble; guint selection_bubble_timeout_id; @@ -273,6 +280,8 @@ struct _GtkTextViewPrivate guint scroll_after_paste : 1; + guint text_handles_enabled : 1; + /* GtkScrollablePolicy needs to be checked when * driving the scrollable adjustment values */ guint hscroll_policy : 1; @@ -565,18 +574,14 @@ static void update_node_ordering (GtkWidget *widget); /* GtkTextHandle handlers */ static void gtk_text_view_handle_drag_started (GtkTextHandle *handle, - GtkTextHandlePosition pos, GtkTextView *text_view); static void gtk_text_view_handle_dragged (GtkTextHandle *handle, - GtkTextHandlePosition pos, gint x, gint y, GtkTextView *text_view); static void gtk_text_view_handle_drag_finished (GtkTextHandle *handle, - GtkTextHandlePosition pos, GtkTextView *text_view); -static void gtk_text_view_update_handles (GtkTextView *text_view, - GtkTextHandleMode mode); +static void gtk_text_view_update_handles (GtkTextView *text_view); static void gtk_text_view_selection_bubble_popup_unset (GtkTextView *text_view); static void gtk_text_view_selection_bubble_popup_set (GtkTextView *text_view); @@ -1719,6 +1724,26 @@ gtk_text_view_class_init (GtkTextViewClass *klass) gtk_text_view_activate_misc_insert_emoji); } +static void +_gtk_text_view_ensure_text_handles (GtkTextView *text_view) +{ + GtkTextViewPrivate *priv = text_view->priv; + int i; + + for (i = 0; i < TEXT_HANDLE_N_HANDLES; i++) + { + if (priv->text_handles[i]) + continue; + priv->text_handles[i] = gtk_text_handle_new (GTK_WIDGET (text_view)); + g_signal_connect (priv->text_handles[i], "drag-started", + G_CALLBACK (gtk_text_view_handle_drag_started), text_view); + g_signal_connect (priv->text_handles[i], "handle-dragged", + G_CALLBACK (gtk_text_view_handle_dragged), text_view); + g_signal_connect (priv->text_handles[i], "drag-finished", + G_CALLBACK (gtk_text_view_handle_drag_finished), text_view); + } +} + static void gtk_text_view_init (GtkTextView *text_view) { @@ -1848,23 +1873,6 @@ gtk_text_view_get_selection_node (GtkTextView *text_view) return text_view->priv->selection_node; } -static void -_gtk_text_view_ensure_text_handles (GtkTextView *text_view) -{ - GtkTextViewPrivate *priv = text_view->priv; - - if (priv->text_handle) - return; - - priv->text_handle = _gtk_text_handle_new (GTK_WIDGET (text_view)); - g_signal_connect (priv->text_handle, "drag-started", - G_CALLBACK (gtk_text_view_handle_drag_started), text_view); - g_signal_connect (priv->text_handle, "handle-dragged", - G_CALLBACK (gtk_text_view_handle_dragged), text_view); - g_signal_connect (priv->text_handle, "drag-finished", - G_CALLBACK (gtk_text_view_handle_drag_finished), text_view); -} - static void _gtk_text_view_ensure_magnifier (GtkTextView *text_view) { @@ -2043,8 +2051,7 @@ gtk_text_view_set_buffer (GtkTextView *text_view, gtk_text_buffer_add_selection_clipboard (priv->buffer, clipboard); } - if (priv->text_handle) - gtk_text_view_update_handles (text_view, GTK_TEXT_HANDLE_MODE_NONE); + gtk_text_view_update_handles (text_view); gtk_widget_action_set_enabled (GTK_WIDGET (text_view), "text.undo", can_undo); gtk_widget_action_set_enabled (GTK_WIDGET (text_view), "text.redo", can_redo); @@ -2655,9 +2662,7 @@ gtk_text_view_flush_scroll (GtkTextView *text_view) scroll->yalign, TRUE); - if (text_view->priv->text_handle) - gtk_text_view_update_handles (text_view, - _gtk_text_handle_get_mode (text_view->priv->text_handle)); + gtk_text_view_update_handles (text_view); free_pending_scroll (scroll); @@ -3695,6 +3700,9 @@ gtk_text_view_destroy (GtkWidget *widget) if (priv->magnifier) _gtk_magnifier_set_inspected (GTK_MAGNIFIER (priv->magnifier), NULL); + g_clear_pointer ((GtkWidget **) &priv->text_handles[TEXT_HANDLE_CURSOR], gtk_widget_unparent); + g_clear_pointer ((GtkWidget **) &priv->text_handles[TEXT_HANDLE_SELECTION_BOUND], gtk_widget_unparent); + GTK_WIDGET_CLASS (gtk_text_view_parent_class)->destroy (widget); } @@ -3737,8 +3745,6 @@ gtk_text_view_finalize (GObject *object) if (priv->magnifier_popover) gtk_widget_destroy (priv->magnifier_popover); - if (priv->text_handle) - g_object_unref (priv->text_handle); g_object_unref (priv->im_context); g_free (priv->im_module); @@ -4428,6 +4434,12 @@ gtk_text_view_size_allocate (GtkWidget *widget, if (priv->popup_menu) gtk_native_check_resize (GTK_NATIVE (priv->popup_menu)); + + if (priv->text_handles[TEXT_HANDLE_CURSOR]) + gtk_native_check_resize (GTK_NATIVE (priv->text_handles[TEXT_HANDLE_CURSOR])); + + if (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]) + gtk_native_check_resize (GTK_NATIVE (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])); } static void @@ -4860,9 +4872,9 @@ _widget_to_text_surface_coords (GtkTextView *text_view, } static void -gtk_text_view_set_handle_position (GtkTextView *text_view, - GtkTextIter *iter, - GtkTextHandlePosition pos) +gtk_text_view_set_handle_position (GtkTextView *text_view, + GtkTextHandle *handle, + GtkTextIter *iter) { GtkTextViewPrivate *priv; GdkRectangle rect; @@ -4874,32 +4886,32 @@ gtk_text_view_set_handle_position (GtkTextView *text_view, x = rect.x - priv->xoffset; y = rect.y - priv->yoffset; - if (!_gtk_text_handle_get_is_dragged (priv->text_handle, pos) && + if (!gtk_text_handle_get_is_dragged (handle) && (x < 0 || x > SCREEN_WIDTH (text_view) || y < 0 || y > SCREEN_HEIGHT (text_view))) { /* Hide the handle if it's not being manipulated * and fell outside of the visible text area. */ - _gtk_text_handle_set_visible (priv->text_handle, pos, FALSE); + gtk_widget_hide (GTK_WIDGET (handle)); } else { GtkTextDirection dir = GTK_TEXT_DIR_LTR; GtkTextAttributes attributes = { 0 }; - _gtk_text_handle_set_visible (priv->text_handle, pos, TRUE); + gtk_widget_show (GTK_WIDGET (handle)); rect.x = CLAMP (x, 0, SCREEN_WIDTH (text_view)); rect.y = CLAMP (y, 0, SCREEN_HEIGHT (text_view)); _text_window_to_widget_coords (text_view, &rect.x, &rect.y); - _gtk_text_handle_set_position (priv->text_handle, pos, &rect); + gtk_text_handle_set_position (handle, &rect); if (gtk_text_iter_get_attributes (iter, &attributes)) dir = attributes.direction; - _gtk_text_handle_set_direction (priv->text_handle, pos, dir); + gtk_widget_set_direction (GTK_WIDGET (handle), dir); } } @@ -4948,23 +4960,17 @@ gtk_text_view_show_magnifier (GtkTextView *text_view, } static void -gtk_text_view_handle_dragged (GtkTextHandle *handle, - GtkTextHandlePosition pos, - gint x, - gint y, - GtkTextView *text_view) +gtk_text_view_handle_dragged (GtkTextHandle *handle, + gint x, + gint y, + GtkTextView *text_view) { GtkTextViewPrivate *priv; - GtkTextIter old_cursor, old_bound; - GtkTextIter cursor, bound, iter; - GtkTextIter *min, *max; - GtkTextHandleMode mode; + GtkTextIter cursor, bound, iter, *old_iter; GtkTextBuffer *buffer; - GtkTextHandlePosition cursor_pos; priv = text_view->priv; buffer = get_buffer (text_view); - mode = _gtk_text_handle_get_mode (handle); _widget_to_text_surface_coords (text_view, &x, &y); @@ -4972,93 +4978,81 @@ gtk_text_view_handle_dragged (GtkTextHandle *handle, gtk_text_layout_get_iter_at_pixel (priv->layout, &iter, x + priv->xoffset, y + priv->yoffset); - gtk_text_buffer_get_iter_at_mark (buffer, &old_cursor, + + gtk_text_buffer_get_iter_at_mark (buffer, &cursor, gtk_text_buffer_get_insert (buffer)); - gtk_text_buffer_get_iter_at_mark (buffer, &old_bound, + gtk_text_buffer_get_iter_at_mark (buffer, &bound, gtk_text_buffer_get_selection_bound (buffer)); - cursor = old_cursor; - bound = old_bound; - if (mode == GTK_TEXT_HANDLE_MODE_CURSOR || - gtk_text_iter_compare (&cursor, &bound) >= 0) - { - cursor_pos = GTK_TEXT_HANDLE_POSITION_CURSOR; - max = &cursor; - min = &bound; - } - else - { - cursor_pos = GTK_TEXT_HANDLE_POSITION_SELECTION_START; - max = &bound; - min = &cursor; - } - if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_END) + if (handle == priv->text_handles[TEXT_HANDLE_CURSOR]) { - if (mode == GTK_TEXT_HANDLE_MODE_SELECTION && - gtk_text_iter_compare (&iter, min) <= 0) + /* Avoid running past the other handle in selection mode */ + if (gtk_text_iter_compare (&iter, &bound) >= 0 && + gtk_widget_is_visible (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]))) { - iter = *min; - gtk_text_iter_forward_char (&iter); - } - - *max = iter; - gtk_text_view_set_handle_position (text_view, &iter, pos); - } - else - { - if (mode == GTK_TEXT_HANDLE_MODE_SELECTION && - gtk_text_iter_compare (&iter, max) >= 0) - { - iter = *max; + iter = bound; gtk_text_iter_backward_char (&iter); } - *min = iter; - gtk_text_view_set_handle_position (text_view, &iter, pos); + old_iter = &cursor; + gtk_text_view_set_handle_position (text_view, handle, &iter); } - - if (gtk_text_iter_compare (&old_cursor, &cursor) != 0 || - gtk_text_iter_compare (&old_bound, &bound) != 0) + else if (handle == priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]) { - if (mode == GTK_TEXT_HANDLE_MODE_CURSOR) + /* Avoid running past the other handle */ + if (gtk_text_iter_compare (&iter, &cursor) <= 0) + { + iter = cursor; + gtk_text_iter_forward_char (&iter); + } + + old_iter = &bound; + gtk_text_view_set_handle_position (text_view, handle, &iter); + } + else + g_assert_not_reached (); + + if (gtk_text_iter_compare (&iter, old_iter) != 0) + { + *old_iter = iter; + + if (handle == priv->text_handles[TEXT_HANDLE_CURSOR] && + !gtk_widget_is_visible (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]))) gtk_text_buffer_place_cursor (buffer, &cursor); else gtk_text_buffer_select_range (buffer, &cursor, &bound); - if (_gtk_text_handle_get_is_dragged (priv->text_handle, cursor_pos)) + if (handle == priv->text_handles[TEXT_HANDLE_CURSOR]) { text_view->priv->cursor_handle_dragged = TRUE; gtk_text_view_scroll_mark_onscreen (text_view, gtk_text_buffer_get_insert (buffer)); } - else + else if (handle == priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]) { text_view->priv->selection_handle_dragged = TRUE; gtk_text_view_scroll_mark_onscreen (text_view, gtk_text_buffer_get_selection_bound (buffer)); } + + gtk_text_view_update_handles (text_view); } - if (_gtk_text_handle_get_is_dragged (priv->text_handle, cursor_pos)) - gtk_text_view_show_magnifier (text_view, &cursor, x, y); - else - gtk_text_view_show_magnifier (text_view, &bound, x, y); + gtk_text_view_show_magnifier (text_view, &iter, x, y); } static void -gtk_text_view_handle_drag_started (GtkTextHandle *handle, - GtkTextHandlePosition pos, - GtkTextView *text_view) +gtk_text_view_handle_drag_started (GtkTextHandle *handle, + GtkTextView *text_view) { text_view->priv->cursor_handle_dragged = FALSE; text_view->priv->selection_handle_dragged = FALSE; } static void -gtk_text_view_handle_drag_finished (GtkTextHandle *handle, - GtkTextHandlePosition pos, - GtkTextView *text_view) +gtk_text_view_handle_drag_finished (GtkTextHandle *handle, + GtkTextView *text_view) { GtkTextViewPrivate *priv = text_view->priv; @@ -5079,7 +5073,7 @@ gtk_text_view_handle_drag_finished (GtkTextHandle *handle, extend_selection (text_view, SELECT_WORDS, &cursor, &start, &end); gtk_text_buffer_select_range (buffer, &start, &end); - gtk_text_view_update_handles (text_view, GTK_TEXT_HANDLE_MODE_SELECTION); + gtk_text_view_update_handles (text_view); } else gtk_text_view_selection_bubble_popup_set (text_view); @@ -5092,52 +5086,64 @@ gtk_text_view_handle_drag_finished (GtkTextHandle *handle, static gboolean cursor_visible (GtkTextView *text_view); static void -gtk_text_view_update_handles (GtkTextView *text_view, - GtkTextHandleMode mode) +gtk_text_view_update_handles (GtkTextView *text_view) { GtkTextViewPrivate *priv = text_view->priv; - GtkTextIter cursor, bound, min, max; + GtkTextIter cursor, bound; GtkTextBuffer *buffer; - buffer = get_buffer (text_view); - - gtk_text_buffer_get_iter_at_mark (buffer, &cursor, - gtk_text_buffer_get_insert (buffer)); - gtk_text_buffer_get_iter_at_mark (buffer, &bound, - gtk_text_buffer_get_selection_bound (buffer)); - - if (mode == GTK_TEXT_HANDLE_MODE_SELECTION && - gtk_text_iter_compare (&cursor, &bound) == 0) + if (!priv->text_handles_enabled) { - mode = GTK_TEXT_HANDLE_MODE_CURSOR; - } - - if (mode == GTK_TEXT_HANDLE_MODE_CURSOR && - (!gtk_widget_is_sensitive (GTK_WIDGET (text_view)) || !cursor_visible (text_view))) - { - mode = GTK_TEXT_HANDLE_MODE_NONE; - } - - _gtk_text_handle_set_mode (priv->text_handle, mode); - - if (gtk_text_iter_compare (&cursor, &bound) >= 0) - { - min = bound; - max = cursor; + if (priv->text_handles[TEXT_HANDLE_CURSOR]) + gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_CURSOR])); + if (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]) + gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])); } else { - min = cursor; - max = bound; + _gtk_text_view_ensure_text_handles (text_view); + buffer = get_buffer (text_view); + + gtk_text_buffer_get_iter_at_mark (buffer, &cursor, + gtk_text_buffer_get_insert (buffer)); + gtk_text_buffer_get_iter_at_mark (buffer, &bound, + gtk_text_buffer_get_selection_bound (buffer)); + + if (gtk_text_iter_compare (&cursor, &bound) == 0 && priv->editable) + { + /* Cursor mode */ + gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])); + + gtk_text_view_set_handle_position (text_view, + priv->text_handles[TEXT_HANDLE_CURSOR], + &cursor); + gtk_text_handle_set_role (priv->text_handles[TEXT_HANDLE_CURSOR], + GTK_TEXT_HANDLE_ROLE_CURSOR); + gtk_widget_show (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_CURSOR])); + } + else if (gtk_text_iter_compare (&cursor, &bound) != 0) + { + /* Selection mode */ + gtk_text_view_set_handle_position (text_view, + priv->text_handles[TEXT_HANDLE_CURSOR], + &cursor); + gtk_text_handle_set_role (priv->text_handles[TEXT_HANDLE_CURSOR], + GTK_TEXT_HANDLE_ROLE_SELECTION_START); + gtk_widget_show (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_CURSOR])); + + gtk_text_view_set_handle_position (text_view, + priv->text_handles[TEXT_HANDLE_SELECTION_BOUND], + &bound); + gtk_text_handle_set_role (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND], + GTK_TEXT_HANDLE_ROLE_SELECTION_END); + gtk_widget_show (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])); + } + else + { + gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_CURSOR])); + gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])); + } } - - if (mode != GTK_TEXT_HANDLE_MODE_NONE) - gtk_text_view_set_handle_position (text_view, &max, - GTK_TEXT_HANDLE_POSITION_SELECTION_END); - - if (mode == GTK_TEXT_HANDLE_MODE_SELECTION) - gtk_text_view_set_handle_position (text_view, &min, - GTK_TEXT_HANDLE_POSITION_SELECTION_START); } static gboolean @@ -5199,9 +5205,8 @@ gtk_text_view_key_controller_key_pressed (GtkEventControllerKey *controller, gtk_text_view_reset_blink_time (text_view); gtk_text_view_pend_cursor_blink (text_view); - if (priv->text_handle) - _gtk_text_handle_set_mode (priv->text_handle, - GTK_TEXT_HANDLE_MODE_NONE); + text_view->priv->text_handles_enabled = FALSE; + gtk_text_view_update_handles (text_view); gtk_text_view_selection_bubble_popup_unset (text_view); @@ -5312,7 +5317,6 @@ gtk_text_view_click_gesture_pressed (GtkGestureClick *gesture, } else if (button == GDK_BUTTON_PRIMARY) { - GtkTextHandleMode handle_mode = GTK_TEXT_HANDLE_MODE_NONE; gboolean extends = FALSE; GdkModifierType state; @@ -5332,8 +5336,7 @@ gtk_text_view_click_gesture_pressed (GtkGestureClick *gesture, */ GtkTextIter start, end; - if (is_touchscreen) - handle_mode = GTK_TEXT_HANDLE_MODE_CURSOR; + priv->text_handles_enabled = is_touchscreen; get_iter_from_gesture (text_view, GTK_GESTURE (gesture), &iter, NULL, NULL); @@ -5348,12 +5351,11 @@ gtk_text_view_click_gesture_pressed (GtkGestureClick *gesture, !gtk_widget_get_visible (priv->selection_bubble)) { gtk_text_view_selection_bubble_popup_set (text_view); - handle_mode = GTK_TEXT_HANDLE_MODE_NONE; + priv->text_handles_enabled = FALSE; } else { gtk_text_view_selection_bubble_popup_unset (text_view); - handle_mode = GTK_TEXT_HANDLE_MODE_SELECTION; } } else @@ -5383,11 +5385,6 @@ gtk_text_view_click_gesture_pressed (GtkGestureClick *gesture, } case 2: case 3: - if (is_touchscreen) - { - handle_mode = GTK_TEXT_HANDLE_MODE_SELECTION; - break; - } gtk_text_view_end_selection_drag (text_view); get_iter_from_gesture (text_view, GTK_GESTURE (gesture), @@ -5400,8 +5397,7 @@ gtk_text_view_click_gesture_pressed (GtkGestureClick *gesture, break; } - _gtk_text_view_ensure_text_handles (text_view); - gtk_text_view_update_handles (text_view, handle_mode); + gtk_text_view_update_handles (text_view); } if (n_press >= 3) @@ -5468,9 +5464,8 @@ gtk_text_view_focus_out (GtkWidget *widget) text_view); gtk_text_view_selection_bubble_popup_unset (text_view); - if (priv->text_handle) - _gtk_text_handle_set_mode (priv->text_handle, - GTK_TEXT_HANDLE_MODE_NONE); + text_view->priv->text_handles_enabled = FALSE; + gtk_text_view_update_handles (text_view); if (priv->editable) { @@ -6840,11 +6835,8 @@ gtk_text_view_buffer_changed_handler (GtkTextBuffer *buffer, gpointer data) { GtkTextView *text_view = data; - GtkTextViewPrivate *priv = text_view->priv; - if (priv->text_handle) - gtk_text_view_update_handles (text_view, - _gtk_text_handle_get_mode (priv->text_handle)); + gtk_text_view_update_handles (text_view); } static void @@ -7288,8 +7280,8 @@ gtk_text_view_drag_gesture_update (GtkGestureDrag *gesture, if (is_touchscreen) { - _gtk_text_view_ensure_text_handles (text_view); - gtk_text_view_update_handles (text_view, GTK_TEXT_HANDLE_MODE_SELECTION); + text_view->priv->text_handles_enabled = TRUE; + gtk_text_view_update_handles (text_view); gtk_text_view_show_magnifier (text_view, &cursor, x, y); } } @@ -7338,7 +7330,6 @@ gtk_text_view_drag_gesture_end (GtkGestureDrag *gesture, if (!is_touchscreen && clicked_in_selection && !gtk_drag_check_threshold (GTK_WIDGET (text_view), start_x, start_y, x, y)) { - GtkTextHandleMode mode = GTK_TEXT_HANDLE_MODE_NONE; GtkTextIter iter; /* Unselect everything; we clicked inside selection, but @@ -7351,13 +7342,7 @@ gtk_text_view_drag_gesture_end (GtkGestureDrag *gesture, 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); - } + gtk_text_view_update_handles (text_view); } } @@ -8094,9 +8079,7 @@ gtk_text_view_value_changed (GtkAdjustment *adjustment, */ gtk_text_view_update_im_spot_location (text_view); - if (priv->text_handle) - gtk_text_view_update_handles (text_view, - _gtk_text_handle_get_mode (priv->text_handle)); + gtk_text_view_update_handles (text_view); if (priv->anchored_children.length > 0) gtk_widget_queue_allocate (GTK_WIDGET (text_view)); @@ -8285,9 +8268,7 @@ gtk_text_view_mark_set_handler (GtkTextBuffer *buffer, if (need_reset) { gtk_text_view_reset_im_context (text_view); - if (text_view->priv->text_handle) - gtk_text_view_update_handles (text_view, - _gtk_text_handle_get_mode (text_view->priv->text_handle)); + gtk_text_view_update_handles (text_view); has_selection = gtk_text_buffer_get_selection_bounds (get_buffer (text_view), NULL, NULL); gtk_css_node_set_visible (text_view->priv->selection_node, has_selection); @@ -8697,21 +8678,10 @@ show_or_hide_handles (GtkWidget *popover, GtkTextView *text_view) { gboolean visible; - GtkTextHandle *handle; - GtkTextHandleMode mode; visible = gtk_widget_get_visible (popover); - - handle = text_view->priv->text_handle; - mode = _gtk_text_handle_get_mode (handle); - - if (!visible) - gtk_text_view_update_handles (text_view, mode); - else - { - _gtk_text_handle_set_visible (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START, FALSE); - _gtk_text_handle_set_visible (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END, FALSE); - } + text_view->priv->text_handles_enabled = !visible; + gtk_text_view_update_handles (text_view); } static void