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