1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
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 /*
19  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
20  * file for a list of people on the GTK+ Team.  See the ChangeLog
21  * files for a list of changes.  These files are distributed with
22  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23  */
24 
25 #include "config.h"
26 
27 #include "gtkviewport.h"
28 
29 #include "gtkadjustmentprivate.h"
30 #include "gtkintl.h"
31 #include "gtkmarshalers.h"
32 #include "gtkprivate.h"
33 #include "gtkscrollable.h"
34 #include "gtktypebuiltins.h"
35 #include "gtkwidgetprivate.h"
36 #include "gtkbuildable.h"
37 #include "gtktext.h"
38 
39 
40 /**
41  * GtkViewport:
42  *
43  * `GtkViewport` implements scrollability for widgets that lack their
44  * own scrolling capabilities.
45  *
46  * Use `GtkViewport` to scroll child widgets such as `GtkGrid`,
47  * `GtkBox`, and so on.
48  *
49  * The `GtkViewport` will start scrolling content only if allocated
50  * less than the child widget’s minimum size in a given orientation.
51  *
52  * # CSS nodes
53  *
54  * `GtkViewport` has a single CSS node with name `viewport`.
55  *
56  * # Accessibility
57  *
58  * `GtkViewport` uses the %GTK_ACCESSIBLE_ROLE_GROUP role.
59  */
60 
61 typedef struct _GtkViewportPrivate       GtkViewportPrivate;
62 typedef struct _GtkViewportClass         GtkViewportClass;
63 
64 struct _GtkViewport
65 {
66   GtkWidget parent_instance;
67 
68   GtkWidget *child;
69 
70   GtkAdjustment  *hadjustment;
71   GtkAdjustment  *vadjustment;
72 
73   /* GtkScrollablePolicy needs to be checked when
74    * driving the scrollable adjustment values */
75   guint hscroll_policy : 1;
76   guint vscroll_policy : 1;
77   guint scroll_to_focus : 1;
78 
79   gulong focus_handler;
80 };
81 
82 struct _GtkViewportClass
83 {
84   GtkWidgetClass parent_class;
85 };
86 
87 enum {
88   PROP_0,
89   PROP_HADJUSTMENT,
90   PROP_VADJUSTMENT,
91   PROP_HSCROLL_POLICY,
92   PROP_VSCROLL_POLICY,
93   PROP_SCROLL_TO_FOCUS,
94   PROP_CHILD
95 };
96 
97 
98 static void gtk_viewport_set_property             (GObject         *object,
99                                                    guint            prop_id,
100                                                    const GValue    *value,
101                                                    GParamSpec      *pspec);
102 static void gtk_viewport_get_property             (GObject         *object,
103                                                    guint            prop_id,
104                                                    GValue          *value,
105                                                    GParamSpec      *pspec);
106 static void gtk_viewport_dispose                  (GObject         *object);
107 static void gtk_viewport_size_allocate            (GtkWidget       *widget,
108                                                    int              width,
109                                                    int              height,
110                                                    int              baseline);
111 
112 static void gtk_viewport_adjustment_value_changed (GtkAdjustment    *adjustment,
113                                                    gpointer          data);
114 static void viewport_set_adjustment               (GtkViewport      *viewport,
115                                                    GtkOrientation    orientation,
116                                                    GtkAdjustment    *adjustment);
117 
118 static void setup_focus_change_handler (GtkViewport *viewport);
119 static void clear_focus_change_handler (GtkViewport *viewport);
120 
121 static void gtk_viewport_buildable_init (GtkBuildableIface *iface);
122 
123 
124 G_DEFINE_TYPE_WITH_CODE (GtkViewport, gtk_viewport, GTK_TYPE_WIDGET,
125                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
126                                                 gtk_viewport_buildable_init)
127                          G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
128 
129 static GtkBuildableIface *parent_buildable_iface;
130 
131 static void
gtk_viewport_buildable_add_child(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const char * type)132 gtk_viewport_buildable_add_child (GtkBuildable *buildable,
133                                   GtkBuilder   *builder,
134                                   GObject      *child,
135                                   const char   *type)
136 {
137   if (GTK_IS_WIDGET (child))
138     gtk_viewport_set_child (GTK_VIEWPORT (buildable), GTK_WIDGET (child));
139   else
140     parent_buildable_iface->add_child (buildable, builder, child, type);
141 }
142 
143 static void
gtk_viewport_buildable_init(GtkBuildableIface * iface)144 gtk_viewport_buildable_init (GtkBuildableIface *iface)
145 {
146   parent_buildable_iface = g_type_interface_peek_parent (iface);
147 
148   iface->add_child = gtk_viewport_buildable_add_child;
149 }
150 
151 static void
viewport_set_adjustment_values(GtkViewport * viewport,GtkOrientation orientation)152 viewport_set_adjustment_values (GtkViewport    *viewport,
153                                 GtkOrientation  orientation)
154 {
155   GtkAdjustment *adjustment;
156   GtkScrollablePolicy scroll_policy;
157   GtkScrollablePolicy other_scroll_policy;
158   GtkOrientation other_orientation;
159   double upper, value;
160   int viewport_size, other_viewport_size;
161   int view_width, view_height;
162 
163   view_width = gtk_widget_get_width (GTK_WIDGET (viewport));
164   view_height = gtk_widget_get_height (GTK_WIDGET (viewport));
165 
166   if (orientation == GTK_ORIENTATION_HORIZONTAL)
167     {
168       adjustment = viewport->hadjustment;
169       other_orientation = GTK_ORIENTATION_VERTICAL;
170       viewport_size = view_width;
171       other_viewport_size = view_height;
172       scroll_policy = viewport->hscroll_policy;
173       other_scroll_policy = viewport->vscroll_policy;
174     }
175   else /* VERTICAL */
176     {
177       adjustment = viewport->vadjustment;
178       other_orientation = GTK_ORIENTATION_HORIZONTAL;
179       viewport_size = view_height;
180       other_viewport_size = view_width;
181       scroll_policy = viewport->vscroll_policy;
182       other_scroll_policy = viewport->hscroll_policy;
183     }
184 
185 
186   if (viewport->child && gtk_widget_get_visible (viewport->child))
187     {
188       int min_size, nat_size;
189       int scroll_size;
190 
191       if (other_scroll_policy == GTK_SCROLL_MINIMUM)
192         gtk_widget_measure (viewport->child, other_orientation, -1,
193                             &scroll_size, NULL, NULL, NULL);
194       else
195         gtk_widget_measure (viewport->child, other_orientation, -1,
196                             NULL, &scroll_size, NULL, NULL);
197 
198       gtk_widget_measure (viewport->child, orientation,
199                           MAX (other_viewport_size, scroll_size),
200                           &min_size, &nat_size, NULL, NULL);
201 
202       if (scroll_policy == GTK_SCROLL_MINIMUM)
203         upper = MAX (min_size, viewport_size);
204       else
205         upper = MAX (nat_size, viewport_size);
206 
207     }
208   else
209     {
210       upper = viewport_size;
211     }
212 
213   value = gtk_adjustment_get_value (adjustment);
214 
215   /* We clamp to the left in RTL mode */
216   if (orientation == GTK_ORIENTATION_HORIZONTAL &&
217       _gtk_widget_get_direction (GTK_WIDGET (viewport)) == GTK_TEXT_DIR_RTL)
218     {
219       double dist = gtk_adjustment_get_upper (adjustment)
220                      - value
221                      - gtk_adjustment_get_page_size (adjustment);
222       value = upper - dist - viewport_size;
223     }
224 
225   gtk_adjustment_configure (adjustment,
226                             value,
227                             0,
228                             upper,
229                             viewport_size * 0.1,
230                             viewport_size * 0.9,
231                             viewport_size);
232 }
233 
234 static void
gtk_viewport_measure(GtkWidget * widget,GtkOrientation orientation,int for_size,int * minimum,int * natural,int * minimum_baseline,int * natural_baseline)235 gtk_viewport_measure (GtkWidget      *widget,
236                       GtkOrientation  orientation,
237                       int             for_size,
238                       int            *minimum,
239                       int            *natural,
240                       int            *minimum_baseline,
241                       int            *natural_baseline)
242 {
243   GtkViewport *viewport = GTK_VIEWPORT (widget);
244 
245   if (viewport->child)
246     gtk_widget_measure (viewport->child,
247                         orientation,
248                         for_size,
249                         minimum, natural,
250                         NULL, NULL);
251 }
252 
253 static void
gtk_viewport_compute_expand(GtkWidget * widget,gboolean * hexpand,gboolean * vexpand)254 gtk_viewport_compute_expand (GtkWidget *widget,
255                              gboolean  *hexpand,
256                              gboolean  *vexpand)
257 {
258   GtkViewport *viewport = GTK_VIEWPORT (widget);
259 
260   if (viewport->child)
261     {
262       *hexpand = gtk_widget_compute_expand (viewport->child, GTK_ORIENTATION_HORIZONTAL);
263       *vexpand = gtk_widget_compute_expand (viewport->child, GTK_ORIENTATION_VERTICAL);
264     }
265   else
266     {
267       *hexpand = FALSE;
268       *vexpand = FALSE;
269     }
270 }
271 
272 static GtkSizeRequestMode
gtk_viewport_get_request_mode(GtkWidget * widget)273 gtk_viewport_get_request_mode (GtkWidget *widget)
274 {
275   GtkViewport *viewport = GTK_VIEWPORT (widget);
276 
277   if (viewport->child)
278     return gtk_widget_get_request_mode (viewport->child);
279   else
280     return GTK_SIZE_REQUEST_CONSTANT_SIZE;
281 }
282 
283 #define ADJUSTMENT_POINTER(orientation)            \
284   (((orientation) == GTK_ORIENTATION_HORIZONTAL) ? \
285      &viewport->hadjustment : &viewport->vadjustment)
286 
287 static void
viewport_disconnect_adjustment(GtkViewport * viewport,GtkOrientation orientation)288 viewport_disconnect_adjustment (GtkViewport    *viewport,
289                                 GtkOrientation  orientation)
290 {
291   GtkAdjustment **adjustmentp = ADJUSTMENT_POINTER (orientation);
292 
293   if (*adjustmentp)
294     {
295       g_signal_handlers_disconnect_by_func (*adjustmentp,
296                                             gtk_viewport_adjustment_value_changed,
297                                             viewport);
298       g_object_unref (*adjustmentp);
299       *adjustmentp = NULL;
300     }
301 }
302 
303 static void
gtk_viewport_dispose(GObject * object)304 gtk_viewport_dispose (GObject *object)
305 {
306   GtkViewport *viewport = GTK_VIEWPORT (object);
307 
308   viewport_disconnect_adjustment (viewport, GTK_ORIENTATION_HORIZONTAL);
309   viewport_disconnect_adjustment (viewport, GTK_ORIENTATION_VERTICAL);
310 
311   clear_focus_change_handler (viewport);
312 
313   g_clear_pointer (&viewport->child, gtk_widget_unparent);
314 
315   G_OBJECT_CLASS (gtk_viewport_parent_class)->dispose (object);
316 
317 }
318 
319 static void
gtk_viewport_root(GtkWidget * widget)320 gtk_viewport_root (GtkWidget *widget)
321 {
322   GtkViewport *viewport = GTK_VIEWPORT (widget);
323 
324   GTK_WIDGET_CLASS (gtk_viewport_parent_class)->root (widget);
325 
326   if (viewport->scroll_to_focus)
327     setup_focus_change_handler (viewport);
328 }
329 
330 static void
gtk_viewport_unroot(GtkWidget * widget)331 gtk_viewport_unroot (GtkWidget *widget)
332 {
333   GtkViewport *viewport = GTK_VIEWPORT (widget);
334 
335   if (viewport->scroll_to_focus)
336     clear_focus_change_handler (viewport);
337 
338   GTK_WIDGET_CLASS (gtk_viewport_parent_class)->unroot (widget);
339 }
340 
341 static void
gtk_viewport_class_init(GtkViewportClass * class)342 gtk_viewport_class_init (GtkViewportClass *class)
343 {
344   GObjectClass   *gobject_class;
345   GtkWidgetClass *widget_class;
346 
347   gobject_class = G_OBJECT_CLASS (class);
348   widget_class = (GtkWidgetClass*) class;
349 
350   gobject_class->dispose = gtk_viewport_dispose;
351   gobject_class->set_property = gtk_viewport_set_property;
352   gobject_class->get_property = gtk_viewport_get_property;
353 
354   widget_class->size_allocate = gtk_viewport_size_allocate;
355   widget_class->measure = gtk_viewport_measure;
356   widget_class->root = gtk_viewport_root;
357   widget_class->unroot = gtk_viewport_unroot;
358   widget_class->compute_expand = gtk_viewport_compute_expand;
359   widget_class->get_request_mode = gtk_viewport_get_request_mode;
360 
361   /* GtkScrollable implementation */
362   g_object_class_override_property (gobject_class, PROP_HADJUSTMENT,    "hadjustment");
363   g_object_class_override_property (gobject_class, PROP_VADJUSTMENT,    "vadjustment");
364   g_object_class_override_property (gobject_class, PROP_HSCROLL_POLICY, "hscroll-policy");
365   g_object_class_override_property (gobject_class, PROP_VSCROLL_POLICY, "vscroll-policy");
366 
367   /**
368    * GtkViewport:scroll-to-focus: (attributes org.gtk.Property.get=gtk_viewport_get_scroll_to_focus org.gtk.Property.set=gtk_viewport_set_scroll_to_focus)
369    *
370    * Whether to scroll when the focus changes.
371    */
372   g_object_class_install_property (gobject_class,
373                                    PROP_SCROLL_TO_FOCUS,
374                                    g_param_spec_boolean ("scroll-to-focus",
375                                                          P_("Scroll to focus"),
376                                                          P_("Whether to scroll when the focus changes"),
377                                                          FALSE,
378                                                          GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
379 
380   /**
381    * GtkViewport:child: (attributes org.gtk.Property.get=gtk_viewport_get_child org.gtk.Property.set=gtk_viewport_set_child)
382    *
383    * The child widget.
384    */
385   g_object_class_install_property (gobject_class,
386                                    PROP_CHILD,
387                                    g_param_spec_object ("child",
388                                                         P_("Child"),
389                                                         P_("The child widget"),
390                                                         GTK_TYPE_WIDGET,
391                                                         GTK_PARAM_READWRITE));
392 
393   gtk_widget_class_set_css_name (widget_class, I_("viewport"));
394   gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GROUP);
395 }
396 
397 static void
gtk_viewport_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)398 gtk_viewport_set_property (GObject         *object,
399                            guint            prop_id,
400                            const GValue    *value,
401                            GParamSpec      *pspec)
402 {
403   GtkViewport *viewport = GTK_VIEWPORT (object);
404 
405   switch (prop_id)
406     {
407     case PROP_HADJUSTMENT:
408       viewport_set_adjustment (viewport, GTK_ORIENTATION_HORIZONTAL, g_value_get_object (value));
409       break;
410     case PROP_VADJUSTMENT:
411       viewport_set_adjustment (viewport, GTK_ORIENTATION_VERTICAL, g_value_get_object (value));
412       break;
413     case PROP_HSCROLL_POLICY:
414       if (viewport->hscroll_policy != g_value_get_enum (value))
415         {
416           viewport->hscroll_policy = g_value_get_enum (value);
417           gtk_widget_queue_resize (GTK_WIDGET (viewport));
418           g_object_notify_by_pspec (object, pspec);
419         }
420       break;
421     case PROP_VSCROLL_POLICY:
422       if (viewport->vscroll_policy != g_value_get_enum (value))
423         {
424           viewport->vscroll_policy = g_value_get_enum (value);
425           gtk_widget_queue_resize (GTK_WIDGET (viewport));
426           g_object_notify_by_pspec (object, pspec);
427         }
428       break;
429     case PROP_SCROLL_TO_FOCUS:
430       gtk_viewport_set_scroll_to_focus (viewport, g_value_get_boolean (value));
431       break;
432     case PROP_CHILD:
433       gtk_viewport_set_child (viewport, g_value_get_object (value));
434       break;
435     default:
436       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
437       break;
438     }
439 }
440 
441 static void
gtk_viewport_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)442 gtk_viewport_get_property (GObject         *object,
443                            guint            prop_id,
444                            GValue          *value,
445                            GParamSpec      *pspec)
446 {
447   GtkViewport *viewport = GTK_VIEWPORT (object);
448 
449   switch (prop_id)
450     {
451     case PROP_HADJUSTMENT:
452       g_value_set_object (value, viewport->hadjustment);
453       break;
454     case PROP_VADJUSTMENT:
455       g_value_set_object (value, viewport->vadjustment);
456       break;
457     case PROP_HSCROLL_POLICY:
458       g_value_set_enum (value, viewport->hscroll_policy);
459       break;
460     case PROP_VSCROLL_POLICY:
461       g_value_set_enum (value, viewport->vscroll_policy);
462       break;
463     case PROP_SCROLL_TO_FOCUS:
464       g_value_set_boolean (value, viewport->scroll_to_focus);
465       break;
466     case PROP_CHILD:
467       g_value_set_object (value, gtk_viewport_get_child (viewport));
468       break;
469     default:
470       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
471       break;
472     }
473 }
474 
475 static void
gtk_viewport_init(GtkViewport * viewport)476 gtk_viewport_init (GtkViewport *viewport)
477 {
478   GtkWidget *widget;
479 
480   widget = GTK_WIDGET (viewport);
481 
482   gtk_widget_set_overflow (widget, GTK_OVERFLOW_HIDDEN);
483 
484   viewport->hadjustment = NULL;
485   viewport->vadjustment = NULL;
486 
487   viewport_set_adjustment (viewport, GTK_ORIENTATION_HORIZONTAL, NULL);
488   viewport_set_adjustment (viewport, GTK_ORIENTATION_VERTICAL, NULL);
489 }
490 
491 /**
492  * gtk_viewport_new:
493  * @hadjustment: (nullable): horizontal adjustment
494  * @vadjustment: (nullable): vertical adjustment
495  *
496  * Creates a new `GtkViewport`.
497  *
498  * The new viewport uses the given adjustments, or default
499  * adjustments if none are given.
500  *
501  * Returns: a new `GtkViewport`
502  */
503 GtkWidget*
gtk_viewport_new(GtkAdjustment * hadjustment,GtkAdjustment * vadjustment)504 gtk_viewport_new (GtkAdjustment *hadjustment,
505                   GtkAdjustment *vadjustment)
506 {
507   GtkWidget *viewport;
508 
509   viewport = g_object_new (GTK_TYPE_VIEWPORT,
510                            "hadjustment", hadjustment,
511                            "vadjustment", vadjustment,
512                            NULL);
513 
514   return viewport;
515 }
516 
517 static void
viewport_set_adjustment(GtkViewport * viewport,GtkOrientation orientation,GtkAdjustment * adjustment)518 viewport_set_adjustment (GtkViewport    *viewport,
519                          GtkOrientation  orientation,
520                          GtkAdjustment  *adjustment)
521 {
522   GtkAdjustment **adjustmentp = ADJUSTMENT_POINTER (orientation);
523 
524   if (adjustment && adjustment == *adjustmentp)
525     return;
526 
527   if (!adjustment)
528     adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
529   viewport_disconnect_adjustment (viewport, orientation);
530   *adjustmentp = adjustment;
531   g_object_ref_sink (adjustment);
532 
533   viewport_set_adjustment_values (viewport, orientation);
534 
535   g_signal_connect (adjustment, "value-changed",
536                     G_CALLBACK (gtk_viewport_adjustment_value_changed),
537                     viewport);
538 
539   gtk_viewport_adjustment_value_changed (adjustment, viewport);
540 }
541 
542 static void
gtk_viewport_size_allocate(GtkWidget * widget,int width,int height,int baseline)543 gtk_viewport_size_allocate (GtkWidget *widget,
544                             int        width,
545                             int        height,
546                             int        baseline)
547 {
548   GtkViewport *viewport = GTK_VIEWPORT (widget);
549   GtkAdjustment *hadjustment = viewport->hadjustment;
550   GtkAdjustment *vadjustment = viewport->vadjustment;
551 
552   g_object_freeze_notify (G_OBJECT (hadjustment));
553   g_object_freeze_notify (G_OBJECT (vadjustment));
554 
555   viewport_set_adjustment_values (viewport, GTK_ORIENTATION_HORIZONTAL);
556   viewport_set_adjustment_values (viewport, GTK_ORIENTATION_VERTICAL);
557 
558   if (viewport->child && gtk_widget_get_visible (viewport->child))
559     {
560       GtkAllocation child_allocation;
561 
562       child_allocation.x = - gtk_adjustment_get_value (hadjustment);
563       child_allocation.y = - gtk_adjustment_get_value (vadjustment);
564       child_allocation.width = gtk_adjustment_get_upper (hadjustment);
565       child_allocation.height = gtk_adjustment_get_upper (vadjustment);
566 
567       gtk_widget_size_allocate (viewport->child, &child_allocation, -1);
568     }
569 
570   g_object_thaw_notify (G_OBJECT (hadjustment));
571   g_object_thaw_notify (G_OBJECT (vadjustment));
572 }
573 
574 static void
gtk_viewport_adjustment_value_changed(GtkAdjustment * adjustment,gpointer data)575 gtk_viewport_adjustment_value_changed (GtkAdjustment *adjustment,
576                                        gpointer       data)
577 {
578   gtk_widget_queue_allocate (GTK_WIDGET (data));
579 }
580 
581 /**
582  * gtk_viewport_get_scroll_to_focus: (attributes org.gtk.Method.get_property=scroll-to-focus)
583  * @viewport: a `GtkViewport`
584  *
585  * Gets whether the viewport is scrolling to keep the focused
586  * child in view.
587  *
588  * Returns: %TRUE if the viewport keeps the focus child scrolled to view
589  */
590 gboolean
gtk_viewport_get_scroll_to_focus(GtkViewport * viewport)591 gtk_viewport_get_scroll_to_focus (GtkViewport *viewport)
592 {
593   g_return_val_if_fail (GTK_IS_VIEWPORT (viewport), FALSE);
594 
595   return viewport->scroll_to_focus;
596 }
597 
598 /**
599  * gtk_viewport_set_scroll_to_focus: (attributes org.gtk.Method.set_property=scroll-to-focus)
600  * @viewport: a `GtkViewport`
601  * @scroll_to_focus: whether to keep the focus widget scrolled to view
602  *
603  * Sets whether the viewport should automatically scroll
604  * to keep the focused child in view.
605  */
606 void
gtk_viewport_set_scroll_to_focus(GtkViewport * viewport,gboolean scroll_to_focus)607 gtk_viewport_set_scroll_to_focus (GtkViewport *viewport,
608                                   gboolean     scroll_to_focus)
609 {
610   g_return_if_fail (GTK_IS_VIEWPORT (viewport));
611 
612   if (viewport->scroll_to_focus == scroll_to_focus)
613     return;
614 
615   viewport->scroll_to_focus = scroll_to_focus;
616 
617   if (gtk_widget_get_root (GTK_WIDGET (viewport)))
618     {
619       if (scroll_to_focus)
620         setup_focus_change_handler (viewport);
621       else
622         clear_focus_change_handler (viewport);
623     }
624 
625   g_object_notify (G_OBJECT (viewport), "scroll-to-focus");
626 }
627 
628 static void
scroll_to_view(GtkAdjustment * adj,double pos,double size)629 scroll_to_view (GtkAdjustment *adj,
630                 double         pos,
631                 double         size)
632 {
633   double value, page_size;
634 
635   value = gtk_adjustment_get_value (adj);
636   page_size = gtk_adjustment_get_page_size (adj);
637 
638   if (pos < 0)
639     gtk_adjustment_animate_to_value (adj, value + pos);
640   else if (pos + size >= page_size)
641     gtk_adjustment_animate_to_value (adj, value + pos + size - page_size);
642 }
643 
644 static void
focus_change_handler(GtkWidget * widget)645 focus_change_handler (GtkWidget *widget)
646 {
647   GtkViewport *viewport = GTK_VIEWPORT (widget);
648   GtkRoot *root;
649   GtkWidget *focus_widget;
650   graphene_rect_t rect;
651   double x, y;
652 
653   if ((gtk_widget_get_state_flags (widget) & GTK_STATE_FLAG_FOCUS_WITHIN) == 0)
654     return;
655 
656   root = gtk_widget_get_root (widget);
657   focus_widget = gtk_root_get_focus (root);
658 
659   if (!focus_widget)
660     return;
661 
662   if (GTK_IS_TEXT (focus_widget))
663     focus_widget = gtk_widget_get_parent (focus_widget);
664 
665   if (!gtk_widget_compute_bounds (focus_widget, viewport->child, &rect))
666     return;
667 
668   gtk_widget_translate_coordinates (viewport->child, widget,
669                                     rect.origin.x,
670                                     rect.origin.y,
671                                      &x, &y);
672 
673   scroll_to_view (viewport->hadjustment, x, rect.size.width);
674   scroll_to_view (viewport->vadjustment, y, rect.size.height);
675 }
676 
677 static void
setup_focus_change_handler(GtkViewport * viewport)678 setup_focus_change_handler (GtkViewport *viewport)
679 {
680   GtkRoot *root;
681 
682   root = gtk_widget_get_root (GTK_WIDGET (viewport));
683 
684   viewport->focus_handler = g_signal_connect_swapped (root, "notify::focus-widget",
685                                                   G_CALLBACK (focus_change_handler), viewport);
686 }
687 
688 static void
clear_focus_change_handler(GtkViewport * viewport)689 clear_focus_change_handler (GtkViewport *viewport)
690 {
691   GtkRoot *root;
692 
693   root = gtk_widget_get_root (GTK_WIDGET (viewport));
694 
695   if (viewport->focus_handler)
696     {
697       g_signal_handler_disconnect (root, viewport->focus_handler);
698       viewport->focus_handler = 0;
699     }
700 }
701 
702 /**
703  * gtk_viewport_set_child: (attributes org.gtk.Method.set_property=child)
704  * @viewport: a `GtkViewport`
705  * @child: (nullable): the child widget
706  *
707  * Sets the child widget of @viewport.
708  */
709 void
gtk_viewport_set_child(GtkViewport * viewport,GtkWidget * child)710 gtk_viewport_set_child (GtkViewport *viewport,
711                         GtkWidget   *child)
712 {
713   g_return_if_fail (GTK_IS_VIEWPORT (viewport));
714   g_return_if_fail (child == NULL || GTK_IS_WIDGET (child));
715 
716   if (viewport->child == child)
717     return;
718 
719   g_clear_pointer (&viewport->child, gtk_widget_unparent);
720 
721   if (child)
722     {
723       viewport->child = child;
724       gtk_widget_set_parent (child, GTK_WIDGET (viewport));
725     }
726 
727   g_object_notify (G_OBJECT (viewport), "child");
728 }
729 
730 /**
731  * gtk_viewport_get_child: (attributes org.gtk.Method.get_property=child)
732  * @viewport: a `GtkViewport`
733  *
734  * Gets the child widget of @viewport.
735  *
736  * Returns: (nullable) (transfer none): the child widget of @viewport
737  */
738 GtkWidget *
gtk_viewport_get_child(GtkViewport * viewport)739 gtk_viewport_get_child (GtkViewport *viewport)
740 {
741   g_return_val_if_fail (GTK_IS_VIEWPORT (viewport), NULL);
742 
743   return viewport->child;
744 }
745 
746