Compare commits

...

25 Commits

Author SHA1 Message Date
Matthias Clasen
39c8db124d Don't show cursor handles without a cursor
A non-editable textview may or may not have a cursor.
Take that into account when showing handles.
2012-09-02 19:56:28 -04:00
Carlos Garnacho
1c11f6ed1a entry: switch handles correctly on rtl
MIN/MAX of pixel values don't cut it, so compare
selection/cursor indexes.
2012-09-02 23:38:14 +02:00
Matthias Clasen
291ff93a6b Fix calculation of selection_bound location
_gtk_entry_get_selection_bound_location was not converting
priv->selection_bound to an index before feeding it to pango.
This was showing up as wrong handle positions when dragging
selections in password entries.
2012-09-02 17:15:18 -04:00
Matthias Clasen
ccc4f0f133 Ignore select_words when updating handle positions
This fixes problems where the handles can appear stuck when
double-click selecting and then dragging the handles.
2012-09-02 16:05:28 -04:00
Matthias Clasen
eecd228493 Minor fix for handle updates
Use a position that matches the mode, when updating handles.
2012-09-02 15:55:46 -04:00
Matthias Clasen
f69179b562 Remove an unused variable 2012-09-02 11:25:42 -04:00
Matthias Clasen
b79946956b Correct some editability/sensitivity checks
I was getting touch handles when double-clicking with a mouse.
And I was not getting touch handles when clicking around in a
textview that is sensitive, but not editable.

Correct these checks: A editable text view can have a cursor
and selections, and when in touchscreen mode, we need to show
handles for them.
2012-09-02 11:25:32 -04:00
Carlos Garnacho
1b5615df75 entry: If a recompute is pending, wait before updating handles
This avoids redundant window visibility and position changes
2012-08-29 16:04:54 +02:00
Carlos Garnacho
adce361ec5 textview: Unset handles when the buffer changes below these
On the situations where a different buffer is set, or the current
one is clobbered, the handles are unset. More effort could be indeed
done on setting those after the view has revalidated the contents
and the markers have valid coordinates again, although it's a bit of
a corner case.

Fixes the "hypertext" textview demo leaving handles at odd positions
after the buffer changed.
2012-08-27 17:18:21 +02:00
Carlos Garnacho
209e39ab8a textview: revert the code preventing initial backwards selection
With the handles being invariably set to the min/max selection positions,
it's no longer necessary to keep this invariant when starting a selection.

Note that dragging from the text handles themselves will still disallow
both handles from crossing.
2012-08-27 14:50:42 +02:00
Carlos Garnacho
3158630a68 entry: revert the code preventing initial backwards selection
With the handles being invariably set to the min/max selection positions,
it's no longer necessary to keep this invariant when starting a selection.

Note that dragging from the text handles themselves will still disallow
both handles from crossing.
2012-08-27 14:47:06 +02:00
Carlos Garnacho
2b02d97a93 textview: Update to new GtkTextHandlePosition values 2012-08-27 14:38:15 +02:00
Carlos Garnacho
f194d499c3 entry: update to new GtkTextHandlePosition values 2012-08-27 14:37:17 +02:00
Carlos Garnacho
3fd45e0267 GtkTextHandle: change selection semantics to start/end
Given text widgets don't behave consitently wrt cursor/selection
bound, nor are really expected to do so, change GtkTextHandle
positions to represent selection start/end. Text widgets are
expected to match buffer iters/positions with handle positions.
2012-08-27 14:29:53 +02:00
Carlos Garnacho
2a537b5b8b textview: Optionally update handles on focus in
This is so toplevels being moved/resized don't leave a selection
with no possibility of being further manipulated
2012-08-24 19:13:31 +02:00
Carlos Garnacho
89e420b390 entry: Optionally update handles on focus in
This is so toplevels being moved/resized don't leave a selection
with no possibility of being further manipulated
2012-08-24 19:12:27 +02:00
Carlos Garnacho
426e8c8c97 entry: Don't unset handles' mode on text DnD 2012-08-24 18:30:51 +02:00
Carlos Garnacho
78a8782b13 text: Set up envvar to test touch features
The GTK_TEST_TOUCHSCREEN_FEATURES envvar is now checked in entries
and textviews to allow testing of text handles with other kinds of
devices.
2012-08-24 17:02:53 +02:00
Carlos Garnacho
da8de20928 entry: Fix warnings on backwards touch selection from the last position
It shouldn't allow backwards selection, but still shouldn't go off bounds
trying to keep a minimum selection.
2012-08-24 17:02:52 +02:00
Carlos Garnacho
2eaabf5ad3 textview: Don't apply selection clamping on handle cursor mode 2012-08-24 17:02:52 +02:00
Carlos Garnacho
e299dfa462 entry: Don't apply selection clamping on handle cursor mode 2012-08-24 17:02:51 +02:00
Carlos Garnacho
edd21e61f2 scrolledwindow: don't capture events meant for non-child windows
GtkTextHandle creates temporary override redirect windows, but still
hook to the text widget for events, so those are effectively captured
by GtkScrolledWindow if a text widget is within it
2012-08-24 17:02:51 +02:00
Carlos Garnacho
80a89c5245 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.
2012-08-24 17:02:51 +02:00
Carlos Garnacho
739b6e1862 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)
2012-08-24 17:02:50 +02:00
Carlos Garnacho
03a7331c5c Add GtkTextHandle
This is a helper object to allow text widgets to implement
text selection on touch devices. It allows for both cursor
placement and text selection, displaying draggable handles
on/around the cursor and selection bound positions.
2012-08-24 17:02:50 +02:00
10 changed files with 1382 additions and 43 deletions

View File

@@ -394,8 +394,9 @@ gtk_appchooser_impl_c_sources = \
gtkappchooseronlinepk.c
endif
gtk_private_type_h_sources = \
gtkcsstypesprivate.h
gtk_private_type_h_sources = \
gtkcsstypesprivate.h \
gtktexthandleprivate.h
# GTK+ header files that don't get installed
@@ -525,6 +526,7 @@ gtk_private_h_sources = \
gtktextbtree.h \
gtktextbufferserialize.h \
gtktextchildprivate.h \
gtktexthandleprivate.h \
gtktextiterprivate.h \
gtktextmarkprivate.h \
gtktextsegment.h \
@@ -821,6 +823,7 @@ gtk_base_c_sources = \
gtktextbufferserialize.c \
gtktextchild.c \
gtktextdisplay.c \
gtktexthandle.c \
gtktextiter.c \
gtktextlayout.c \
gtktextmark.c \

View File

@@ -65,6 +65,7 @@
#include "gtkicontheme.h"
#include "gtkwidgetprivate.h"
#include "gtkstylecontextprivate.h"
#include "gtktexthandleprivate.h"
#include "a11y/gtkentryaccessible.h"
@@ -160,6 +161,8 @@ struct _GtkEntryPrivate
gchar *placeholder_text;
GtkTextHandle *text_handle;
gfloat xalign;
gint ascent; /* font ascent in pango units */
@@ -215,6 +218,7 @@ struct _GtkEntryPrivate
guint select_words : 1;
guint select_lines : 1;
guint truncate_multiline : 1;
guint update_handles_on_focus : 1;
};
struct _EntryIconInfo
@@ -314,6 +318,7 @@ enum {
};
static guint signals[LAST_SIGNAL] = { 0 };
static gboolean test_touchscreen = FALSE;
typedef enum {
CURSOR_STANDARD,
@@ -576,6 +581,13 @@ 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);
/* Completion */
static gint gtk_entry_completion_timeout (gpointer data);
static gboolean gtk_entry_completion_key_press (GtkWidget *widget,
@@ -1935,6 +1947,7 @@ gtk_entry_class_init (GtkEntryClass *class)
G_PARAM_DEPRECATED));
g_type_class_add_private (gobject_class, sizeof (GtkEntryPrivate));
test_touchscreen = g_getenv ("GTK_TEST_TOUCHSCREEN_FEATURES") != NULL;
gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_ENTRY_ACCESSIBLE);
}
@@ -2552,6 +2565,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
@@ -2789,6 +2806,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);
@@ -3010,6 +3028,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)
@@ -3083,7 +3104,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()
@@ -3111,6 +3132,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,7 +3874,108 @@ in_selection (GtkEntry *entry,
g_free (ranges);
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)
@@ -3920,7 +4043,14 @@ 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 = test_touchscreen ||
gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN;
priv->update_handles_on_focus = is_touchscreen;
priv->select_words = FALSE;
priv->select_lines = FALSE;
@@ -3998,7 +4128,12 @@ gtk_entry_button_press (GtkWidget *widget,
priv->drag_start_y = event->y;
}
else
gtk_editable_set_position (editable, tmp_pos);
{
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:
@@ -4009,6 +4144,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:
@@ -4019,6 +4157,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:
@@ -4088,9 +4228,16 @@ 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 (test_touchscreen ||
gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN)
_gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_CURSOR);
priv->in_drag = 0;
}
@@ -4212,13 +4359,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;
@@ -4254,13 +4410,20 @@ gtk_entry_motion_notify (GtkWidget *widget,
if (priv->current_pos != max)
pos = min;
}
gtk_entry_set_positions (entry, pos, bound);
}
else
gtk_entry_set_positions (entry, tmp_pos, -1);
/* Update touch handles' position */
if (test_touchscreen || 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;
}
@@ -4300,6 +4463,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)
{
@@ -4392,6 +4557,10 @@ gtk_entry_focus_in (GtkWidget *widget,
gtk_entry_check_cursor_blink (entry);
}
if (priv->update_handles_on_focus &&
gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), NULL, NULL))
_gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_SELECTION);
return FALSE;
}
@@ -4404,6 +4573,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));
@@ -5585,10 +5757,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;
@@ -6020,6 +6199,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);
}
}
void
_gtk_entry_reset_im_context (GtkEntry *entry)
{
@@ -6182,6 +6425,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)
{
@@ -6194,6 +6454,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;
@@ -6230,22 +6491,33 @@ gtk_entry_adjust_scroll (GtkEntry *entry)
priv->scroll_offset = CLAMP (priv->scroll_offset, min_offset, max_offset);
/* 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
* looks better to to me than confining the cursor inside the
* border entirely, though it means that the cursor gets one
* pixel closer to the edge of the widget on the right than
* on the left. This might need changing if one changed
* INNER_BORDER from 2 to 1, as one would do on a
* small-screen-real-estate display.
*
* We always make sure that the strong cursor is on screen, and
* put the weak cursor on screen if possible.
*/
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
* looks better to to me than confining the cursor inside the
* border entirely, though it means that the cursor gets one
* pixel closer to the edge of the widget on the right than
* on the left. This might need changing if one changed
* INNER_BORDER from 2 to 1, as one would do on a
* small-screen-real-estate display.
*
* 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);
}
gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &strong_x, &weak_x);
strong_xoffset = strong_x - priv->scroll_offset;
if (strong_xoffset < 0)
@@ -6272,6 +6544,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
@@ -6294,7 +6571,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);

View File

@@ -69,6 +69,7 @@ VOID:ENUM,FLOAT
VOID:ENUM,FLOAT,BOOLEAN
VOID:ENUM,INT
VOID:ENUM,INT,BOOLEAN
VOID:ENUM,INT,INT
VOID:ENUM,BOXED
VOID:ENUM,STRING
VOID:FLAGS

View File

@@ -29,6 +29,7 @@
#include <gdk/gdk.h>
#include "gtkcsstypesprivate.h"
#include "gtktexthandleprivate.h"
G_BEGIN_DECLS

View File

@@ -2907,6 +2907,9 @@ gtk_scrolled_window_captured_event (GtkWidget *widget,
gboolean retval = FALSE;
GtkScrolledWindowPrivate *priv = GTK_SCROLLED_WINDOW (widget)->priv;
if (gdk_window_get_window_type (event->any.window) == GDK_WINDOW_TEMP)
return FALSE;
switch (event->type)
{
case GDK_TOUCH_BEGIN:

View File

@@ -701,6 +701,18 @@ struct _GtkStyleContextClass
*/
#define GTK_STYLE_CLASS_LEVEL_BAR "level-bar"
/**
* GTK_STYLE_CLASS_CURSOR_HANDLE:
*
*/
#define GTK_STYLE_CLASS_CURSOR_HANDLE "cursor-handle"
/**
* GTK_STYLE_CLASS_INVERTED_CURSOR_HANDLE:
*
*/
#define GTK_STYLE_CLASS_INVERTED_CURSOR_HANDLE "inverted-cursor-handle"
/* Predefined set of widget regions */
/**

666
gtk/gtktexthandle.c Normal file
View File

@@ -0,0 +1,666 @@
/* GTK - The GIMP Toolkit
* Copyright © 2012 Carlos Garnacho <carlosg@gnome.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gtkprivatetypebuiltins.h"
#include "gtktexthandleprivate.h"
#include "gtkmarshalers.h"
#include "gtkprivate.h"
#include "gtkintl.h"
#include <gtk/gtk.h>
typedef struct _GtkTextHandlePrivate GtkTextHandlePrivate;
typedef struct _HandleWindow HandleWindow;
enum {
HANDLE_DRAGGED,
DRAG_FINISHED,
LAST_SIGNAL
};
enum {
PROP_0,
PROP_PARENT,
PROP_RELATIVE_TO
};
struct _HandleWindow
{
GdkWindow *window;
GdkRectangle pointing_to;
gint dx;
gint dy;
guint dragged : 1;
};
struct _GtkTextHandlePrivate
{
HandleWindow windows[2];
GtkWidget *parent;
GdkWindow *relative_to;
gulong draw_signal_id;
gulong event_signal_id;
gulong style_updated_id;
gulong composited_changed_id;
guint realized : 1;
guint mode : 2;
};
G_DEFINE_TYPE (GtkTextHandle, _gtk_text_handle, G_TYPE_OBJECT)
static guint signals[LAST_SIGNAL] = { 0 };
static void
_gtk_text_handle_get_size (GtkTextHandle *handle,
gint *width,
gint *height)
{
GtkTextHandlePrivate *priv;
gint w, h;
priv = handle->priv;
gtk_widget_style_get (priv->parent,
"text-handle-width", &w,
"text-handle-height", &h,
NULL);
if (width)
*width = w;
if (height)
*height = h;
}
static void
_gtk_text_handle_draw (GtkTextHandle *handle,
cairo_t *cr,
GtkTextHandlePosition pos)
{
GtkTextHandlePrivate *priv;
GtkStyleContext *context;
gint width, height;
priv = handle->priv;
cairo_save (cr);
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
cairo_set_source_rgba (cr, 0, 0, 0, 0);
cairo_paint (cr);
context = gtk_widget_get_style_context (priv->parent);
gtk_style_context_save (context);
gtk_style_context_add_class (context, GTK_STYLE_CLASS_ENTRY);
if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_END)
gtk_style_context_add_class (context, GTK_STYLE_CLASS_CURSOR_HANDLE);
else
gtk_style_context_add_class (context, GTK_STYLE_CLASS_INVERTED_CURSOR_HANDLE);
_gtk_text_handle_get_size (handle, &width, &height);
gtk_render_slider (context, cr, 0, 0, width, height,
GTK_ORIENTATION_HORIZONTAL);
gtk_style_context_restore (context);
cairo_restore (cr);
}
static void
_gtk_text_handle_update_shape (GtkTextHandle *handle,
GdkWindow *window)
{
GtkTextHandlePrivate *priv;
priv = handle->priv;
if (gtk_widget_is_composited (priv->parent))
gdk_window_shape_combine_region (window, NULL, 0, 0);
else
{
GtkTextHandlePosition pos;
cairo_surface_t *surface;
cairo_region_t *region;
cairo_t *cr;
surface =
gdk_window_create_similar_surface (window,
CAIRO_CONTENT_COLOR_ALPHA,
gdk_window_get_width (window),
gdk_window_get_height (window));
if (window == priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window)
pos = GTK_TEXT_HANDLE_POSITION_SELECTION_START;
else if (window == priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window)
pos = GTK_TEXT_HANDLE_POSITION_SELECTION_END;
cr = cairo_create (surface);
_gtk_text_handle_draw (handle, cr, pos);
cairo_destroy (cr);
region = gdk_cairo_region_create_from_surface (surface);
gdk_window_shape_combine_region (window, region, 0, 0);
cairo_surface_destroy (surface);
cairo_region_destroy (region);
}
}
static GdkWindow *
_gtk_text_handle_create_window (GtkTextHandle *handle)
{
GtkTextHandlePrivate *priv;
GdkRGBA bg = { 0, 0, 0, 0 };
GdkWindowAttr attributes;
GdkWindow *window;
GdkVisual *visual;
gint mask;
priv = handle->priv;
attributes.x = 0;
attributes.y = 0;
_gtk_text_handle_get_size (handle, &attributes.width, &attributes.height);
attributes.window_type = GDK_WINDOW_TEMP;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.event_mask = (GDK_EXPOSURE_MASK |
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_BUTTON1_MOTION_MASK);
mask = GDK_WA_X | GDK_WA_Y;
visual = gdk_screen_get_rgba_visual (gtk_widget_get_screen (priv->parent));
if (visual)
{
attributes.visual = visual;
mask |= GDK_WA_VISUAL;
}
window = gdk_window_new (NULL, &attributes, mask);
gdk_window_set_user_data (window, priv->parent);
gdk_window_set_background_rgba (window, &bg);
_gtk_text_handle_update_shape (handle, window);
return window;
}
static gboolean
gtk_text_handle_widget_draw (GtkWidget *widget,
cairo_t *cr,
GtkTextHandle *handle)
{
GtkTextHandlePrivate *priv;
GtkTextHandlePosition pos;
priv = handle->priv;
if (!priv->realized)
return FALSE;
if (gtk_cairo_should_draw_window (cr, priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window))
pos = GTK_TEXT_HANDLE_POSITION_SELECTION_START;
else if (gtk_cairo_should_draw_window (cr, priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window))
pos = GTK_TEXT_HANDLE_POSITION_SELECTION_END;
else
return FALSE;
_gtk_text_handle_draw (handle, cr, pos);
return TRUE;
}
static gboolean
gtk_text_handle_widget_event (GtkWidget *widget,
GdkEvent *event,
GtkTextHandle *handle)
{
GtkTextHandlePrivate *priv;
GtkTextHandlePosition pos;
priv = handle->priv;
if (event->any.window == priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window)
pos = GTK_TEXT_HANDLE_POSITION_SELECTION_START;
else if (event->any.window == priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window)
pos = GTK_TEXT_HANDLE_POSITION_SELECTION_END;
else
return FALSE;
if (event->type == GDK_BUTTON_PRESS)
{
priv->windows[pos].dx = event->button.x;
priv->windows[pos].dy = event->button.y;
priv->windows[pos].dragged = TRUE;
}
else if (event->type == GDK_BUTTON_RELEASE)
{
g_signal_emit (handle, signals[DRAG_FINISHED], 0, pos);
priv->windows[pos].dx = priv->windows[pos].dy = 0;
priv->windows[pos].dragged = FALSE;
}
else if (event->type == GDK_MOTION_NOTIFY && priv->windows[pos].dragged)
{
gint x, y, width, height;
_gtk_text_handle_get_size (handle, &width, &height);
gdk_window_get_origin (priv->relative_to, &x, &y);
x = event->motion.x_root - priv->windows[pos].dx + (width / 2) - x;
y = event->motion.y_root - priv->windows[pos].dy - y;
if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_START)
y += height;
g_signal_emit (handle, signals[HANDLE_DRAGGED], 0, pos, x, y);
}
return TRUE;
}
static void
_gtk_text_handle_update_window (GtkTextHandle *handle,
GtkTextHandlePosition pos)
{
GtkTextHandlePrivate *priv;
HandleWindow *handle_window;
gboolean visible;
gint x, y;
priv = handle->priv;
handle_window = &priv->windows[pos];
if (!handle_window->window)
return;
/* Get current state and destroy */
visible = gdk_window_is_visible (handle_window->window);
if (visible)
{
gint width;
_gtk_text_handle_get_size (handle, &width, NULL);
gdk_window_get_root_coords (handle_window->window,
width / 2, 0, &x, &y);
}
gdk_window_destroy (handle_window->window);
/* Create new window and apply old state */
handle_window->window = _gtk_text_handle_create_window (handle);
if (visible)
{
gdk_window_show (handle_window->window);
_gtk_text_handle_set_position (handle, pos,
&handle_window->pointing_to);
}
}
static void
_gtk_text_handle_update_windows (GtkTextHandle *handle)
{
_gtk_text_handle_update_window (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START);
_gtk_text_handle_update_window (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END);
}
static void
gtk_text_handle_constructed (GObject *object)
{
GtkTextHandlePrivate *priv;
priv = GTK_TEXT_HANDLE (object)->priv;
g_assert (priv->parent != NULL);
priv->draw_signal_id =
g_signal_connect (priv->parent, "draw",
G_CALLBACK (gtk_text_handle_widget_draw),
object);
priv->event_signal_id =
g_signal_connect (priv->parent, "event",
G_CALLBACK (gtk_text_handle_widget_event),
object);
priv->composited_changed_id =
g_signal_connect_swapped (priv->parent, "composited-changed",
G_CALLBACK (_gtk_text_handle_update_windows),
object);
priv->style_updated_id =
g_signal_connect_swapped (priv->parent, "style-updated",
G_CALLBACK (_gtk_text_handle_update_windows),
object);
}
static void
gtk_text_handle_finalize (GObject *object)
{
GtkTextHandlePrivate *priv;
priv = GTK_TEXT_HANDLE (object)->priv;
if (priv->relative_to)
g_object_unref (priv->relative_to);
if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window)
gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window)
gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window);
if (g_signal_handler_is_connected (priv->parent, priv->draw_signal_id))
g_signal_handler_disconnect (priv->parent, priv->draw_signal_id);
if (g_signal_handler_is_connected (priv->parent, priv->event_signal_id))
g_signal_handler_disconnect (priv->parent, priv->event_signal_id);
if (g_signal_handler_is_connected (priv->parent, priv->composited_changed_id))
g_signal_handler_disconnect (priv->parent, priv->composited_changed_id);
if (g_signal_handler_is_connected (priv->parent, priv->style_updated_id))
g_signal_handler_disconnect (priv->parent, priv->style_updated_id);
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)
{
GtkTextHandlePrivate *priv;
GtkTextHandle *handle;
handle = GTK_TEXT_HANDLE (object);
priv = handle->priv;
switch (prop_id)
{
case PROP_PARENT:
priv->parent = g_value_get_object (value);
break;
case PROP_RELATIVE_TO:
_gtk_text_handle_set_relative_to (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;
case PROP_RELATIVE_TO:
g_value_set_object (value, priv->relative_to);
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->constructed = gtk_text_handle_constructed;
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_FINISHED] =
g_signal_new (I_("drag-finished"),
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST, 0,
NULL, NULL,
g_cclosure_marshal_VOID__ENUM,
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));
g_object_class_install_property (object_class,
PROP_RELATIVE_TO,
g_param_spec_object ("relative-to",
P_("Window"),
P_("Window the coordinates are based upon"),
GDK_TYPE_WINDOW,
GTK_PARAM_READWRITE));
g_type_class_add_private (object_class, sizeof (GtkTextHandlePrivate));
}
static void
_gtk_text_handle_init (GtkTextHandle *handle)
{
handle->priv = G_TYPE_INSTANCE_GET_PRIVATE (handle,
GTK_TYPE_TEXT_HANDLE,
GtkTextHandlePrivate);
}
GtkTextHandle *
_gtk_text_handle_new (GtkWidget *parent)
{
return g_object_new (GTK_TYPE_TEXT_HANDLE,
"parent", parent,
NULL);
}
void
_gtk_text_handle_set_relative_to (GtkTextHandle *handle,
GdkWindow *window)
{
GtkTextHandlePrivate *priv;
g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
g_return_if_fail (!window || GDK_IS_WINDOW (window));
priv = handle->priv;
if (priv->relative_to)
{
gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window);
g_object_unref (priv->relative_to);
}
if (window)
{
priv->relative_to = g_object_ref (window);
priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window =
_gtk_text_handle_create_window (handle);
priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window =
_gtk_text_handle_create_window (handle);
priv->realized = TRUE;
}
else
{
priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window = NULL;
priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window = NULL;
priv->relative_to = NULL;
priv->realized = FALSE;
}
g_object_notify (G_OBJECT (handle), "relative-to");
}
void
_gtk_text_handle_set_mode (GtkTextHandle *handle,
GtkTextHandleMode mode)
{
GtkTextHandlePrivate *priv;
g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
priv = handle->priv;
if (priv->mode == mode)
return;
switch (mode)
{
case GTK_TEXT_HANDLE_MODE_CURSOR:
/* Only display one handle */
gdk_window_show (priv->windows[GTK_TEXT_HANDLE_POSITION_CURSOR].window);
gdk_window_hide (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
break;
case GTK_TEXT_HANDLE_MODE_SELECTION:
/* Display both handles */
gdk_window_show (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
gdk_window_show (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window);
break;
case GTK_TEXT_HANDLE_MODE_NONE:
default:
gdk_window_hide (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
gdk_window_hide (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window);
break;
}
priv->mode = mode;
}
GtkTextHandleMode
_gtk_text_handle_get_mode (GtkTextHandle *handle)
{
GtkTextHandlePrivate *priv;
g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), GTK_TEXT_HANDLE_MODE_NONE);
priv = handle->priv;
return priv->mode;
}
void
_gtk_text_handle_set_position (GtkTextHandle *handle,
GtkTextHandlePosition pos,
GdkRectangle *rect)
{
GtkTextHandlePrivate *priv;
gint x, y, width, height;
HandleWindow *handle_window;
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);
if (!priv->realized)
return;
if (priv->mode == GTK_TEXT_HANDLE_MODE_NONE ||
(priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR &&
pos != GTK_TEXT_HANDLE_POSITION_CURSOR))
return;
gdk_window_get_root_coords (priv->relative_to,
rect->x, rect->y,
&x, &y);
_gtk_text_handle_get_size (handle, &width, &height);
handle_window = &priv->windows[pos];
if (pos == GTK_TEXT_HANDLE_POSITION_CURSOR)
y += rect->height;
else
y -= height;
x -= width / 2;
gdk_window_move (handle_window->window, x, y);
handle_window->pointing_to = *rect;
}
void
_gtk_text_handle_set_visible (GtkTextHandle *handle,
GtkTextHandlePosition pos,
gboolean visible)
{
GtkTextHandlePrivate *priv;
GdkWindow *window;
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);
if (!priv->realized)
return;
window = priv->windows[pos].window;
if (!window)
return;
if (!visible)
gdk_window_hide (window);
else
{
if (priv->mode == GTK_TEXT_HANDLE_MODE_NONE ||
(priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR &&
pos != GTK_TEXT_HANDLE_POSITION_CURSOR))
return;
if (!gdk_window_is_visible (window))
gdk_window_show (window);
}
}
gboolean
_gtk_text_handle_get_is_dragged (GtkTextHandle *handle,
GtkTextHandlePosition pos)
{
GtkTextHandlePrivate *priv;
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;
}

View File

@@ -0,0 +1,90 @@
/* GTK - The GIMP Toolkit
* Copyright © 2012 Carlos Garnacho <carlosg@gnome.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __GTK_TEXT_HANDLE_PRIVATE_H__
#define __GTK_TEXT_HANDLE_PRIVATE_H__
#include <gtk/gtkwidget.h>
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;
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;
typedef enum
{
GTK_TEXT_HANDLE_MODE_NONE,
GTK_TEXT_HANDLE_MODE_CURSOR,
GTK_TEXT_HANDLE_MODE_SELECTION
} GtkTextHandleMode;
struct _GtkTextHandle
{
GObject parent_instance;
gpointer priv;
};
struct _GtkTextHandleClass
{
GObjectClass parent_class;
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);
void _gtk_text_handle_set_relative_to (GtkTextHandle *handle,
GdkWindow *window);
gboolean _gtk_text_handle_get_is_dragged (GtkTextHandle *handle,
GtkTextHandlePosition pos);
G_END_DECLS
#endif /* __GTK_TEXT_HANDLE_PRIVATE_H__ */

View File

@@ -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 update_handles_on_focus : 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;
@@ -559,6 +574,7 @@ static gint text_window_get_height (GtkTextWindow *win);
static guint signals[LAST_SIGNAL] = { 0 };
static gboolean test_touchscreen = FALSE;
G_DEFINE_TYPE_WITH_CODE (GtkTextView, gtk_text_view, GTK_TYPE_CONTAINER,
G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
@@ -1390,6 +1406,7 @@ gtk_text_view_class_init (GtkTextViewClass *klass)
g_type_class_add_private (gobject_class, sizeof (GtkTextViewPrivate));
gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_TEXT_VIEW_ACCESSIBLE);
test_touchscreen = g_getenv ("GTK_TEST_TOUCHSCREEN_FEATURES") != NULL;
}
static void
@@ -1456,6 +1473,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 +1579,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 +1631,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 +1643,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 +3135,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 +4137,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 +4179,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 +4448,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 +4747,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 +4784,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,10 +4809,16 @@ 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 = test_touchscreen ||
gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN;
if (event->type == GDK_BUTTON_PRESS)
{
gtk_text_view_reset_im_context (text_view);
priv->update_handles_on_focus = is_touchscreen;
if (gdk_event_triggers_context_menu ((GdkEvent *) event))
{
gtk_text_view_do_popup (text_view, event);
@@ -4638,7 +4851,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 +4891,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 +4899,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 +4938,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 +4953,19 @@ 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) &&
(test_touchscreen ||
gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN))
mode = GTK_TEXT_HANDLE_MODE_CURSOR;
else
mode = GTK_TEXT_HANDLE_MODE_NONE;
_gtk_text_view_update_handles (text_view, mode);
priv->pending_place_cursor_button = 0;
return FALSE;
}
}
@@ -4770,6 +5012,10 @@ gtk_text_view_focus_in_event (GtkWidget *widget, GdkEventFocus *event)
gtk_im_context_focus_in (priv->im_context);
}
if (priv->update_handles_on_focus &&
gtk_text_buffer_get_selection_bounds (get_buffer (text_view), NULL, NULL))
_gtk_text_view_update_handles (text_view, GTK_TEXT_HANDLE_MODE_SELECTION);
return FALSE;
}
@@ -4797,6 +5043,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 +6349,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 +6512,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 +6715,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 +6754,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 +6769,9 @@ selection_motion_event_handler (GtkTextView *text_view,
text_view->priv->scroll_timeout =
gdk_threads_add_timeout (50, selection_scan_timeout, text_view);
if (test_touchscreen || input_source == GDK_SOURCE_TOUCHSCREEN)
_gtk_text_view_update_handles (text_view, GTK_TEXT_HANDLE_MODE_SELECTION);
return TRUE;
}
@@ -7757,7 +8023,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 +8202,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

View File

@@ -3292,6 +3292,19 @@ gtk_widget_class_init (GtkWidgetClass *klass)
1, G_MAXINT, 16,
GTK_PARAM_READABLE));
gtk_widget_class_install_style_property (klass,
g_param_spec_int ("text-handle-width",
P_("Width of text selection handles"),
P_("Width of text selection handles"),
1, G_MAXINT, 16,
GTK_PARAM_READABLE));
gtk_widget_class_install_style_property (klass,
g_param_spec_int ("text-handle-height",
P_("Height of text selection handles"),
P_("Height of text selection handles"),
1, G_MAXINT, 20,
GTK_PARAM_READABLE));
g_type_class_add_private (klass, sizeof (GtkWidgetPrivate));
gtk_widget_class_set_accessible_type (klass, GTK_TYPE_WIDGET_ACCESSIBLE);