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