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 #include "gtkprivatetypebuiltins.h"
20 #include "gtktexthandleprivate.h"
21 #include "gtkmarshalers.h"
22 #include "gtkprivate.h"
23 #include "gtkwindowprivate.h"
24 #include "gtkcssnodeprivate.h"
25 #include "gtkwidgetprivate.h"
26 #include "gtkintl.h"
27
28 #include <gtk/gtk.h>
29
30 typedef struct _GtkTextHandlePrivate GtkTextHandlePrivate;
31 typedef struct _HandleWindow HandleWindow;
32
33 enum {
34 DRAG_STARTED,
35 HANDLE_DRAGGED,
36 DRAG_FINISHED,
37 LAST_SIGNAL
38 };
39
40 enum {
41 PROP_0,
42 PROP_PARENT
43 };
44
45 struct _HandleWindow
46 {
47 GtkWidget *widget;
48 GdkRectangle pointing_to;
49 GtkBorder border;
50 gint dx;
51 gint dy;
52 GtkTextDirection dir;
53 guint dragged : 1;
54 guint mode_visible : 1;
55 guint user_visible : 1;
56 guint has_point : 1;
57 };
58
59 struct _GtkTextHandlePrivate
60 {
61 HandleWindow windows[2];
62 GtkWidget *parent;
63 GtkScrollable *parent_scrollable;
64 GtkAdjustment *vadj;
65 GtkAdjustment *hadj;
66 guint hierarchy_changed_id;
67 guint scrollable_notify_id;
68 guint mode : 2;
69 };
70
71 G_DEFINE_TYPE_WITH_PRIVATE (GtkTextHandle, _gtk_text_handle, G_TYPE_OBJECT)
72
73 static guint signals[LAST_SIGNAL] = { 0 };
74
75 static void _gtk_text_handle_update (GtkTextHandle *handle,
76 GtkTextHandlePosition pos);
77
78 static void
_gtk_text_handle_get_size(GtkTextHandle * handle,gint * width,gint * height)79 _gtk_text_handle_get_size (GtkTextHandle *handle,
80 gint *width,
81 gint *height)
82 {
83 GtkTextHandlePrivate *priv;
84 gint w, h;
85
86 priv = handle->priv;
87
88 gtk_widget_style_get (priv->parent,
89 "text-handle-width", &w,
90 "text-handle-height", &h,
91 NULL);
92 if (width)
93 *width = w;
94
95 if (height)
96 *height = h;
97 }
98
99 static void
_gtk_text_handle_draw(GtkTextHandle * handle,cairo_t * cr,GtkTextHandlePosition pos)100 _gtk_text_handle_draw (GtkTextHandle *handle,
101 cairo_t *cr,
102 GtkTextHandlePosition pos)
103 {
104 GtkTextHandlePrivate *priv;
105 HandleWindow *handle_window;
106 GtkStyleContext *context;
107 gint width, height;
108
109 priv = handle->priv;
110 handle_window = &priv->windows[pos];
111
112 context = gtk_widget_get_style_context (handle_window->widget);
113 _gtk_text_handle_get_size (handle, &width, &height);
114
115 cairo_save (cr);
116 cairo_translate (cr, handle_window->border.left, handle_window->border.top);
117
118 gtk_render_handle (context, cr, 0, 0, width, height);
119
120 cairo_restore (cr);
121 }
122
123 static gint
_text_handle_pos_from_widget(GtkTextHandle * handle,GtkWidget * widget)124 _text_handle_pos_from_widget (GtkTextHandle *handle,
125 GtkWidget *widget)
126 {
127 GtkTextHandlePrivate *priv = handle->priv;
128
129 if (widget == priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget)
130 return GTK_TEXT_HANDLE_POSITION_SELECTION_START;
131 else if (widget == priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget)
132 return GTK_TEXT_HANDLE_POSITION_SELECTION_END;
133 else
134 return -1;
135 }
136
137 static gboolean
gtk_text_handle_widget_draw(GtkWidget * widget,cairo_t * cr,GtkTextHandle * handle)138 gtk_text_handle_widget_draw (GtkWidget *widget,
139 cairo_t *cr,
140 GtkTextHandle *handle)
141 {
142 gint pos;
143
144 pos = _text_handle_pos_from_widget (handle, widget);
145
146 if (pos < 0)
147 return FALSE;
148
149 #if 0
150 /* Show the invisible border */
151 cairo_set_source_rgba (cr, 1, 0, 0, 0.5);
152 cairo_paint (cr);
153 #endif
154
155 _gtk_text_handle_draw (handle, cr, pos);
156 return TRUE;
157 }
158
159 static void
gtk_text_handle_set_state(GtkTextHandle * handle,gint pos,GtkStateFlags state)160 gtk_text_handle_set_state (GtkTextHandle *handle,
161 gint pos,
162 GtkStateFlags state)
163 {
164 GtkTextHandlePrivate *priv = handle->priv;
165
166 if (!priv->windows[pos].widget)
167 return;
168
169 gtk_widget_set_state_flags (priv->windows[pos].widget, state, FALSE);
170 gtk_widget_queue_draw (priv->windows[pos].widget);
171 }
172
173 static void
gtk_text_handle_unset_state(GtkTextHandle * handle,gint pos,GtkStateFlags state)174 gtk_text_handle_unset_state (GtkTextHandle *handle,
175 gint pos,
176 GtkStateFlags state)
177 {
178 GtkTextHandlePrivate *priv = handle->priv;
179
180 if (!priv->windows[pos].widget)
181 return;
182
183 gtk_widget_unset_state_flags (priv->windows[pos].widget, state);
184 gtk_widget_queue_draw (priv->windows[pos].widget);
185 }
186
187 static gboolean
gtk_text_handle_widget_event(GtkWidget * widget,GdkEvent * event,GtkTextHandle * handle)188 gtk_text_handle_widget_event (GtkWidget *widget,
189 GdkEvent *event,
190 GtkTextHandle *handle)
191 {
192 GtkTextHandlePrivate *priv;
193 gint pos;
194
195 priv = handle->priv;
196 pos = _text_handle_pos_from_widget (handle, widget);
197
198 if (pos < 0)
199 return FALSE;
200
201 if (event->type == GDK_BUTTON_PRESS)
202 {
203 priv->windows[pos].dx = event->button.x;
204 priv->windows[pos].dy = event->button.y;
205 priv->windows[pos].dragged = TRUE;
206 gtk_text_handle_set_state (handle, pos, GTK_STATE_FLAG_ACTIVE);
207 g_signal_emit (handle, signals[DRAG_STARTED], 0, pos);
208 }
209 else if (event->type == GDK_BUTTON_RELEASE)
210 {
211 g_signal_emit (handle, signals[DRAG_FINISHED], 0, pos);
212 priv->windows[pos].dragged = FALSE;
213 gtk_text_handle_unset_state (handle, pos, GTK_STATE_FLAG_ACTIVE);
214 }
215 else if (event->type == GDK_ENTER_NOTIFY)
216 gtk_text_handle_set_state (handle, pos, GTK_STATE_FLAG_PRELIGHT);
217 else if (event->type == GDK_LEAVE_NOTIFY)
218 {
219 if (!priv->windows[pos].dragged &&
220 (event->crossing.mode == GDK_CROSSING_NORMAL ||
221 event->crossing.mode == GDK_CROSSING_UNGRAB))
222 gtk_text_handle_unset_state (handle, pos, GTK_STATE_FLAG_PRELIGHT);
223 }
224 else if (event->type == GDK_MOTION_NOTIFY &&
225 event->motion.state & GDK_BUTTON1_MASK &&
226 priv->windows[pos].dragged)
227 {
228 gint x, y, handle_width, handle_height;
229 cairo_rectangle_int_t rect;
230 GtkAllocation allocation;
231 GtkWidget *window;
232
233 window = gtk_widget_get_parent (priv->windows[pos].widget);
234 gtk_widget_get_allocation (priv->windows[pos].widget, &allocation);
235 _gtk_text_handle_get_size (handle, &handle_width, &handle_height);
236
237 _gtk_window_get_popover_position (GTK_WINDOW (window),
238 priv->windows[pos].widget,
239 NULL, &rect);
240
241 x = rect.x + event->motion.x - priv->windows[pos].dx;
242 y = rect.y + event->motion.y - priv->windows[pos].dy +
243 priv->windows[pos].border.top / 2;
244
245 if (pos == GTK_TEXT_HANDLE_POSITION_CURSOR &&
246 priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR)
247 x += handle_width / 2;
248 else if ((pos == GTK_TEXT_HANDLE_POSITION_CURSOR &&
249 priv->windows[pos].dir == GTK_TEXT_DIR_RTL) ||
250 (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_START &&
251 priv->windows[pos].dir != GTK_TEXT_DIR_RTL))
252 x += handle_width;
253
254 gtk_widget_translate_coordinates (window, priv->parent, x, y, &x, &y);
255 g_signal_emit (handle, signals[HANDLE_DRAGGED], 0, pos, x, y);
256 }
257
258 return TRUE;
259 }
260
261 static void
gtk_text_handle_widget_style_updated(GtkWidget * widget,GtkTextHandle * handle)262 gtk_text_handle_widget_style_updated (GtkWidget *widget,
263 GtkTextHandle *handle)
264 {
265 GtkTextHandlePrivate *priv;
266
267 priv = handle->priv;
268 gtk_style_context_set_parent (gtk_widget_get_style_context (widget),
269 gtk_widget_get_style_context (priv->parent));
270
271 _gtk_text_handle_update (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START);
272 _gtk_text_handle_update (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END);
273 }
274
275 static GtkWidget *
_gtk_text_handle_ensure_widget(GtkTextHandle * handle,GtkTextHandlePosition pos)276 _gtk_text_handle_ensure_widget (GtkTextHandle *handle,
277 GtkTextHandlePosition pos)
278 {
279 GtkTextHandlePrivate *priv;
280
281 priv = handle->priv;
282
283 if (!priv->windows[pos].widget)
284 {
285 GtkWidget *widget, *window;
286 GtkStyleContext *context;
287
288 widget = gtk_event_box_new ();
289 gtk_event_box_set_visible_window (GTK_EVENT_BOX (widget), TRUE);
290 gtk_widget_set_events (widget,
291 GDK_BUTTON_PRESS_MASK |
292 GDK_BUTTON_RELEASE_MASK |
293 GDK_ENTER_NOTIFY_MASK |
294 GDK_LEAVE_NOTIFY_MASK |
295 GDK_POINTER_MOTION_MASK);
296
297 gtk_widget_set_direction (widget, priv->windows[pos].dir);
298
299 g_signal_connect (widget, "draw",
300 G_CALLBACK (gtk_text_handle_widget_draw), handle);
301 g_signal_connect (widget, "event",
302 G_CALLBACK (gtk_text_handle_widget_event), handle);
303 g_signal_connect (widget, "style-updated",
304 G_CALLBACK (gtk_text_handle_widget_style_updated),
305 handle);
306
307 priv->windows[pos].widget = g_object_ref_sink (widget);
308 window = gtk_widget_get_ancestor (priv->parent, GTK_TYPE_WINDOW);
309 _gtk_window_add_popover (GTK_WINDOW (window), widget, priv->parent, FALSE);
310
311 context = gtk_widget_get_style_context (widget);
312 gtk_style_context_set_parent (context, gtk_widget_get_style_context (priv->parent));
313 gtk_css_node_set_name (gtk_widget_get_css_node (widget), I_("cursor-handle"));
314 if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_END)
315 {
316 gtk_style_context_add_class (context, GTK_STYLE_CLASS_BOTTOM);
317 if (priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR)
318 gtk_style_context_add_class (context, GTK_STYLE_CLASS_INSERTION_CURSOR);
319 }
320 else
321 gtk_style_context_add_class (context, GTK_STYLE_CLASS_TOP);
322 }
323
324 return priv->windows[pos].widget;
325 }
326
327 static void
_handle_update_child_visible(GtkTextHandle * handle,GtkTextHandlePosition pos)328 _handle_update_child_visible (GtkTextHandle *handle,
329 GtkTextHandlePosition pos)
330 {
331 HandleWindow *handle_window;
332 GtkTextHandlePrivate *priv;
333 cairo_rectangle_int_t rect;
334 GtkAllocation allocation;
335 GtkWidget *parent;
336
337 priv = handle->priv;
338 handle_window = &priv->windows[pos];
339
340 if (!priv->parent_scrollable)
341 {
342 gtk_widget_set_child_visible (handle_window->widget, TRUE);
343 return;
344 }
345
346 parent = gtk_widget_get_parent (GTK_WIDGET (priv->parent_scrollable));
347 rect = handle_window->pointing_to;
348
349 gtk_widget_translate_coordinates (priv->parent, parent,
350 rect.x, rect.y, &rect.x, &rect.y);
351
352 gtk_widget_get_allocation (GTK_WIDGET (parent), &allocation);
353
354 if (rect.x < 0 || rect.x + rect.width > allocation.width ||
355 rect.y < 0 || rect.y + rect.height > allocation.height)
356 gtk_widget_set_child_visible (handle_window->widget, FALSE);
357 else
358 gtk_widget_set_child_visible (handle_window->widget, TRUE);
359 }
360
361 static void
_gtk_text_handle_update(GtkTextHandle * handle,GtkTextHandlePosition pos)362 _gtk_text_handle_update (GtkTextHandle *handle,
363 GtkTextHandlePosition pos)
364 {
365 GtkTextHandlePrivate *priv;
366 HandleWindow *handle_window;
367 GtkBorder *border;
368
369 priv = handle->priv;
370 handle_window = &priv->windows[pos];
371 border = &handle_window->border;
372
373 if (!priv->parent || !gtk_widget_is_drawable (priv->parent))
374 return;
375
376 if (handle_window->has_point &&
377 handle_window->mode_visible && handle_window->user_visible)
378 {
379 cairo_rectangle_int_t rect;
380 gint width, height;
381 GtkWidget *window;
382 GtkAllocation alloc;
383 gint w, h;
384
385 _gtk_text_handle_ensure_widget (handle, pos);
386 _gtk_text_handle_get_size (handle, &width, &height);
387
388 border->top = height;
389 border->bottom = height;
390 border->left = width;
391 border->right = width;
392
393 rect.x = handle_window->pointing_to.x;
394 rect.y = handle_window->pointing_to.y + handle_window->pointing_to.height - handle_window->border.top;
395 rect.width = width;
396 rect.height = 0;
397
398 _handle_update_child_visible (handle, pos);
399
400 window = gtk_widget_get_parent (handle_window->widget);
401 gtk_widget_translate_coordinates (priv->parent, window,
402 rect.x, rect.y, &rect.x, &rect.y);
403
404 if (pos == GTK_TEXT_HANDLE_POSITION_CURSOR &&
405 priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR)
406 rect.x -= rect.width / 2;
407 else if ((pos == GTK_TEXT_HANDLE_POSITION_CURSOR &&
408 handle_window->dir == GTK_TEXT_DIR_RTL) ||
409 (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_START &&
410 handle_window->dir != GTK_TEXT_DIR_RTL))
411 rect.x -= rect.width;
412
413 /* The goal is to make the window 3 times as wide and high. The handle
414 * will be rendered in the center, making the rest an invisible border.
415 * If we hit the edge of the toplevel, we shrink the border to avoid
416 * mispositioning the handle, if at all possible. This calculation uses
417 * knowledge about how popover_get_rect() works.
418 */
419
420 gtk_widget_get_allocation (window, &alloc);
421
422 w = width + border->left + border->right;
423 h = height + border->top + border->bottom;
424
425 if (rect.x + rect.width/2 - w/2 < alloc.x)
426 border->left = MAX (0, border->left - (alloc.x - (rect.x + rect.width/2 - w/2)));
427 if (rect.y + rect.height/2 - h/2 < alloc.y)
428 border->top = MAX (0, border->top - (alloc.y - (rect.y + rect.height/2 - h/2)));
429 if (rect.x + rect.width/2 + w/2 > alloc.x + alloc.width)
430 border->right = MAX (0, border->right - (rect.x + rect.width/2 + w/2 - (alloc.x + alloc.width)));
431 if (rect.y + rect.height/2 + h/2 > alloc.y + alloc.height)
432 border->bottom = MAX (0, border->bottom - (rect.y + rect.height/2 + h/2 - (alloc.y + alloc.height)));
433
434 width += border->left + border->right;
435 height += border->top + border->bottom;
436
437 gtk_widget_set_size_request (handle_window->widget, width, height);
438 gtk_widget_show (handle_window->widget);
439 _gtk_window_raise_popover (GTK_WINDOW (window), handle_window->widget);
440 _gtk_window_set_popover_position (GTK_WINDOW (window),
441 handle_window->widget,
442 GTK_POS_BOTTOM, &rect);
443 }
444 else if (handle_window->widget)
445 gtk_widget_hide (handle_window->widget);
446 }
447
448 static void
adjustment_changed_cb(GtkAdjustment * adjustment,GtkTextHandle * handle)449 adjustment_changed_cb (GtkAdjustment *adjustment,
450 GtkTextHandle *handle)
451 {
452 _gtk_text_handle_update (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START);
453 _gtk_text_handle_update (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END);
454 }
455
456 static void
_gtk_text_handle_set_scrollable(GtkTextHandle * handle,GtkScrollable * scrollable)457 _gtk_text_handle_set_scrollable (GtkTextHandle *handle,
458 GtkScrollable *scrollable)
459 {
460 GtkTextHandlePrivate *priv;
461
462 priv = handle->priv;
463
464 if (priv->vadj)
465 {
466 g_signal_handlers_disconnect_by_data (priv->vadj, handle);
467 g_clear_object (&priv->vadj);
468 }
469
470 if (priv->hadj)
471 {
472 g_signal_handlers_disconnect_by_data (priv->hadj, handle);
473 g_clear_object (&priv->hadj);
474 }
475
476 if (priv->parent_scrollable)
477 g_object_remove_weak_pointer (G_OBJECT (priv->parent_scrollable), (gpointer *) &priv->parent_scrollable);
478
479 priv->parent_scrollable = scrollable;
480
481 if (scrollable)
482 {
483 g_object_add_weak_pointer (G_OBJECT (priv->parent_scrollable), (gpointer *) &priv->parent_scrollable);
484
485 priv->vadj = gtk_scrollable_get_vadjustment (scrollable);
486 priv->hadj = gtk_scrollable_get_hadjustment (scrollable);
487
488 if (priv->vadj)
489 {
490 g_object_ref (priv->vadj);
491 g_signal_connect (priv->vadj, "changed",
492 G_CALLBACK (adjustment_changed_cb), handle);
493 g_signal_connect (priv->vadj, "value-changed",
494 G_CALLBACK (adjustment_changed_cb), handle);
495 }
496
497 if (priv->hadj)
498 {
499 g_object_ref (priv->hadj);
500 g_signal_connect (priv->hadj, "changed",
501 G_CALLBACK (adjustment_changed_cb), handle);
502 g_signal_connect (priv->hadj, "value-changed",
503 G_CALLBACK (adjustment_changed_cb), handle);
504 }
505 }
506 }
507
508 static void
_gtk_text_handle_scrollable_notify(GObject * object,GParamSpec * pspec,GtkTextHandle * handle)509 _gtk_text_handle_scrollable_notify (GObject *object,
510 GParamSpec *pspec,
511 GtkTextHandle *handle)
512 {
513 if (pspec->value_type == GTK_TYPE_ADJUSTMENT)
514 _gtk_text_handle_set_scrollable (handle, GTK_SCROLLABLE (object));
515 }
516
517 static void
_gtk_text_handle_update_scrollable(GtkTextHandle * handle,GtkScrollable * scrollable)518 _gtk_text_handle_update_scrollable (GtkTextHandle *handle,
519 GtkScrollable *scrollable)
520 {
521 GtkTextHandlePrivate *priv;
522
523 priv = handle->priv;
524
525 if (priv->parent_scrollable == scrollable)
526 return;
527
528 if (priv->parent_scrollable && priv->scrollable_notify_id &&
529 g_signal_handler_is_connected (priv->parent_scrollable,
530 priv->scrollable_notify_id))
531 g_signal_handler_disconnect (priv->parent_scrollable,
532 priv->scrollable_notify_id);
533
534 _gtk_text_handle_set_scrollable (handle, scrollable);
535
536 if (priv->parent_scrollable)
537 priv->scrollable_notify_id =
538 g_signal_connect (priv->parent_scrollable, "notify",
539 G_CALLBACK (_gtk_text_handle_scrollable_notify),
540 handle);
541 }
542
543 static GtkWidget *
gtk_text_handle_lookup_scrollable(GtkTextHandle * handle)544 gtk_text_handle_lookup_scrollable (GtkTextHandle *handle)
545 {
546 GtkTextHandlePrivate *priv;
547 GtkWidget *scrolled_window;
548
549 priv = handle->priv;
550 scrolled_window = gtk_widget_get_ancestor (priv->parent,
551 GTK_TYPE_SCROLLED_WINDOW);
552 if (!scrolled_window)
553 return NULL;
554
555 return gtk_bin_get_child (GTK_BIN (scrolled_window));
556 }
557
558 static void
_gtk_text_handle_parent_hierarchy_changed(GtkWidget * widget,GtkWindow * previous_toplevel,GtkTextHandle * handle)559 _gtk_text_handle_parent_hierarchy_changed (GtkWidget *widget,
560 GtkWindow *previous_toplevel,
561 GtkTextHandle *handle)
562 {
563 GtkWidget *toplevel, *scrollable;
564 GtkTextHandlePrivate *priv;
565
566 priv = handle->priv;
567 toplevel = gtk_widget_get_ancestor (widget, GTK_TYPE_WINDOW);
568
569 if (previous_toplevel && !toplevel)
570 {
571 if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget)
572 {
573 _gtk_window_remove_popover (GTK_WINDOW (previous_toplevel),
574 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget);
575 g_object_unref (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget);
576 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget = NULL;
577 }
578
579 if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget)
580 {
581 _gtk_window_remove_popover (GTK_WINDOW (previous_toplevel),
582 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget);
583 g_object_unref (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget);
584 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget = NULL;
585 }
586 }
587
588 scrollable = gtk_text_handle_lookup_scrollable (handle);
589 _gtk_text_handle_update_scrollable (handle, GTK_SCROLLABLE (scrollable));
590 }
591
592 static void
_gtk_text_handle_set_parent(GtkTextHandle * handle,GtkWidget * parent)593 _gtk_text_handle_set_parent (GtkTextHandle *handle,
594 GtkWidget *parent)
595 {
596 GtkTextHandlePrivate *priv;
597 GtkWidget *scrollable = NULL;
598
599 priv = handle->priv;
600
601 if (priv->parent == parent)
602 return;
603
604 if (priv->parent && priv->hierarchy_changed_id &&
605 g_signal_handler_is_connected (priv->parent, priv->hierarchy_changed_id))
606 g_signal_handler_disconnect (priv->parent, priv->hierarchy_changed_id);
607
608 priv->parent = parent;
609
610 if (parent)
611 {
612 priv->hierarchy_changed_id =
613 g_signal_connect (parent, "hierarchy-changed",
614 G_CALLBACK (_gtk_text_handle_parent_hierarchy_changed),
615 handle);
616
617 scrollable = gtk_text_handle_lookup_scrollable (handle);
618 }
619
620 _gtk_text_handle_update_scrollable (handle, GTK_SCROLLABLE (scrollable));
621 }
622
623 static void
gtk_text_handle_finalize(GObject * object)624 gtk_text_handle_finalize (GObject *object)
625 {
626 GtkTextHandlePrivate *priv;
627
628 priv = GTK_TEXT_HANDLE (object)->priv;
629
630 _gtk_text_handle_set_parent (GTK_TEXT_HANDLE (object), NULL);
631
632 /* We sank the references, unref here */
633 if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget)
634 g_object_unref (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget);
635
636 if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget)
637 g_object_unref (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget);
638
639 G_OBJECT_CLASS (_gtk_text_handle_parent_class)->finalize (object);
640 }
641
642 static void
gtk_text_handle_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)643 gtk_text_handle_set_property (GObject *object,
644 guint prop_id,
645 const GValue *value,
646 GParamSpec *pspec)
647 {
648 GtkTextHandle *handle;
649
650 handle = GTK_TEXT_HANDLE (object);
651
652 switch (prop_id)
653 {
654 case PROP_PARENT:
655 _gtk_text_handle_set_parent (handle, g_value_get_object (value));
656 break;
657 default:
658 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
659 }
660 }
661
662 static void
gtk_text_handle_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)663 gtk_text_handle_get_property (GObject *object,
664 guint prop_id,
665 GValue *value,
666 GParamSpec *pspec)
667 {
668 GtkTextHandlePrivate *priv;
669
670 priv = GTK_TEXT_HANDLE (object)->priv;
671
672 switch (prop_id)
673 {
674 case PROP_PARENT:
675 g_value_set_object (value, priv->parent);
676 break;
677 default:
678 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
679 }
680 }
681
682 static void
_gtk_text_handle_class_init(GtkTextHandleClass * klass)683 _gtk_text_handle_class_init (GtkTextHandleClass *klass)
684 {
685 GObjectClass *object_class = G_OBJECT_CLASS (klass);
686
687 object_class->finalize = gtk_text_handle_finalize;
688 object_class->set_property = gtk_text_handle_set_property;
689 object_class->get_property = gtk_text_handle_get_property;
690
691 signals[HANDLE_DRAGGED] =
692 g_signal_new (I_("handle-dragged"),
693 G_OBJECT_CLASS_TYPE (object_class),
694 G_SIGNAL_RUN_LAST,
695 G_STRUCT_OFFSET (GtkTextHandleClass, handle_dragged),
696 NULL, NULL,
697 _gtk_marshal_VOID__ENUM_INT_INT,
698 G_TYPE_NONE, 3,
699 GTK_TYPE_TEXT_HANDLE_POSITION,
700 G_TYPE_INT, G_TYPE_INT);
701 signals[DRAG_STARTED] =
702 g_signal_new (I_("drag-started"),
703 G_OBJECT_CLASS_TYPE (object_class),
704 G_SIGNAL_RUN_LAST, 0,
705 NULL, NULL,
706 NULL,
707 G_TYPE_NONE, 1,
708 GTK_TYPE_TEXT_HANDLE_POSITION);
709 signals[DRAG_FINISHED] =
710 g_signal_new (I_("drag-finished"),
711 G_OBJECT_CLASS_TYPE (object_class),
712 G_SIGNAL_RUN_LAST, 0,
713 NULL, NULL,
714 NULL,
715 G_TYPE_NONE, 1,
716 GTK_TYPE_TEXT_HANDLE_POSITION);
717
718 g_object_class_install_property (object_class,
719 PROP_PARENT,
720 g_param_spec_object ("parent",
721 P_("Parent widget"),
722 P_("Parent widget"),
723 GTK_TYPE_WIDGET,
724 GTK_PARAM_READWRITE |
725 G_PARAM_CONSTRUCT_ONLY));
726 }
727
728 static void
_gtk_text_handle_init(GtkTextHandle * handle)729 _gtk_text_handle_init (GtkTextHandle *handle)
730 {
731 handle->priv = _gtk_text_handle_get_instance_private (handle);
732 }
733
734 GtkTextHandle *
_gtk_text_handle_new(GtkWidget * parent)735 _gtk_text_handle_new (GtkWidget *parent)
736 {
737 return g_object_new (GTK_TYPE_TEXT_HANDLE,
738 "parent", parent,
739 NULL);
740 }
741
742 void
_gtk_text_handle_set_mode(GtkTextHandle * handle,GtkTextHandleMode mode)743 _gtk_text_handle_set_mode (GtkTextHandle *handle,
744 GtkTextHandleMode mode)
745 {
746 GtkTextHandlePrivate *priv;
747 HandleWindow *start, *end;
748
749 g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
750
751 priv = handle->priv;
752
753 if (priv->mode == mode)
754 return;
755
756 priv->mode = mode;
757 start = &priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START];
758 end = &priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END];
759
760 switch (mode)
761 {
762 case GTK_TEXT_HANDLE_MODE_CURSOR:
763 start->mode_visible = FALSE;
764 /* end = cursor */
765 end->mode_visible = TRUE;
766 break;
767 case GTK_TEXT_HANDLE_MODE_SELECTION:
768 start->mode_visible = TRUE;
769 end->mode_visible = TRUE;
770 break;
771 case GTK_TEXT_HANDLE_MODE_NONE:
772 default:
773 start->mode_visible = FALSE;
774 end->mode_visible = FALSE;
775 break;
776 }
777
778 if (end->widget)
779 {
780 if (mode == GTK_TEXT_HANDLE_MODE_CURSOR)
781 gtk_style_context_add_class (gtk_widget_get_style_context (end->widget), GTK_STYLE_CLASS_INSERTION_CURSOR);
782 else
783 gtk_style_context_remove_class (gtk_widget_get_style_context (end->widget), GTK_STYLE_CLASS_INSERTION_CURSOR);
784 }
785
786 _gtk_text_handle_update (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START);
787 _gtk_text_handle_update (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END);
788
789 if (start->widget && start->mode_visible)
790 gtk_widget_queue_draw (start->widget);
791 if (end->widget && end->mode_visible)
792 gtk_widget_queue_draw (end->widget);
793 }
794
795 GtkTextHandleMode
_gtk_text_handle_get_mode(GtkTextHandle * handle)796 _gtk_text_handle_get_mode (GtkTextHandle *handle)
797 {
798 GtkTextHandlePrivate *priv;
799
800 g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), GTK_TEXT_HANDLE_MODE_NONE);
801
802 priv = handle->priv;
803 return priv->mode;
804 }
805
806 void
_gtk_text_handle_set_position(GtkTextHandle * handle,GtkTextHandlePosition pos,GdkRectangle * rect)807 _gtk_text_handle_set_position (GtkTextHandle *handle,
808 GtkTextHandlePosition pos,
809 GdkRectangle *rect)
810 {
811 GtkTextHandlePrivate *priv;
812 HandleWindow *handle_window;
813
814 g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
815
816 priv = handle->priv;
817 pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR,
818 GTK_TEXT_HANDLE_POSITION_SELECTION_START);
819 handle_window = &priv->windows[pos];
820
821 if (priv->mode == GTK_TEXT_HANDLE_MODE_NONE ||
822 (priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR &&
823 pos != GTK_TEXT_HANDLE_POSITION_CURSOR))
824 return;
825
826 handle_window->pointing_to = *rect;
827 handle_window->has_point = TRUE;
828
829 if (gtk_widget_is_visible (priv->parent))
830 _gtk_text_handle_update (handle, pos);
831 }
832
833 void
_gtk_text_handle_set_visible(GtkTextHandle * handle,GtkTextHandlePosition pos,gboolean visible)834 _gtk_text_handle_set_visible (GtkTextHandle *handle,
835 GtkTextHandlePosition pos,
836 gboolean visible)
837 {
838 GtkTextHandlePrivate *priv;
839
840 g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
841
842 priv = handle->priv;
843 pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR,
844 GTK_TEXT_HANDLE_POSITION_SELECTION_START);
845
846 priv->windows[pos].user_visible = visible;
847
848 if (gtk_widget_is_visible (priv->parent))
849 _gtk_text_handle_update (handle, pos);
850 }
851
852 gboolean
_gtk_text_handle_get_is_dragged(GtkTextHandle * handle,GtkTextHandlePosition pos)853 _gtk_text_handle_get_is_dragged (GtkTextHandle *handle,
854 GtkTextHandlePosition pos)
855 {
856 GtkTextHandlePrivate *priv;
857
858 g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), FALSE);
859
860 priv = handle->priv;
861 pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR,
862 GTK_TEXT_HANDLE_POSITION_SELECTION_START);
863
864 return priv->windows[pos].dragged;
865 }
866
867 void
_gtk_text_handle_set_direction(GtkTextHandle * handle,GtkTextHandlePosition pos,GtkTextDirection dir)868 _gtk_text_handle_set_direction (GtkTextHandle *handle,
869 GtkTextHandlePosition pos,
870 GtkTextDirection dir)
871 {
872 GtkTextHandlePrivate *priv;
873
874 g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
875
876 priv = handle->priv;
877 pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR,
878 GTK_TEXT_HANDLE_POSITION_SELECTION_START);
879 priv->windows[pos].dir = dir;
880
881 if (priv->windows[pos].widget)
882 {
883 gtk_widget_set_direction (priv->windows[pos].widget, dir);
884 _gtk_text_handle_update (handle, pos);
885 }
886 }
887