1/* GTK - The GIMP Toolkit
2 * Copyright © 2012 Carlos Garnacho <carlosg@gnome.org>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "config.h"
19
20#include "gtktexthandleprivate.h"
21
22#include "gtkbinlayout.h"
23#include "gtkcssboxesimplprivate.h"
24#include "gtkcssnodeprivate.h"
25#include "gtkgesturedrag.h"
26#include "gtkgizmoprivate.h"
27#include "gtkintl.h"
28#include "gtkmarshalers.h"
29#include "gtknativeprivate.h"
30#include "gtkprivatetypebuiltins.h"
31#include "gtkrendericonprivate.h"
32#include "gtkwidgetprivate.h"
33#include "gtkwindowprivate.h"
34
35#include "gdk/gdksurfaceprivate.h"
36
37enum {
38 DRAG_STARTED,
39 HANDLE_DRAGGED,
40 DRAG_FINISHED,
41 LAST_SIGNAL
42};
43
44struct _GtkTextHandle
45{
46 GtkWidget parent_instance;
47
48 GdkSurface *surface;
49 GskRenderer *renderer;
50 GtkEventController *controller;
51 GtkWidget *controller_widget;
52 GtkWidget *contents;
53
54 GdkRectangle pointing_to;
55 GtkBorder border;
56 int dx;
57 int dy;
58 guint role : 2;
59 guint dragged : 1;
60 guint mode_visible : 1;
61 guint user_visible : 1;
62 guint has_point : 1;
63};
64
65static void gtk_text_handle_native_interface_init (GtkNativeInterface *iface);
66
67static void handle_drag_begin (GtkGestureDrag *gesture,
68 double x,
69 double y,
70 GtkTextHandle *handle);
71static void handle_drag_update (GtkGestureDrag *gesture,
72 double offset_x,
73 double offset_y,
74 GtkWidget *widget);
75static void handle_drag_end (GtkGestureDrag *gesture,
76 double offset_x,
77 double offset_y,
78 GtkTextHandle *handle);
79
80G_DEFINE_TYPE_WITH_CODE (GtkTextHandle, gtk_text_handle, GTK_TYPE_WIDGET,
81 G_IMPLEMENT_INTERFACE (GTK_TYPE_NATIVE,
82 gtk_text_handle_native_interface_init))
83
84static guint signals[LAST_SIGNAL] = { 0 };
85
86static GdkSurface *
87gtk_text_handle_native_get_surface (GtkNative *native)
88{
89 return GTK_TEXT_HANDLE (ptr: native)->surface;
90}
91
92static GskRenderer *
93gtk_text_handle_native_get_renderer (GtkNative *native)
94{
95 return GTK_TEXT_HANDLE (ptr: native)->renderer;
96}
97
98static void
99gtk_text_handle_native_get_surface_transform (GtkNative *native,
100 double *x,
101 double *y)
102{
103 GtkCssBoxes css_boxes;
104 const graphene_rect_t *margin_rect;
105
106 gtk_css_boxes_init (boxes: &css_boxes, GTK_WIDGET (native));
107 margin_rect = gtk_css_boxes_get_margin_rect (boxes: &css_boxes);
108
109 *x = - margin_rect->origin.x;
110 *y = - margin_rect->origin.y;
111}
112
113static void
114gtk_text_handle_get_padding (GtkTextHandle *handle,
115 GtkBorder *padding)
116{
117 GtkWidget *widget = GTK_WIDGET (handle);
118 GtkCssStyle *style = gtk_css_node_get_style (cssnode: gtk_widget_get_css_node (widget));
119
120 padding->left = _gtk_css_number_value_get (number: style->size->padding_left, one_hundred_percent: 100);
121 padding->right = _gtk_css_number_value_get (number: style->size->padding_right, one_hundred_percent: 100);
122 padding->top = _gtk_css_number_value_get (number: style->size->padding_top, one_hundred_percent: 100);
123 padding->bottom = _gtk_css_number_value_get (number: style->size->padding_bottom, one_hundred_percent: 100);
124}
125
126static void
127gtk_text_handle_present_surface (GtkTextHandle *handle)
128{
129 GtkWidget *widget = GTK_WIDGET (handle);
130 GdkPopupLayout *layout;
131 GdkRectangle rect;
132 GtkRequisition req;
133 GtkWidget *parent;
134
135 gtk_widget_get_preferred_size (widget, NULL, natural_size: &req);
136 gtk_text_handle_get_padding (handle, padding: &handle->border);
137
138 parent = gtk_widget_get_parent (widget);
139 gtk_widget_get_surface_allocation (widget: parent, allocation: &rect);
140
141 rect.x += handle->pointing_to.x;
142 rect.y += handle->pointing_to.y + handle->pointing_to.height - handle->border.top;
143 rect.width = req.width - handle->border.left - handle->border.right;
144 rect.height = 1;
145
146 if (handle->role == GTK_TEXT_HANDLE_ROLE_CURSOR)
147 rect.x -= rect.width / 2;
148 else if ((handle->role == GTK_TEXT_HANDLE_ROLE_SELECTION_END &&
149 gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ||
150 (handle->role == GTK_TEXT_HANDLE_ROLE_SELECTION_START &&
151 gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL))
152 rect.x -= rect.width;
153
154 layout = gdk_popup_layout_new (anchor_rect: &rect,
155 rect_anchor: GDK_GRAVITY_SOUTH,
156 surface_anchor: GDK_GRAVITY_NORTH);
157 gdk_popup_layout_set_anchor_hints (layout,
158 anchor_hints: GDK_ANCHOR_FLIP_Y | GDK_ANCHOR_SLIDE_X);
159
160 gdk_popup_present (popup: GDK_POPUP (ptr: handle->surface),
161 MAX (req.width, 1),
162 MAX (req.height, 1),
163 layout);
164 gdk_popup_layout_unref (layout);
165}
166
167void
168gtk_text_handle_present (GtkTextHandle *handle)
169{
170 GtkWidget *widget = GTK_WIDGET (handle);
171
172 if (!_gtk_widget_get_alloc_needed (widget))
173 gtk_widget_ensure_allocate (widget);
174 else if (gtk_widget_get_visible (widget))
175 gtk_text_handle_present_surface (handle);
176}
177
178static void
179gtk_text_handle_native_layout (GtkNative *native,
180 int width,
181 int height)
182{
183 GtkWidget *widget = GTK_WIDGET (native);
184
185 if (_gtk_widget_get_alloc_needed (widget))
186 gtk_widget_allocate (widget, width, height, baseline: -1, NULL);
187 else
188 gtk_widget_ensure_allocate (widget);
189}
190
191static void
192gtk_text_handle_native_interface_init (GtkNativeInterface *iface)
193{
194 iface->get_surface = gtk_text_handle_native_get_surface;
195 iface->get_renderer = gtk_text_handle_native_get_renderer;
196 iface->get_surface_transform = gtk_text_handle_native_get_surface_transform;
197 iface->layout = gtk_text_handle_native_layout;
198}
199
200static gboolean
201surface_render (GdkSurface *surface,
202 cairo_region_t *region,
203 GtkTextHandle *handle)
204{
205 gtk_widget_render (GTK_WIDGET (handle), surface, region);
206 return TRUE;
207}
208
209static void
210surface_mapped_changed (GtkWidget *widget)
211{
212 GtkTextHandle *handle = GTK_TEXT_HANDLE (ptr: widget);
213
214 gtk_widget_set_visible (widget, visible: gdk_surface_get_mapped (surface: handle->surface));
215}
216
217static void
218gtk_text_handle_snapshot (GtkWidget *widget,
219 GtkSnapshot *snapshot)
220{
221 GtkCssStyle *style = gtk_css_node_get_style (cssnode: gtk_widget_get_css_node (widget));
222
223 gtk_css_style_snapshot_icon (style,
224 snapshot,
225 width: gtk_widget_get_width (widget),
226 height: gtk_widget_get_height (widget));
227
228 GTK_WIDGET_CLASS (gtk_text_handle_parent_class)->snapshot (widget, snapshot);
229}
230
231static void
232gtk_text_handle_realize (GtkWidget *widget)
233{
234 GtkTextHandle *handle = GTK_TEXT_HANDLE (ptr: widget);
235 GdkSurface *parent_surface;
236 GtkWidget *parent;
237
238 parent = gtk_widget_get_parent (widget);
239 parent_surface = gtk_native_get_surface (self: gtk_widget_get_native (widget: parent));
240
241 handle->surface = gdk_surface_new_popup (parent: parent_surface, FALSE);
242 gdk_surface_set_widget (self: handle->surface, widget);
243 gdk_surface_set_input_region (surface: handle->surface, region: cairo_region_create ());
244
245 g_signal_connect_swapped (handle->surface, "notify::mapped",
246 G_CALLBACK (surface_mapped_changed), widget);
247 g_signal_connect (handle->surface, "render", G_CALLBACK (surface_render), widget);
248
249 GTK_WIDGET_CLASS (gtk_text_handle_parent_class)->realize (widget);
250
251 handle->renderer = gsk_renderer_new_for_surface (surface: handle->surface);
252
253 gtk_native_realize (self: GTK_NATIVE (ptr: handle));
254}
255
256static void
257gtk_text_handle_unrealize (GtkWidget *widget)
258{
259 GtkTextHandle *handle = GTK_TEXT_HANDLE (ptr: widget);
260
261 gtk_native_unrealize (self: GTK_NATIVE (ptr: handle));
262
263 GTK_WIDGET_CLASS (gtk_text_handle_parent_class)->unrealize (widget);
264
265 gsk_renderer_unrealize (renderer: handle->renderer);
266 g_clear_object (&handle->renderer);
267
268 g_signal_handlers_disconnect_by_func (handle->surface, surface_render, widget);
269 g_signal_handlers_disconnect_by_func (handle->surface, surface_mapped_changed, widget);
270
271 gdk_surface_set_widget (self: handle->surface, NULL);
272 gdk_surface_destroy (surface: handle->surface);
273 g_clear_object (&handle->surface);
274}
275
276static void
277text_handle_set_up_gesture (GtkTextHandle *handle)
278{
279 GtkNative *native;
280
281 /* The drag gesture is hooked on the parent native */
282 native = gtk_widget_get_native (widget: gtk_widget_get_parent (GTK_WIDGET (handle)));
283 handle->controller_widget = GTK_WIDGET (native);
284
285 handle->controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ());
286 gtk_event_controller_set_propagation_phase (controller: handle->controller,
287 phase: GTK_PHASE_CAPTURE);
288 g_signal_connect (handle->controller, "drag-begin",
289 G_CALLBACK (handle_drag_begin), handle);
290 g_signal_connect (handle->controller, "drag-update",
291 G_CALLBACK (handle_drag_update), handle);
292 g_signal_connect (handle->controller, "drag-end",
293 G_CALLBACK (handle_drag_end), handle);
294
295 gtk_widget_add_controller (widget: handle->controller_widget, controller: handle->controller);
296}
297
298static void
299gtk_text_handle_map (GtkWidget *widget)
300{
301 GtkTextHandle *handle = GTK_TEXT_HANDLE (ptr: widget);
302
303 GTK_WIDGET_CLASS (gtk_text_handle_parent_class)->map (widget);
304
305 if (handle->has_point)
306 {
307 gtk_text_handle_present_surface (handle);
308 text_handle_set_up_gesture (handle);
309 }
310}
311
312static void
313gtk_text_handle_unmap (GtkWidget *widget)
314{
315 GtkTextHandle *handle = GTK_TEXT_HANDLE (ptr: widget);
316
317 GTK_WIDGET_CLASS (gtk_text_handle_parent_class)->unmap (widget);
318 gdk_surface_hide (surface: handle->surface);
319
320 if (handle->controller_widget)
321 {
322 gtk_widget_remove_controller (widget: handle->controller_widget,
323 controller: handle->controller);
324 handle->controller_widget = NULL;
325 handle->controller = NULL;
326 }
327}
328
329static void
330gtk_text_handle_dispose (GObject *object)
331{
332 GtkTextHandle *handle = GTK_TEXT_HANDLE (ptr: object);
333
334 g_clear_pointer (&handle->contents, gtk_widget_unparent);
335
336 G_OBJECT_CLASS (gtk_text_handle_parent_class)->dispose (object);
337}
338
339static void
340gtk_text_handle_class_init (GtkTextHandleClass *klass)
341{
342 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
343 GObjectClass *object_class = G_OBJECT_CLASS (klass);
344
345 object_class->dispose = gtk_text_handle_dispose;
346
347 widget_class->snapshot = gtk_text_handle_snapshot;
348 widget_class->realize = gtk_text_handle_realize;
349 widget_class->unrealize = gtk_text_handle_unrealize;
350 widget_class->map = gtk_text_handle_map;
351 widget_class->unmap = gtk_text_handle_unmap;
352
353 signals[HANDLE_DRAGGED] =
354 g_signal_new (I_("handle-dragged"),
355 G_OBJECT_CLASS_TYPE (object_class),
356 signal_flags: G_SIGNAL_RUN_LAST, class_offset: 0,
357 NULL, NULL,
358 c_marshaller: _gtk_marshal_VOID__INT_INT,
359 G_TYPE_NONE, n_params: 2,
360 G_TYPE_INT, G_TYPE_INT);
361 signals[DRAG_STARTED] =
362 g_signal_new (I_("drag-started"),
363 G_OBJECT_CLASS_TYPE (object_class),
364 signal_flags: G_SIGNAL_RUN_LAST, class_offset: 0,
365 NULL, NULL,
366 NULL,
367 G_TYPE_NONE, n_params: 0, G_TYPE_NONE);
368 signals[DRAG_FINISHED] =
369 g_signal_new (I_("drag-finished"),
370 G_OBJECT_CLASS_TYPE (object_class),
371 signal_flags: G_SIGNAL_RUN_LAST, class_offset: 0,
372 NULL, NULL,
373 NULL,
374 G_TYPE_NONE, n_params: 0, G_TYPE_NONE);
375
376 gtk_widget_class_set_css_name (widget_class, I_("cursor-handle"));
377 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
378}
379
380/* Relative to pointing_to x/y */
381static void
382handle_get_input_extents (GtkTextHandle *handle,
383 GtkBorder *border)
384{
385 GtkWidget *widget = GTK_WIDGET (handle);
386
387 if (handle->role == GTK_TEXT_HANDLE_ROLE_CURSOR)
388 {
389 border->left = (-gtk_widget_get_width (widget) / 2) - handle->border.left;
390 border->right = (gtk_widget_get_width (widget) / 2) + handle->border.right;
391 }
392 else if ((handle->role == GTK_TEXT_HANDLE_ROLE_SELECTION_END &&
393 gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ||
394 (handle->role == GTK_TEXT_HANDLE_ROLE_SELECTION_START &&
395 gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL))
396 {
397 border->left = -gtk_widget_get_width (widget) - handle->border.left;
398 border->right = handle->border.right;
399 }
400 else
401 {
402 border->left = -handle->border.left;
403 border->right = gtk_widget_get_width (widget) + handle->border.right;
404 }
405
406 border->top = - handle->border.top;
407 border->bottom = gtk_widget_get_height (widget) + handle->border.bottom;
408}
409
410static void
411handle_drag_begin (GtkGestureDrag *gesture,
412 double x,
413 double y,
414 GtkTextHandle *handle)
415{
416 GtkBorder input_extents;
417 double widget_x, widget_y;
418
419 x -= handle->pointing_to.x;
420 y -= handle->pointing_to.y;
421
422 /* Figure out if the coordinates fall into the handle input area, coordinates
423 * are relative to the parent widget.
424 */
425 handle_get_input_extents (handle, border: &input_extents);
426 gtk_widget_translate_coordinates (src_widget: handle->controller_widget,
427 dest_widget: gtk_widget_get_parent (GTK_WIDGET (handle)),
428 src_x: x, src_y: y, dest_x: &widget_x, dest_y: &widget_y);
429
430 if (widget_x < input_extents.left || widget_x >= input_extents.right ||
431 widget_y < input_extents.top || widget_y >= input_extents.bottom)
432 {
433 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_DENIED);
434 return;
435 }
436
437 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED);
438 /* Store untranslated coordinates here, so ::update does not need
439 * an extra translation
440 */
441 handle->dx = x;
442 handle->dy = y;
443 handle->dragged = TRUE;
444 g_signal_emit (instance: handle, signal_id: signals[DRAG_STARTED], detail: 0);
445}
446
447static void
448handle_drag_update (GtkGestureDrag *gesture,
449 double offset_x,
450 double offset_y,
451 GtkWidget *widget)
452{
453 GtkTextHandle *handle = GTK_TEXT_HANDLE (ptr: widget);
454 double start_x, start_y;
455 int x, y;
456
457 gtk_gesture_drag_get_start_point (gesture, x: &start_x, y: &start_y);
458
459 x = start_x + offset_x - handle->dx;
460 y = start_y + offset_y - handle->dy + (handle->pointing_to.height / 2);
461 g_signal_emit (instance: widget, signal_id: signals[HANDLE_DRAGGED], detail: 0, x, y);
462}
463
464static void
465handle_drag_end (GtkGestureDrag *gesture,
466 double offset_x,
467 double offset_y,
468 GtkTextHandle *handle)
469{
470 GdkEventSequence *sequence;
471 GtkEventSequenceState state;
472
473 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
474 state = gtk_gesture_get_sequence_state (GTK_GESTURE (gesture), sequence);
475
476 if (state == GTK_EVENT_SEQUENCE_CLAIMED)
477 g_signal_emit (instance: handle, signal_id: signals[DRAG_FINISHED], detail: 0);
478
479 handle->dragged = FALSE;
480}
481
482static void
483gtk_text_handle_update_for_role (GtkTextHandle *handle)
484{
485 GtkWidget *widget = GTK_WIDGET (handle);
486
487 if (handle->role == GTK_TEXT_HANDLE_ROLE_CURSOR)
488 {
489 gtk_widget_remove_css_class (widget, css_class: "top");
490 gtk_widget_add_css_class (widget, css_class: "bottom");
491 gtk_widget_add_css_class (widget, css_class: "insertion-cursor");
492 }
493 else if (handle->role == GTK_TEXT_HANDLE_ROLE_SELECTION_END)
494 {
495 gtk_widget_remove_css_class (widget, css_class: "top");
496 gtk_widget_add_css_class (widget, css_class: "bottom");
497 gtk_widget_remove_css_class (widget, css_class: "insertion-cursor");
498 }
499 else if (handle->role == GTK_TEXT_HANDLE_ROLE_SELECTION_START)
500 {
501 gtk_widget_add_css_class (widget, css_class: "top");
502 gtk_widget_remove_css_class (widget, css_class: "bottom");
503 gtk_widget_remove_css_class (widget, css_class: "insertion-cursor");
504 }
505
506 gtk_widget_queue_draw (widget);
507}
508
509static void
510gtk_text_handle_init (GtkTextHandle *handle)
511{
512 handle->contents = gtk_gizmo_new (css_name: "contents", NULL, NULL, NULL, NULL, NULL, NULL);
513 gtk_widget_set_can_target (widget: handle->contents, FALSE);
514 gtk_widget_set_parent (widget: handle->contents, GTK_WIDGET (handle));
515
516 gtk_text_handle_update_for_role (handle);
517}
518
519GtkTextHandle *
520gtk_text_handle_new (GtkWidget *parent)
521{
522 GtkTextHandle *handle;
523
524 handle = g_object_new (GTK_TYPE_TEXT_HANDLE, NULL);
525 gtk_widget_set_parent (GTK_WIDGET (handle), parent);
526
527 return handle;
528}
529
530void
531gtk_text_handle_set_role (GtkTextHandle *handle,
532 GtkTextHandleRole role)
533{
534 g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
535
536 if (handle->role == role)
537 return;
538
539 handle->role = role;
540 gtk_text_handle_update_for_role (handle);
541
542 if (gtk_widget_get_visible (GTK_WIDGET (handle)))
543 {
544 if (handle->has_point)
545 gtk_text_handle_present_surface (handle);
546 }
547}
548
549GtkTextHandleRole
550gtk_text_handle_get_role (GtkTextHandle *handle)
551{
552 g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), GTK_TEXT_HANDLE_ROLE_CURSOR);
553
554 return handle->role;
555}
556
557void
558gtk_text_handle_set_position (GtkTextHandle *handle,
559 const GdkRectangle *rect)
560{
561 g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
562
563 if (handle->pointing_to.x == rect->x &&
564 handle->pointing_to.y == rect->y &&
565 handle->pointing_to.width == rect->width &&
566 handle->pointing_to.height == rect->height)
567 return;
568
569 handle->pointing_to = *rect;
570 handle->has_point = TRUE;
571
572 if (gtk_widget_is_visible (GTK_WIDGET (handle)))
573 gtk_text_handle_present_surface (handle);
574}
575
576gboolean
577gtk_text_handle_get_is_dragged (GtkTextHandle *handle)
578{
579 g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), FALSE);
580
581 return handle->dragged;
582}
583

source code of gtk/gtk/gtktexthandle.c