forked from AuroraMiddleware/gtk
Implement touch text selection in GtkTextView
GtkTextHandle is used to indicate both the cursor position and the selection bound, dragging the handles will modify the selection and scroll if necessary. Backwards text selection is also blocked for touch devices, so the handles don't get inverted positions and possibly obscure portions of the selected text.
This commit is contained in:
parent
d38efb1713
commit
5bda3a001d
@ -50,6 +50,7 @@
|
||||
#include "gtkwindow.h"
|
||||
#include "gtkscrollable.h"
|
||||
#include "gtktypebuiltins.h"
|
||||
#include "gtktexthandleprivate.h"
|
||||
|
||||
#include "a11y/gtktextviewaccessible.h"
|
||||
|
||||
@ -132,6 +133,7 @@ struct _GtkTextViewPrivate
|
||||
GdkDevice *dnd_device;
|
||||
|
||||
gulong selection_drag_handler;
|
||||
GtkTextHandle *text_handle;
|
||||
|
||||
GtkTextWindow *text_window;
|
||||
GtkTextWindow *left_window;
|
||||
@ -229,6 +231,8 @@ struct _GtkTextViewPrivate
|
||||
* driving the scrollable adjustment values */
|
||||
guint hscroll_policy : 1;
|
||||
guint vscroll_policy : 1;
|
||||
guint cursor_handle_dragged : 1;
|
||||
guint selection_handle_dragged : 1;
|
||||
};
|
||||
|
||||
struct _GtkTextPendingScroll
|
||||
@ -454,6 +458,8 @@ static void gtk_text_view_target_list_notify (GtkTextBuffer *buffer,
|
||||
static void gtk_text_view_paste_done_handler (GtkTextBuffer *buffer,
|
||||
GtkClipboard *clipboard,
|
||||
gpointer data);
|
||||
static void gtk_text_view_buffer_changed_handler (GtkTextBuffer *buffer,
|
||||
gpointer data);
|
||||
static void gtk_text_view_get_virtual_cursor_pos (GtkTextView *text_view,
|
||||
GtkTextIter *cursor,
|
||||
gint *x,
|
||||
@ -497,6 +503,15 @@ static void gtk_text_view_forall (GtkContainer *container,
|
||||
GtkCallback callback,
|
||||
gpointer callback_data);
|
||||
|
||||
/* GtkTextHandle handlers */
|
||||
static void gtk_text_view_handle_dragged (GtkTextHandle *handle,
|
||||
GtkTextHandlePosition pos,
|
||||
gint x,
|
||||
gint y,
|
||||
GtkTextView *text_view);
|
||||
static void gtk_text_view_update_handles (GtkTextView *text_view,
|
||||
GtkTextHandleMode mode);
|
||||
|
||||
/* FIXME probably need the focus methods. */
|
||||
|
||||
typedef struct _GtkTextViewChild GtkTextViewChild;
|
||||
@ -1456,6 +1471,10 @@ gtk_text_view_init (GtkTextView *text_view)
|
||||
|
||||
/* We handle all our own redrawing */
|
||||
gtk_widget_set_redraw_on_allocate (widget, FALSE);
|
||||
|
||||
priv->text_handle = _gtk_text_handle_new (widget);
|
||||
g_signal_connect (priv->text_handle, "handle-dragged",
|
||||
G_CALLBACK (gtk_text_view_handle_dragged), text_view);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1558,6 +1577,9 @@ gtk_text_view_set_buffer (GtkTextView *text_view,
|
||||
g_signal_handlers_disconnect_by_func (priv->buffer,
|
||||
gtk_text_view_paste_done_handler,
|
||||
text_view);
|
||||
g_signal_handlers_disconnect_by_func (priv->buffer,
|
||||
gtk_text_view_buffer_changed_handler,
|
||||
text_view);
|
||||
|
||||
if (gtk_widget_get_realized (GTK_WIDGET (text_view)))
|
||||
{
|
||||
@ -1607,6 +1629,9 @@ gtk_text_view_set_buffer (GtkTextView *text_view,
|
||||
g_signal_connect (priv->buffer, "paste-done",
|
||||
G_CALLBACK (gtk_text_view_paste_done_handler),
|
||||
text_view);
|
||||
g_signal_connect (priv->buffer, "changed",
|
||||
G_CALLBACK (gtk_text_view_buffer_changed_handler),
|
||||
text_view);
|
||||
|
||||
gtk_text_view_target_list_notify (priv->buffer, NULL, text_view);
|
||||
|
||||
@ -1616,6 +1641,8 @@ gtk_text_view_set_buffer (GtkTextView *text_view,
|
||||
GDK_SELECTION_PRIMARY);
|
||||
gtk_text_buffer_add_selection_clipboard (priv->buffer, clipboard);
|
||||
}
|
||||
|
||||
gtk_text_view_update_handles (text_view, GTK_TEXT_HANDLE_MODE_NONE);
|
||||
}
|
||||
|
||||
_gtk_text_view_accessible_set_buffer (text_view, old_buffer);
|
||||
@ -3106,6 +3133,7 @@ gtk_text_view_finalize (GObject *object)
|
||||
if (priv->bottom_window)
|
||||
text_window_free (priv->bottom_window);
|
||||
|
||||
g_object_unref (priv->text_handle);
|
||||
g_object_unref (priv->im_context);
|
||||
|
||||
g_free (priv->im_module);
|
||||
@ -4107,6 +4135,8 @@ gtk_text_view_realize (GtkWidget *widget)
|
||||
|
||||
/* Ensure updating the spot location. */
|
||||
gtk_text_view_update_im_spot_location (text_view);
|
||||
|
||||
_gtk_text_handle_set_relative_to (priv->text_handle, priv->text_window->window);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -4147,6 +4177,8 @@ gtk_text_view_unrealize (GtkWidget *widget)
|
||||
if (priv->bottom_window)
|
||||
text_window_unrealize (priv->bottom_window);
|
||||
|
||||
_gtk_text_handle_set_relative_to (priv->text_handle, NULL);
|
||||
|
||||
GTK_WIDGET_CLASS (gtk_text_view_parent_class)->unrealize (widget);
|
||||
}
|
||||
|
||||
@ -4414,6 +4446,174 @@ emit_event_on_tags (GtkWidget *widget,
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_text_view_set_handle_position (GtkTextView *text_view,
|
||||
GtkTextIter *iter,
|
||||
GtkTextHandlePosition pos)
|
||||
{
|
||||
GtkTextViewPrivate *priv;
|
||||
GdkRectangle rect;
|
||||
gint x, y;
|
||||
|
||||
priv = text_view->priv;
|
||||
gtk_text_view_get_cursor_locations (text_view, iter, &rect, NULL);
|
||||
|
||||
x = rect.x - priv->xoffset;
|
||||
y = rect.y - priv->yoffset;
|
||||
|
||||
if (!_gtk_text_handle_get_is_dragged (priv->text_handle, pos) &&
|
||||
(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);
|
||||
}
|
||||
else
|
||||
{
|
||||
_gtk_text_handle_set_visible (priv->text_handle, pos, TRUE);
|
||||
|
||||
rect.x = CLAMP (x, 0, SCREEN_WIDTH (text_view));
|
||||
rect.y = CLAMP (y, 0, SCREEN_HEIGHT (text_view));
|
||||
_gtk_text_handle_set_position (priv->text_handle, pos, &rect);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_text_view_handle_dragged (GtkTextHandle *handle,
|
||||
GtkTextHandlePosition pos,
|
||||
gint x,
|
||||
gint y,
|
||||
GtkTextView *text_view)
|
||||
{
|
||||
GtkTextViewPrivate *priv;
|
||||
GtkTextIter old_cursor, old_bound;
|
||||
GtkTextIter cursor, bound, iter;
|
||||
GtkTextIter *min, *max;
|
||||
GtkTextHandleMode mode;
|
||||
GtkTextBuffer *buffer;
|
||||
GtkTextHandlePosition cursor_pos;
|
||||
|
||||
priv = text_view->priv;
|
||||
buffer = get_buffer (text_view);
|
||||
mode = _gtk_text_handle_get_mode (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_insert (buffer));
|
||||
gtk_text_buffer_get_iter_at_mark (buffer, &old_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 (mode == GTK_TEXT_HANDLE_MODE_SELECTION &&
|
||||
gtk_text_iter_compare (&iter, min) <= 0)
|
||||
{
|
||||
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;
|
||||
gtk_text_iter_backward_char (&iter);
|
||||
}
|
||||
|
||||
*min = iter;
|
||||
gtk_text_view_set_handle_position (text_view, &iter, pos);
|
||||
}
|
||||
|
||||
if (gtk_text_iter_compare (&old_cursor, &cursor) != 0 ||
|
||||
gtk_text_iter_compare (&old_bound, &bound) != 0)
|
||||
{
|
||||
if (mode == GTK_TEXT_HANDLE_MODE_CURSOR)
|
||||
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))
|
||||
gtk_text_view_scroll_mark_onscreen (text_view,
|
||||
gtk_text_buffer_get_insert (buffer));
|
||||
else
|
||||
gtk_text_view_scroll_mark_onscreen (text_view,
|
||||
gtk_text_buffer_get_selection_bound (buffer));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_text_view_update_handles (GtkTextView *text_view,
|
||||
GtkTextHandleMode mode)
|
||||
{
|
||||
GtkTextViewPrivate *priv = text_view->priv;
|
||||
GtkTextIter cursor, bound, min, max;
|
||||
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)
|
||||
{
|
||||
mode = GTK_TEXT_HANDLE_MODE_CURSOR;
|
||||
}
|
||||
|
||||
if (mode == GTK_TEXT_HANDLE_MODE_CURSOR &&
|
||||
(!gtk_widget_is_sensitive (GTK_WIDGET (text_view)) || !priv->cursor_visible))
|
||||
{
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
min = cursor;
|
||||
max = 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 gint
|
||||
gtk_text_view_event (GtkWidget *widget, GdkEvent *event)
|
||||
{
|
||||
@ -4545,6 +4745,9 @@ gtk_text_view_key_press_event (GtkWidget *widget, GdkEventKey *event)
|
||||
gtk_text_view_reset_blink_time (text_view);
|
||||
gtk_text_view_pend_cursor_blink (text_view);
|
||||
|
||||
_gtk_text_handle_set_mode (priv->text_handle,
|
||||
GTK_TEXT_HANDLE_MODE_NONE);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -4579,6 +4782,8 @@ gtk_text_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
|
||||
{
|
||||
GtkTextView *text_view;
|
||||
GtkTextViewPrivate *priv;
|
||||
GdkDevice *device;
|
||||
gboolean is_touchscreen;
|
||||
|
||||
text_view = GTK_TEXT_VIEW (widget);
|
||||
priv = text_view->priv;
|
||||
@ -4602,6 +4807,9 @@ gtk_text_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
|
||||
gtk_text_layout_spew (GTK_TEXT_VIEW (widget)->layout);
|
||||
#endif
|
||||
|
||||
device = gdk_event_get_source_device ((GdkEvent *) event);
|
||||
is_touchscreen = gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN;
|
||||
|
||||
if (event->type == GDK_BUTTON_PRESS)
|
||||
{
|
||||
gtk_text_view_reset_im_context (text_view);
|
||||
@ -4638,7 +4846,16 @@ gtk_text_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
|
||||
}
|
||||
else
|
||||
{
|
||||
GtkTextHandleMode mode;
|
||||
|
||||
gtk_text_view_start_selection_drag (text_view, &iter, event);
|
||||
|
||||
if (gtk_widget_is_sensitive (widget) && is_touchscreen)
|
||||
mode = GTK_TEXT_HANDLE_MODE_CURSOR;
|
||||
else
|
||||
mode = GTK_TEXT_HANDLE_MODE_NONE;
|
||||
|
||||
gtk_text_view_update_handles (text_view, mode);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
@ -4669,6 +4886,7 @@ gtk_text_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
|
||||
event->button == GDK_BUTTON_PRIMARY)
|
||||
{
|
||||
GtkTextIter iter;
|
||||
GtkTextHandleMode mode;
|
||||
|
||||
gtk_text_view_end_selection_drag (text_view);
|
||||
|
||||
@ -4676,11 +4894,18 @@ gtk_text_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
|
||||
&iter,
|
||||
event->x + priv->xoffset,
|
||||
event->y + priv->yoffset);
|
||||
|
||||
|
||||
gtk_text_view_start_selection_drag (text_view, &iter, event);
|
||||
|
||||
if (gtk_widget_is_sensitive (widget) && is_touchscreen)
|
||||
mode = GTK_TEXT_HANDLE_MODE_SELECTION;
|
||||
else
|
||||
mode = GTK_TEXT_HANDLE_MODE_NONE;
|
||||
|
||||
gtk_text_view_update_handles (text_view, mode);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@ -4708,6 +4933,8 @@ gtk_text_view_button_release_event (GtkWidget *widget, GdkEventButton *event)
|
||||
return TRUE;
|
||||
else if (priv->pending_place_cursor_button == event->button)
|
||||
{
|
||||
GtkTextHandleMode mode;
|
||||
GdkDevice *device;
|
||||
GtkTextIter iter;
|
||||
|
||||
/* Unselect everything; we clicked inside selection, but
|
||||
@ -4721,9 +4948,18 @@ gtk_text_view_button_release_event (GtkWidget *widget, GdkEventButton *event)
|
||||
|
||||
gtk_text_buffer_place_cursor (get_buffer (text_view), &iter);
|
||||
gtk_text_view_check_cursor_blink (text_view);
|
||||
|
||||
|
||||
device = gdk_event_get_source_device ((GdkEvent *) event);
|
||||
|
||||
if (gtk_widget_is_sensitive (widget) &&
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -4797,6 +5033,8 @@ gtk_text_view_focus_out_event (GtkWidget *widget, GdkEventFocus *event)
|
||||
g_signal_handlers_disconnect_by_func (gdk_keymap_get_for_display (gtk_widget_get_display (widget)),
|
||||
keymap_direction_changed,
|
||||
text_view);
|
||||
_gtk_text_handle_set_mode (priv->text_handle,
|
||||
GTK_TEXT_HANDLE_MODE_NONE);
|
||||
|
||||
if (priv->editable)
|
||||
{
|
||||
@ -6101,6 +6339,14 @@ gtk_text_view_paste_done_handler (GtkTextBuffer *buffer,
|
||||
priv->scroll_after_paste = TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_text_view_buffer_changed_handler (GtkTextBuffer *buffer,
|
||||
gpointer data)
|
||||
{
|
||||
GtkTextView *text_view = data;
|
||||
gtk_text_view_update_handles (text_view, GTK_TEXT_HANDLE_MODE_NONE);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_text_view_toggle_overwrite (GtkTextView *text_view)
|
||||
{
|
||||
@ -6256,24 +6502,25 @@ get_iter_at_pointer (GtkTextView *text_view,
|
||||
}
|
||||
|
||||
static void
|
||||
move_mark_to_pointer_and_scroll (GtkTextView *text_view,
|
||||
const gchar *mark_name,
|
||||
GdkDevice *device)
|
||||
move_mark_to_pointer_and_scroll (GtkTextView *text_view,
|
||||
const gchar *mark_name,
|
||||
GdkDevice *device,
|
||||
GdkInputSource source)
|
||||
{
|
||||
GtkTextIter newplace;
|
||||
GtkTextBuffer *buffer;
|
||||
GtkTextMark *mark;
|
||||
|
||||
buffer = get_buffer (text_view);
|
||||
get_iter_at_pointer (text_view, device, &newplace, NULL, NULL);
|
||||
|
||||
mark = gtk_text_buffer_get_mark (get_buffer (text_view), mark_name);
|
||||
|
||||
|
||||
mark = gtk_text_buffer_get_mark (buffer, mark_name);
|
||||
|
||||
/* This may invalidate the layout */
|
||||
DV(g_print (G_STRLOC": move mark\n"));
|
||||
|
||||
gtk_text_buffer_move_mark (get_buffer (text_view),
|
||||
mark,
|
||||
&newplace);
|
||||
|
||||
|
||||
gtk_text_buffer_move_mark (buffer, mark, &newplace);
|
||||
|
||||
DV(g_print (G_STRLOC": scrolling onscreen\n"));
|
||||
gtk_text_view_scroll_mark_onscreen (text_view, mark);
|
||||
|
||||
@ -6458,16 +6705,22 @@ selection_motion_event_handler (GtkTextView *text_view,
|
||||
SelectionData *data)
|
||||
{
|
||||
GtkTextViewPrivate *priv;
|
||||
GdkInputSource input_source;
|
||||
GdkDevice *device;
|
||||
|
||||
priv = text_view->priv;
|
||||
gdk_event_request_motions (event);
|
||||
|
||||
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)
|
||||
{
|
||||
move_mark_to_pointer_and_scroll (text_view, "insert", event->device);
|
||||
move_mark_to_pointer_and_scroll (text_view, "insert",
|
||||
event->device, input_source);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -6491,7 +6744,7 @@ selection_motion_event_handler (GtkTextView *text_view,
|
||||
else
|
||||
gtk_text_buffer_select_range (buffer, &end, &orig_start);
|
||||
|
||||
gtk_text_view_scroll_mark_onscreen (text_view,
|
||||
gtk_text_view_scroll_mark_onscreen (text_view,
|
||||
gtk_text_buffer_get_insert (buffer));
|
||||
}
|
||||
|
||||
@ -6506,6 +6759,9 @@ selection_motion_event_handler (GtkTextView *text_view,
|
||||
text_view->priv->scroll_timeout =
|
||||
gdk_threads_add_timeout (50, selection_scan_timeout, text_view);
|
||||
|
||||
if (input_source == GDK_SOURCE_TOUCHSCREEN)
|
||||
gtk_text_view_update_handles (text_view, GTK_TEXT_HANDLE_MODE_SELECTION);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -7757,7 +8013,10 @@ gtk_text_view_value_changed (GtkAdjustment *adjustment,
|
||||
* changes made by the validation are pushed through.
|
||||
*/
|
||||
gtk_text_view_update_im_spot_location (text_view);
|
||||
|
||||
|
||||
gtk_text_view_update_handles (text_view,
|
||||
_gtk_text_handle_get_mode (priv->text_handle));
|
||||
|
||||
DV(g_print(">End scroll offset changed handler ("G_STRLOC")\n"));
|
||||
}
|
||||
|
||||
@ -7933,7 +8192,11 @@ gtk_text_view_mark_set_handler (GtkTextBuffer *buffer,
|
||||
}
|
||||
|
||||
if (need_reset)
|
||||
gtk_text_view_reset_im_context (text_view);
|
||||
{
|
||||
gtk_text_view_reset_im_context (text_view);
|
||||
gtk_text_view_update_handles (text_view,
|
||||
_gtk_text_handle_get_mode (text_view->priv->text_handle));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
Loading…
Reference in New Issue
Block a user