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