forked from AuroraMiddleware/gtk
Implement touch text selection in GtkEntry
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 (This is more important though on GtkTextView, as inverted handles may obscure portions of the selected text, good for consistence though)
This commit is contained in:
parent
1f7e375c33
commit
d38efb1713
276
gtk/gtkentry.c
276
gtk/gtkentry.c
@ -65,6 +65,7 @@
|
||||
#include "gtkicontheme.h"
|
||||
#include "gtkwidgetprivate.h"
|
||||
#include "gtkstylecontextprivate.h"
|
||||
#include "gtktexthandleprivate.h"
|
||||
|
||||
#include "a11y/gtkentryaccessible.h"
|
||||
|
||||
@ -157,6 +158,8 @@ struct _GtkEntryPrivate
|
||||
|
||||
gchar *placeholder_text;
|
||||
|
||||
GtkTextHandle *text_handle;
|
||||
|
||||
gfloat xalign;
|
||||
|
||||
gint ascent; /* font ascent in pango units */
|
||||
@ -213,6 +216,8 @@ struct _GtkEntryPrivate
|
||||
guint select_words : 1;
|
||||
guint select_lines : 1;
|
||||
guint truncate_multiline : 1;
|
||||
guint cursor_handle_dragged : 1;
|
||||
guint selection_handle_dragged : 1;
|
||||
};
|
||||
|
||||
struct _EntryIconInfo
|
||||
@ -575,6 +580,12 @@ static GdkPixbuf * gtk_entry_ensure_pixbuf (GtkEntry *en
|
||||
static void gtk_entry_update_cached_style_values(GtkEntry *entry);
|
||||
static gboolean get_middle_click_paste (GtkEntry *entry);
|
||||
|
||||
/* GtkTextHandle handlers */
|
||||
static void gtk_entry_handle_dragged (GtkTextHandle *handle,
|
||||
GtkTextHandlePosition pos,
|
||||
gint x,
|
||||
gint y,
|
||||
GtkEntry *entry);
|
||||
|
||||
static void begin_change (GtkEntry *entry);
|
||||
static void end_change (GtkEntry *entry);
|
||||
@ -2552,6 +2563,10 @@ gtk_entry_init (GtkEntry *entry)
|
||||
gtk_style_context_add_class (context, GTK_STYLE_CLASS_ENTRY);
|
||||
|
||||
gtk_entry_update_cached_style_values (entry);
|
||||
|
||||
priv->text_handle = _gtk_text_handle_new (GTK_WIDGET (entry));
|
||||
g_signal_connect (priv->text_handle, "handle-dragged",
|
||||
G_CALLBACK (gtk_entry_handle_dragged), entry);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -2788,6 +2803,7 @@ gtk_entry_finalize (GObject *object)
|
||||
if (priv->recompute_idle)
|
||||
g_source_remove (priv->recompute_idle);
|
||||
|
||||
g_object_unref (priv->text_handle);
|
||||
g_free (priv->placeholder_text);
|
||||
g_free (priv->im_module);
|
||||
|
||||
@ -3009,6 +3025,9 @@ gtk_entry_unmap (GtkWidget *widget)
|
||||
EntryIconInfo *icon_info = NULL;
|
||||
gint i;
|
||||
|
||||
_gtk_text_handle_set_mode (priv->text_handle,
|
||||
GTK_TEXT_HANDLE_MODE_NONE);
|
||||
|
||||
for (i = 0; i < MAX_ICONS; i++)
|
||||
{
|
||||
if ((icon_info = priv->icons[i]) != NULL)
|
||||
@ -3082,7 +3101,7 @@ gtk_entry_realize (GtkWidget *widget)
|
||||
|
||||
gtk_entry_adjust_scroll (entry);
|
||||
gtk_entry_update_primary_selection (entry);
|
||||
|
||||
_gtk_text_handle_set_relative_to (priv->text_handle, priv->text_area);
|
||||
|
||||
/* If the icon positions are already setup, create their windows.
|
||||
* Otherwise if they don't exist yet, then construct_icon_info()
|
||||
@ -3110,6 +3129,7 @@ gtk_entry_unrealize (GtkWidget *widget)
|
||||
gtk_entry_reset_layout (entry);
|
||||
|
||||
gtk_im_context_set_client_window (priv->im_context, NULL);
|
||||
_gtk_text_handle_set_relative_to (priv->text_handle, NULL);
|
||||
|
||||
clipboard = gtk_widget_get_clipboard (widget, GDK_SELECTION_PRIMARY);
|
||||
if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (entry))
|
||||
@ -3852,6 +3872,107 @@ in_selection (GtkEntry *entry,
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_entry_move_handle (GtkEntry *entry,
|
||||
GtkTextHandlePosition pos,
|
||||
gint x,
|
||||
gint y,
|
||||
gint height)
|
||||
{
|
||||
GtkEntryPrivate *priv = entry->priv;
|
||||
|
||||
if (!_gtk_text_handle_get_is_dragged (priv->text_handle, pos) &&
|
||||
(x < 0 || x > gdk_window_get_width (priv->text_area)))
|
||||
{
|
||||
/* 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
|
||||
{
|
||||
GdkRectangle rect;
|
||||
|
||||
rect.x = CLAMP (x, 0, gdk_window_get_width (priv->text_area));
|
||||
rect.y = y;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
static gint
|
||||
gtk_entry_get_selection_bound_location (GtkEntry *entry)
|
||||
{
|
||||
GtkEntryPrivate *priv = entry->priv;
|
||||
PangoLayout *layout;
|
||||
PangoRectangle pos;
|
||||
gint x;
|
||||
const gchar *text;
|
||||
gint index;
|
||||
|
||||
layout = gtk_entry_ensure_layout (entry, FALSE);
|
||||
text = pango_layout_get_text (layout);
|
||||
index = g_utf8_offset_to_pointer (text, priv->selection_bound) - text;
|
||||
pango_layout_index_to_pos (layout, index, &pos);
|
||||
|
||||
if (gtk_widget_get_direction (GTK_WIDGET (entry)) == GTK_TEXT_DIR_RTL)
|
||||
x = (pos.x + pos.width) / PANGO_SCALE;
|
||||
else
|
||||
x = pos.x / PANGO_SCALE;
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_entry_update_handles (GtkEntry *entry,
|
||||
GtkTextHandleMode mode)
|
||||
{
|
||||
GtkEntryPrivate *priv = entry->priv;
|
||||
gint strong_x, height;
|
||||
gint cursor, bound;
|
||||
|
||||
_gtk_text_handle_set_mode (priv->text_handle, mode);
|
||||
|
||||
/* Wait for recomputation before repositioning */
|
||||
if (priv->recompute_idle != 0)
|
||||
return;
|
||||
|
||||
height = gdk_window_get_height (priv->text_area);
|
||||
|
||||
gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &strong_x, NULL);
|
||||
cursor = strong_x - priv->scroll_offset;
|
||||
|
||||
if (mode == GTK_TEXT_HANDLE_MODE_SELECTION)
|
||||
{
|
||||
gint start, end;
|
||||
|
||||
bound = gtk_entry_get_selection_bound_location (entry) - priv->scroll_offset;
|
||||
|
||||
if (priv->selection_bound > priv->current_pos)
|
||||
{
|
||||
start = cursor;
|
||||
end = bound;
|
||||
}
|
||||
else
|
||||
{
|
||||
start = bound;
|
||||
end = cursor;
|
||||
}
|
||||
|
||||
/* Update start selection bound */
|
||||
gtk_entry_move_handle (entry, GTK_TEXT_HANDLE_POSITION_SELECTION_START,
|
||||
start, 0, height);
|
||||
gtk_entry_move_handle (entry, GTK_TEXT_HANDLE_POSITION_SELECTION_END,
|
||||
end, 0, height);
|
||||
}
|
||||
else
|
||||
gtk_entry_move_handle (entry, GTK_TEXT_HANDLE_POSITION_CURSOR,
|
||||
cursor, 0, height);
|
||||
}
|
||||
|
||||
static gint
|
||||
gtk_entry_button_press (GtkWidget *widget,
|
||||
GdkEventButton *event)
|
||||
@ -3919,6 +4040,11 @@ gtk_entry_button_press (GtkWidget *widget,
|
||||
else if (event->button == GDK_BUTTON_PRIMARY)
|
||||
{
|
||||
gboolean have_selection = gtk_editable_get_selection_bounds (editable, &sel_start, &sel_end);
|
||||
gboolean is_touchscreen;
|
||||
GdkDevice *source;
|
||||
|
||||
source = gdk_event_get_source_device ((GdkEvent *) event);
|
||||
is_touchscreen = gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN;
|
||||
|
||||
priv->select_words = FALSE;
|
||||
priv->select_lines = FALSE;
|
||||
@ -3997,7 +4123,12 @@ gtk_entry_button_press (GtkWidget *widget,
|
||||
priv->drag_start_y = event->y;
|
||||
}
|
||||
else
|
||||
{
|
||||
gtk_editable_set_position (editable, tmp_pos);
|
||||
|
||||
if (is_touchscreen)
|
||||
gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_CURSOR);
|
||||
}
|
||||
break;
|
||||
|
||||
case GDK_2BUTTON_PRESS:
|
||||
@ -4008,6 +4139,9 @@ gtk_entry_button_press (GtkWidget *widget,
|
||||
priv->in_drag = FALSE;
|
||||
priv->select_words = TRUE;
|
||||
gtk_entry_select_word (entry);
|
||||
|
||||
if (is_touchscreen)
|
||||
gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_SELECTION);
|
||||
break;
|
||||
|
||||
case GDK_3BUTTON_PRESS:
|
||||
@ -4018,6 +4152,8 @@ gtk_entry_button_press (GtkWidget *widget,
|
||||
priv->in_drag = FALSE;
|
||||
priv->select_lines = TRUE;
|
||||
gtk_entry_select_line (entry);
|
||||
if (is_touchscreen)
|
||||
gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_SELECTION);
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -4087,9 +4223,15 @@ gtk_entry_button_release (GtkWidget *widget,
|
||||
if (priv->in_drag)
|
||||
{
|
||||
gint tmp_pos = gtk_entry_find_position (entry, priv->drag_start_x);
|
||||
GdkDevice *source;
|
||||
|
||||
gtk_editable_set_position (GTK_EDITABLE (entry), tmp_pos);
|
||||
|
||||
source = gdk_event_get_source_device ((GdkEvent *) event);
|
||||
|
||||
if (gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN)
|
||||
gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_CURSOR);
|
||||
|
||||
priv->in_drag = 0;
|
||||
}
|
||||
|
||||
@ -4211,13 +4353,22 @@ gtk_entry_motion_notify (GtkWidget *widget,
|
||||
}
|
||||
else
|
||||
{
|
||||
GdkInputSource input_source;
|
||||
GdkDevice *source;
|
||||
guint length;
|
||||
|
||||
length = gtk_entry_buffer_get_length (get_buffer (entry));
|
||||
|
||||
if (event->y < 0)
|
||||
tmp_pos = 0;
|
||||
else if (event->y >= gdk_window_get_height (priv->text_area))
|
||||
tmp_pos = gtk_entry_buffer_get_length (get_buffer (entry));
|
||||
tmp_pos = length;
|
||||
else
|
||||
tmp_pos = gtk_entry_find_position (entry, event->x + priv->scroll_offset);
|
||||
|
||||
source = gdk_event_get_source_device ((GdkEvent *) event);
|
||||
input_source = gdk_device_get_source (source);
|
||||
|
||||
if (priv->select_words)
|
||||
{
|
||||
gint min, max;
|
||||
@ -4258,6 +4409,13 @@ gtk_entry_motion_notify (GtkWidget *widget,
|
||||
}
|
||||
else
|
||||
gtk_entry_set_positions (entry, tmp_pos, -1);
|
||||
|
||||
/* Update touch handles' position */
|
||||
if (input_source == GDK_SOURCE_TOUCHSCREEN)
|
||||
gtk_entry_update_handles (entry,
|
||||
(priv->current_pos == priv->selection_bound) ?
|
||||
GTK_TEXT_HANDLE_MODE_CURSOR :
|
||||
GTK_TEXT_HANDLE_MODE_SELECTION);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
@ -4299,6 +4457,8 @@ gtk_entry_key_press (GtkWidget *widget,
|
||||
|
||||
gtk_entry_reset_blink_time (entry);
|
||||
gtk_entry_pend_cursor_blink (entry);
|
||||
_gtk_text_handle_set_mode (priv->text_handle,
|
||||
GTK_TEXT_HANDLE_MODE_NONE);
|
||||
|
||||
if (priv->editable)
|
||||
{
|
||||
@ -4393,6 +4553,9 @@ gtk_entry_focus_out (GtkWidget *widget,
|
||||
GtkEntryCompletion *completion;
|
||||
GdkKeymap *keymap;
|
||||
|
||||
_gtk_text_handle_set_mode (priv->text_handle,
|
||||
GTK_TEXT_HANDLE_MODE_NONE);
|
||||
|
||||
gtk_widget_queue_draw (widget);
|
||||
|
||||
keymap = gdk_keymap_get_for_display (gtk_widget_get_display (widget));
|
||||
@ -5573,10 +5736,17 @@ recompute_idle_func (gpointer data)
|
||||
|
||||
if (gtk_widget_has_screen (GTK_WIDGET (entry)))
|
||||
{
|
||||
GtkTextHandleMode handle_mode;
|
||||
|
||||
gtk_entry_adjust_scroll (entry);
|
||||
gtk_widget_queue_draw (GTK_WIDGET (entry));
|
||||
|
||||
update_im_cursor_location (entry);
|
||||
|
||||
handle_mode = _gtk_text_handle_get_mode (priv->text_handle);
|
||||
|
||||
if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE)
|
||||
gtk_entry_update_handles (entry, handle_mode);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
@ -6010,6 +6180,70 @@ gtk_entry_draw_cursor (GtkEntry *entry,
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_entry_handle_dragged (GtkTextHandle *handle,
|
||||
GtkTextHandlePosition pos,
|
||||
gint x,
|
||||
gint y,
|
||||
GtkEntry *entry)
|
||||
{
|
||||
gint cursor_pos, selection_bound_pos, tmp_pos;
|
||||
GtkEntryPrivate *priv = entry->priv;
|
||||
GtkTextHandleMode mode;
|
||||
gint *min, *max;
|
||||
|
||||
cursor_pos = priv->current_pos;
|
||||
selection_bound_pos = priv->selection_bound;
|
||||
mode = _gtk_text_handle_get_mode (handle);
|
||||
tmp_pos = gtk_entry_find_position (entry, x + priv->scroll_offset);
|
||||
|
||||
if (mode == GTK_TEXT_HANDLE_MODE_CURSOR ||
|
||||
cursor_pos >= selection_bound_pos)
|
||||
{
|
||||
max = &cursor_pos;
|
||||
min = &selection_bound_pos;
|
||||
}
|
||||
else
|
||||
{
|
||||
max = &selection_bound_pos;
|
||||
min = &cursor_pos;
|
||||
}
|
||||
|
||||
if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_END)
|
||||
{
|
||||
if (mode == GTK_TEXT_HANDLE_MODE_SELECTION)
|
||||
{
|
||||
gint 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)
|
||||
{
|
||||
gint 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)
|
||||
gtk_entry_set_positions (entry, cursor_pos, cursor_pos);
|
||||
else
|
||||
gtk_entry_set_positions (entry, cursor_pos, selection_bound_pos);
|
||||
|
||||
gtk_entry_update_handles (entry, mode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_entry_reset_im_context:
|
||||
* @entry: a #GtkEntry
|
||||
@ -6166,6 +6400,23 @@ gtk_entry_get_cursor_locations (GtkEntry *entry,
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_entry_get_is_selection_handle_dragged (GtkEntry *entry)
|
||||
{
|
||||
GtkEntryPrivate *priv = entry->priv;
|
||||
GtkTextHandlePosition pos;
|
||||
|
||||
if (_gtk_text_handle_get_mode (priv->text_handle) != GTK_TEXT_HANDLE_MODE_SELECTION)
|
||||
return FALSE;
|
||||
|
||||
if (priv->current_pos >= priv->selection_bound)
|
||||
pos = GTK_TEXT_HANDLE_POSITION_SELECTION_START;
|
||||
else
|
||||
pos = GTK_TEXT_HANDLE_POSITION_SELECTION_END;
|
||||
|
||||
return _gtk_text_handle_get_is_dragged (priv->text_handle, pos);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_entry_adjust_scroll (GtkEntry *entry)
|
||||
{
|
||||
@ -6178,6 +6429,7 @@ gtk_entry_adjust_scroll (GtkEntry *entry)
|
||||
PangoLayout *layout;
|
||||
PangoLayoutLine *line;
|
||||
PangoRectangle logical_rect;
|
||||
GtkTextHandleMode handle_mode;
|
||||
|
||||
if (!gtk_widget_get_realized (GTK_WIDGET (entry)))
|
||||
return;
|
||||
@ -6214,6 +6466,17 @@ gtk_entry_adjust_scroll (GtkEntry *entry)
|
||||
|
||||
priv->scroll_offset = CLAMP (priv->scroll_offset, min_offset, max_offset);
|
||||
|
||||
if (gtk_entry_get_is_selection_handle_dragged (entry))
|
||||
{
|
||||
/* The text handle corresponding to the selection bound is
|
||||
* being dragged, ensure it stays onscreen even if we scroll
|
||||
* cursors away, this is so both handles can cause content
|
||||
* to scroll.
|
||||
*/
|
||||
strong_x = weak_x = gtk_entry_get_selection_bound_location (entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* And make sure cursors are on screen. Note that the cursor is
|
||||
* actually drawn one pixel into the INNER_BORDER space on
|
||||
* the right, when the scroll is at the utmost right. This
|
||||
@ -6227,8 +6490,8 @@ gtk_entry_adjust_scroll (GtkEntry *entry)
|
||||
* We always make sure that the strong cursor is on screen, and
|
||||
* put the weak cursor on screen if possible.
|
||||
*/
|
||||
|
||||
gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &strong_x, &weak_x);
|
||||
}
|
||||
|
||||
strong_xoffset = strong_x - priv->scroll_offset;
|
||||
|
||||
@ -6256,6 +6519,11 @@ gtk_entry_adjust_scroll (GtkEntry *entry)
|
||||
}
|
||||
|
||||
g_object_notify (G_OBJECT (entry), "scroll-offset");
|
||||
|
||||
handle_mode = _gtk_text_handle_get_mode (priv->text_handle);
|
||||
|
||||
if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE)
|
||||
gtk_entry_update_handles (entry, handle_mode);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -6278,7 +6546,7 @@ gtk_entry_move_adjustments (GtkEntry *entry)
|
||||
|
||||
gtk_widget_get_allocation (widget, &allocation);
|
||||
|
||||
/* Cursor position, layout offset, border width, and widget allocation */
|
||||
/* Cursor/char position, layout offset, border width, and widget allocation */
|
||||
gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &x, NULL);
|
||||
get_layout_position (entry, &layout_x, NULL);
|
||||
_gtk_entry_get_borders (entry, &borders);
|
||||
|
Loading…
Reference in New Issue
Block a user