Compare commits

...

18 Commits

Author SHA1 Message Date
Carlos Garnacho
9c46630553 textview: Unset selection popover on !touchscreen when clicking outside
The intent is hiding it on interaction from other than a touchscreen,
actually do that here.
2014-12-18 18:13:39 +01:00
Carlos Garnacho
e0ac74b50e texthandle: check there is a widget to set state on
The handle widgets might be uninitialized, just skip those if they aren't
there yet.
2014-12-18 14:52:11 +01:00
Carlos Garnacho
7a574f6ba8 textview: hide the selection popover when moving the cursor
It should appear on second click on the cursor position, as if previously
hidden.
2014-12-18 14:46:17 +01:00
Carlos Garnacho
b8d160c637 textview: Do not unset edition popover visibility on drag end
The code here is only meant to show the popover after text selection has
finished, there is no need to toggle it off here, plus it interacts with
long presses.
2014-12-18 13:55:22 +01:00
Carlos Garnacho
c1cff4f846 textview: raise a bit magnifier popover
Not a good hint if the finger covers bits of the magnifier itself, this
seems enough to at least only cover the tail.
2014-12-18 13:55:22 +01:00
Carlos Garnacho
dc699fd02a textview: just show one line in magnifier 2014-12-18 13:55:22 +01:00
Carlos Garnacho
9798a80d85 textview: Fix magnifier positioning/sizing nitpicks
The magnifier width is now set to at least 80px, and made to show at least
3 lines of text. The popover is made again to snap to line height, but is
shown closer to the selected line.

This can be squashed with 53f50f6e4cd9
2014-12-18 13:55:22 +01:00
Carlos Garnacho
87fab63a5c textview: set magnifier magnification to 1.5x 2014-12-18 13:55:22 +01:00
Carlos Garnacho
2d7fe86c7e textview: make magnifier size dependent on text size
The magnifier is now set enough height to show the line being currently
manipulated, and the shown coordinates are made relative to the iter
position on the Y axis so it always guarantees to show the full line.

The popover positioning (and the X axis of the shown content) are made
relative to the touch position, with an extra margin accounting for the
finger, similarly to how it was done previously.
2014-12-18 13:55:22 +01:00
Carlos Garnacho
88c15def46 Adwaita: Make text handles a bit larger
This improves visibility, and interaction, as the invisible rect around is
dependent on the visible handle size
2014-12-18 13:55:22 +01:00
Carlos Garnacho
faab5ecbd9 textview: Implement friendlier touch selection
- It is not possible anymore to trigger text DnD through touch, pressing
  and dragging from within the selection will instead extend it. Text
  shrinking is still available through the handles
- The selection mode for touch is per-word, char-level manipulation is
  still available through the handles.
- Tapping within the selection will bring in text handles, and toggle
  text selection popover.
2014-12-18 13:55:21 +01:00
Carlos Garnacho
100e57c326 textview: Make "extend selection" only extend
This mode could also shrink the selection, plus the starting point would
seem somewhat arbitrary (actually dependent on the dragging direction of
the last selection).

Made this mode more consistent by only allowing it to extend the selection,
only in one direction for each operation, and so it keeps the current
selection as a minimum.
2014-12-18 13:55:21 +01:00
Carlos Garnacho
35715a449e textview: Pass a boolean telling whether a selection drag should extend
Instead of passing a GdkEvent and let the function figure out whether the
selection should be extended, let that to the caller and just pass a
boolean here.
2014-12-18 13:55:21 +01:00
Carlos Garnacho
8305d93e8b textview: Show magnifier on top of current line
The X coordinate is dependent on touch position, but Y is stepped.
2014-12-18 13:55:20 +01:00
Carlos Garnacho
fa230ffb11 texthandle: Use active state when handles are being dragged
The active state flags is set on both handles when this happens.
2014-12-18 13:55:20 +01:00
Carlos Garnacho
cceea46f36 texthandle: Use the handle widget style context for rendering
Using the parent widget context is a leftover of the pre-popover
implementation, which used GdkWindows directly. This will make the context
reflect widget state, at the expense of changing the selector paths
that used to match the handles.
2014-12-18 13:55:20 +01:00
Carlos Garnacho
b97f276ee6 gesturesingle: check for the sequence being handled, not just the event
Checking the return value was valid for most gestures, but
GtkGestureLongPress, where the first press triggers internally an action,
but does nothing for the sequence to be claimed/denied, FALSE was eventually
returned, and the button/sequence functions would be incorrect when
::pressed is emitted.

So check that the sequence is being handled by the gesture, this is more
desirable than the return value as it's independent of sequence state,
and still will be FALSE for the cases we want to catch here.
2014-12-18 13:55:20 +01:00
Carlos Garnacho
a167753741 window: Let popovers use the shadow area 2014-12-18 13:55:20 +01:00
7 changed files with 164 additions and 71 deletions

View File

@@ -220,7 +220,8 @@ gtk_gesture_single_handle_event (GtkEventController *controller,
if (sequence == priv->current_sequence &&
(event->type == GDK_BUTTON_RELEASE || event->type == GDK_TOUCH_END))
priv->current_button = 0;
else if (!retval)
else if (priv->current_sequence == sequence &&
!gtk_gesture_handles_sequence (GTK_GESTURE (controller), sequence))
{
if (button == priv->current_button && event->type == GDK_BUTTON_PRESS)
priv->current_button = 0;

View File

@@ -101,7 +101,7 @@ _gtk_text_handle_draw (GtkTextHandle *handle,
gint width, height;
priv = handle->priv;
context = gtk_widget_get_style_context (priv->parent);
context = gtk_widget_get_style_context (priv->windows[pos].widget);
_gtk_text_handle_get_size (handle, &width, &height);
cairo_save (cr);
@@ -166,6 +166,25 @@ gtk_text_handle_widget_draw (GtkWidget *widget,
return TRUE;
}
static void
gtk_text_handle_set_state (GtkTextHandle *handle,
GtkStateFlags state)
{
GtkTextHandlePrivate *priv;
gint i;
priv = handle->priv;
for (i = 0; i <= GTK_TEXT_HANDLE_POSITION_SELECTION_START; i++)
{
if (!priv->windows[i].widget)
continue;
gtk_widget_set_state_flags (priv->windows[i].widget, state, TRUE);
gtk_widget_queue_draw (priv->windows[i].widget);
}
}
static gboolean
gtk_text_handle_widget_event (GtkWidget *widget,
GdkEvent *event,
@@ -185,11 +204,13 @@ gtk_text_handle_widget_event (GtkWidget *widget,
priv->windows[pos].dx = event->button.x;
priv->windows[pos].dy = event->button.y;
priv->windows[pos].dragged = TRUE;
gtk_text_handle_set_state (handle, GTK_STATE_FLAG_ACTIVE);
}
else if (event->type == GDK_BUTTON_RELEASE)
{
g_signal_emit (handle, signals[DRAG_FINISHED], 0, pos);
priv->windows[pos].dragged = FALSE;
gtk_text_handle_set_state (handle, GTK_STATE_FLAG_NORMAL);
}
else if (event->type == GDK_MOTION_NOTIFY &&
event->motion.state & GDK_BUTTON1_MASK &&
@@ -221,6 +242,12 @@ static void
gtk_text_handle_widget_style_updated (GtkWidget *widget,
GtkTextHandle *handle)
{
GtkTextHandlePrivate *priv;
priv = handle->priv;
gtk_style_context_set_parent (gtk_widget_get_style_context (widget),
gtk_widget_get_style_context (priv->parent));
_gtk_text_handle_update (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START);
_gtk_text_handle_update (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END);
}
@@ -254,6 +281,9 @@ _gtk_text_handle_ensure_widget (GtkTextHandle *handle,
priv->windows[pos].widget = g_object_ref_sink (widget);
window = gtk_widget_get_ancestor (priv->parent, GTK_TYPE_WINDOW);
_gtk_window_add_popover (GTK_WINDOW (window), widget);
gtk_style_context_set_parent (gtk_widget_get_style_context (widget),
gtk_widget_get_style_context (priv->parent));
}
return priv->windows[pos].widget;

View File

@@ -458,7 +458,7 @@ static void gtk_text_view_check_keymap_direction (GtkTextView *text_v
static void gtk_text_view_start_selection_drag (GtkTextView *text_view,
const GtkTextIter *iter,
SelectionGranularity granularity,
const GdkEvent *event);
gboolean extends);
static gboolean gtk_text_view_end_selection_drag (GtkTextView *text_view);
static void gtk_text_view_start_selection_dnd (GtkTextView *text_view,
const GtkTextIter *iter,
@@ -1639,8 +1639,7 @@ _gtk_text_view_ensure_magnifier (GtkTextView *text_view)
return;
priv->magnifier = _gtk_magnifier_new (GTK_WIDGET (text_view));
gtk_widget_set_size_request (priv->magnifier, 100, 60);
_gtk_magnifier_set_magnification (GTK_MAGNIFIER (priv->magnifier), 2.0);
_gtk_magnifier_set_magnification (GTK_MAGNIFIER (priv->magnifier), 1.5);
priv->magnifier_popover = gtk_popover_new (GTK_WIDGET (text_view));
gtk_style_context_add_class (gtk_widget_get_style_context (priv->magnifier_popover),
GTK_STYLE_CLASS_OSD);
@@ -4719,32 +4718,42 @@ gtk_text_view_set_handle_position (GtkTextView *text_view,
static void
gtk_text_view_show_magnifier (GtkTextView *text_view,
GtkTextIter *iter,
gint x,
gint y)
{
cairo_rectangle_int_t rect;
GtkTextViewPrivate *priv;
GtkAllocation allocation;
GtkRequisition req;
_gtk_text_view_ensure_magnifier (text_view);
gtk_widget_get_allocation (GTK_WIDGET (text_view), &allocation);
#define RECT_WIDTH 40
#define N_LINES 1
priv = text_view->priv;
x = CLAMP (x, 0, allocation.width);
y = CLAMP (y, 0, allocation.height);
rect.x = x - (RECT_WIDTH / 2);
rect.y = y - (RECT_WIDTH / 2);
rect.width = rect.height = RECT_WIDTH;
_text_window_to_widget_coords (text_view, &rect.x, &rect.y);
_gtk_text_view_ensure_magnifier (text_view);
_gtk_magnifier_set_coords (GTK_MAGNIFIER (priv->magnifier), x, y);
/* Set size/content depending on iter rect */
gtk_text_view_get_iter_location (text_view, iter,
(GdkRectangle *) &rect);
rect.x = x + priv->xoffset;
gtk_text_view_buffer_to_window_coords (text_view, GTK_TEXT_WINDOW_TEXT,
rect.x, rect.y, &rect.x, &rect.y);
_text_window_to_widget_coords (text_view, &rect.x, &rect.y);
req.height = rect.height * N_LINES *
_gtk_magnifier_get_magnification (GTK_MAGNIFIER (priv->magnifier));
req.width = MAX ((req.height * 4) / 3, 80);
gtk_widget_set_size_request (priv->magnifier, req.width, req.height);
_gtk_magnifier_set_coords (GTK_MAGNIFIER (priv->magnifier),
rect.x, rect.y + rect.height / 2);
rect.y += rect.height / 4;
rect.height -= rect.height / 4;
gtk_popover_set_pointing_to (GTK_POPOVER (priv->magnifier_popover),
&rect);
gtk_widget_show (priv->magnifier_popover);
#undef RECT_WIDTH
#undef N_LINES
}
static void
@@ -4834,7 +4843,10 @@ gtk_text_view_handle_dragged (GtkTextHandle *handle,
gtk_text_buffer_get_selection_bound (buffer));
}
gtk_text_view_show_magnifier (text_view, x, y);
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);
}
static void
@@ -5171,6 +5183,15 @@ gtk_text_view_multipress_gesture_pressed (GtkGestureMultiPress *gesture,
else if (button == GDK_BUTTON_PRIMARY)
{
GtkTextHandleMode handle_mode = GTK_TEXT_HANDLE_MODE_NONE;
gboolean extends = FALSE;
GdkModifierType state;
gdk_event_get_state (event, &state);
if (state &
gtk_widget_get_modifier_mask (GTK_WIDGET (text_view),
GDK_MODIFIER_INTENT_EXTEND_SELECTION))
extends = TRUE;
switch (n_press)
{
@@ -5187,23 +5208,45 @@ gtk_text_view_multipress_gesture_pressed (GtkGestureMultiPress *gesture,
if (gtk_text_buffer_get_selection_bounds (get_buffer (text_view),
&start, &end) &&
gtk_text_iter_in_range (&iter, &start, &end) &&
!(event->button.state &
gtk_widget_get_modifier_mask (GTK_WIDGET (text_view),
GDK_MODIFIER_INTENT_EXTEND_SELECTION)))
gtk_text_iter_in_range (&iter, &start, &end) && !extends)
{
/* Claim the sequence on the drag gesture, but attach no selection data */
gtk_gesture_set_state (priv->drag_gesture,
GTK_EVENT_SEQUENCE_CLAIMED);
if (is_touchscreen)
{
if (!priv->selection_bubble ||
!gtk_widget_get_visible (priv->selection_bubble))
gtk_text_view_selection_bubble_popup_set (text_view);
else
gtk_text_view_selection_bubble_popup_unset (text_view);
handle_mode = GTK_TEXT_HANDLE_MODE_SELECTION;
}
else
{
/* Claim the sequence on the drag gesture, but attach no
* selection data, this is a special case to start DnD.
*/
gtk_gesture_set_state (priv->drag_gesture,
GTK_EVENT_SEQUENCE_CLAIMED);
}
break;
}
else
{
gtk_text_view_selection_bubble_popup_unset (text_view);
gtk_text_view_start_selection_drag (text_view, &iter,
SELECT_CHARACTERS, event);
if (is_touchscreen)
gtk_text_buffer_place_cursor (get_buffer (text_view), &iter);
else
gtk_text_view_start_selection_drag (text_view, &iter,
SELECT_CHARACTERS, extends);
}
break;
}
case 2:
case 3:
if (is_touchscreen)
break;
handle_mode = GTK_TEXT_HANDLE_MODE_SELECTION;
gtk_text_view_end_selection_drag (text_view);
@@ -5211,7 +5254,7 @@ gtk_text_view_multipress_gesture_pressed (GtkGestureMultiPress *gesture,
&iter, NULL, NULL);
gtk_text_view_start_selection_drag (text_view, &iter,
n_press == 2 ? SELECT_WORDS : SELECT_LINES,
event);
extends);
break;
default:
break;
@@ -7089,6 +7132,7 @@ gtk_text_view_drag_gesture_update (GtkGestureDrag *gesture,
const GdkEvent *event;
SelectionData *data;
GdkDevice *device;
GtkTextIter cursor;
data = g_object_get_qdata (G_OBJECT (gesture), quark_text_selection_data);
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
@@ -7096,6 +7140,15 @@ gtk_text_view_drag_gesture_update (GtkGestureDrag *gesture,
drag_gesture_get_text_window_coords (gesture, text_view,
&start_x, &start_y, &x, &y);
device = gdk_event_get_source_device (event);
is_touchscreen = test_touchscreen ||
(gtk_get_debug_flags () & GTK_DEBUG_TOUCHSCREEN) != 0 ||
gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN;
get_iter_from_gesture (text_view, text_view->priv->drag_gesture,
&cursor, NULL, NULL);
if (!data)
{
/* If no data is attached, the initial press happened within the current
@@ -7104,39 +7157,43 @@ gtk_text_view_drag_gesture_update (GtkGestureDrag *gesture,
if (gtk_drag_check_threshold (GTK_WIDGET (text_view),
start_x, start_y, x, y))
{
GtkTextIter iter;
gint buffer_x, buffer_y;
if (!is_touchscreen)
{
GtkTextIter iter;
gint buffer_x, buffer_y;
gtk_text_view_window_to_buffer_coords (text_view,
GTK_TEXT_WINDOW_TEXT,
start_x, start_y,
&buffer_x,
&buffer_y);
gtk_text_view_window_to_buffer_coords (text_view,
GTK_TEXT_WINDOW_TEXT,
start_x, start_y,
&buffer_x,
&buffer_y);
gtk_text_layout_get_iter_at_pixel (text_view->priv->layout,
&iter, buffer_x, buffer_y);
gtk_text_layout_get_iter_at_pixel (text_view->priv->layout,
&iter, buffer_x, buffer_y);
gtk_text_view_start_selection_dnd (text_view, &iter, event,
start_x, start_y);
gtk_text_view_start_selection_dnd (text_view, &iter, event,
start_x, start_y);
return;
}
else
{
gtk_text_view_start_selection_drag (text_view, &cursor,
SELECT_WORDS, TRUE);
data = g_object_get_qdata (G_OBJECT (gesture), quark_text_selection_data);
}
}
return;
else
return;
}
/* Text selection */
device = gdk_event_get_source_device (event);
is_touchscreen = test_touchscreen ||
(gtk_get_debug_flags () & GTK_DEBUG_TOUCHSCREEN) != 0 ||
gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN;
if (data->granularity == SELECT_CHARACTERS)
{
move_mark_to_pointer_and_scroll (text_view, "insert");
}
else
{
GtkTextIter cursor, start, end;
GtkTextIter start, end;
GtkTextIter orig_start, orig_end;
GtkTextBuffer *buffer;
@@ -7151,10 +7208,11 @@ gtk_text_view_drag_gesture_update (GtkGestureDrag *gesture,
extend_selection (text_view, data->granularity, &cursor, &start, &end);
/* either the selection extends to the front, or end (or not) */
if (gtk_text_iter_compare (&cursor, &orig_start) < 0)
gtk_text_buffer_select_range (buffer, &start, &orig_end);
else
gtk_text_buffer_select_range (buffer, &end, &orig_start);
if (gtk_text_iter_compare (&orig_start, &start) < 0)
start = orig_start;
if (gtk_text_iter_compare (&orig_end, &end) > 0)
end = orig_end;
gtk_text_buffer_select_range (buffer, &start, &end);
gtk_text_view_scroll_mark_onscreen (text_view,
gtk_text_buffer_get_insert (buffer));
@@ -7178,7 +7236,7 @@ gtk_text_view_drag_gesture_update (GtkGestureDrag *gesture,
{
_gtk_text_view_ensure_text_handles (text_view);
gtk_text_view_update_handles (text_view, GTK_TEXT_HANDLE_MODE_SELECTION);
gtk_text_view_show_magnifier (text_view, x, y);
gtk_text_view_show_magnifier (text_view, &cursor, x, y);
}
}
@@ -7227,13 +7285,11 @@ gtk_text_view_drag_gesture_end (GtkGestureDrag *gesture,
(gtk_get_debug_flags () & GTK_DEBUG_TOUCHSCREEN) != 0 ||
gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN;
if (priv->selection_bubble &&
gtk_widget_get_visible (priv->selection_bubble))
gtk_text_view_selection_bubble_popup_unset (text_view);
else if (is_touchscreen)
if (!clicked_in_selection && is_touchscreen &&
(!priv->selection_bubble || !gtk_widget_get_visible (priv->selection_bubble)))
gtk_text_view_selection_bubble_popup_set (text_view);
if (clicked_in_selection &&
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;
@@ -7263,14 +7319,13 @@ static void
gtk_text_view_start_selection_drag (GtkTextView *text_view,
const GtkTextIter *iter,
SelectionGranularity granularity,
const GdkEvent *event)
gboolean extend)
{
GtkTextViewPrivate *priv;
GtkTextIter cursor, ins, bound;
GtkTextIter orig_start, orig_end;
GtkTextBuffer *buffer;
SelectionData *data;
GdkModifierType state;
priv = text_view->priv;
data = g_slice_new0 (SelectionData);
@@ -7283,11 +7338,8 @@ gtk_text_view_start_selection_drag (GtkTextView *text_view,
orig_start = ins;
orig_end = bound;
gdk_event_get_state (event, &state);
if (state &
gtk_widget_get_modifier_mask (GTK_WIDGET (text_view),
GDK_MODIFIER_INTENT_EXTEND_SELECTION))
if (extend)
{
/* Extend selection */
GtkTextIter old_ins, old_bound;
@@ -7307,15 +7359,18 @@ gtk_text_view_start_selection_drag (GtkTextView *text_view,
gtk_text_iter_compare (&old_ins, &old_bound) <= 0))
{
bound = old_end;
orig_start = old_end;
orig_end = old_end;
}
else
{
ins = bound;
bound = old_start;
orig_end = bound;
orig_start = bound;
}
/* Store any previous selection */
if (gtk_text_iter_compare (&old_start, &old_end) != 0)
{
orig_start = old_ins;
orig_end = old_bound;
}
}

View File

@@ -6248,7 +6248,7 @@ popover_get_rect (GtkWindowPopover *popover,
}
else
rect->y = CLAMP (popover->rect.y + (popover->rect.height / 2) -
(req.height / 2), win_alloc.y, win_alloc.y + win_alloc.height - req.height);
(req.height / 2), 0, win_alloc.y + win_alloc.height + win_border.top - req.height);
if ((popover->pos == GTK_POS_LEFT) ==
(gtk_widget_get_direction (popover->widget) == GTK_TEXT_DIR_LTR))
@@ -6280,7 +6280,7 @@ popover_get_rect (GtkWindowPopover *popover,
}
else
rect->x = CLAMP (popover->rect.x + (popover->rect.width / 2) -
(req.width / 2), win_alloc.x, win_alloc.x + win_alloc.width - req.width);
(req.width / 2), 0, win_alloc.x + win_alloc.width + win_border.right - req.width);
if (popover->pos == GTK_POS_TOP)
{

View File

@@ -35,6 +35,9 @@ $ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
-GtkWidget-focus-padding: 2; // FIXME: do we still need these?
-GtkWidget-focus-line-width: 1; //
-GtkWidget-text-handle-width: 20;
-GtkWidget-text-handle-height: 20;
-GtkDialog-button-spacing: 4;
-GtkDialog-action-area-border: 0;

View File

@@ -16,6 +16,8 @@
-GtkWidget-visited-link-color: #2a76c6;
-GtkWidget-focus-padding: 2;
-GtkWidget-focus-line-width: 1;
-GtkWidget-text-handle-width: 20;
-GtkWidget-text-handle-height: 20;
-GtkDialog-button-spacing: 4;
-GtkDialog-action-area-border: 0;
-GtkStatusbar-shadow-type: none;

View File

@@ -16,6 +16,8 @@
-GtkWidget-visited-link-color: #215d9c;
-GtkWidget-focus-padding: 2;
-GtkWidget-focus-line-width: 1;
-GtkWidget-text-handle-width: 20;
-GtkWidget-text-handle-height: 20;
-GtkDialog-button-spacing: 4;
-GtkDialog-action-area-border: 0;
-GtkStatusbar-shadow-type: none;