1/* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3 *
4 * GtkAspectFrame: Ensure that the child window has a specified aspect ratio
5 * or, if obey_child, has the same aspect ratio as its requested size
6 *
7 * Copyright Owen Taylor 4/9/97
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23/*
24 * Modified by the GTK+ Team and others 1997-2001. See the AUTHORS
25 * file for a list of people on the GTK+ Team. See the ChangeLog
26 * files for a list of changes. These files are distributed with
27 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
28 */
29
30/**
31 * GtkAspectFrame:
32 *
33 * `GtkAspectFrame` preserves the aspect ratio of its child.
34 *
35 * The frame can respect the aspect ratio of the child widget,
36 * or use its own aspect ratio.
37 *
38 * # CSS nodes
39 *
40 * `GtkAspectFrame` uses a CSS node with name `frame`.
41 */
42
43#include "config.h"
44
45#include "gtkaspectframe.h"
46
47#include "gtksizerequest.h"
48
49#include "gtkbuildable.h"
50
51#include "gtkwidgetprivate.h"
52#include "gtkprivate.h"
53#include "gtkintl.h"
54
55
56typedef struct _GtkAspectFrameClass GtkAspectFrameClass;
57
58struct _GtkAspectFrame
59{
60 GtkWidget parent_instance;
61
62 GtkWidget *child;
63 gboolean obey_child;
64 float xalign;
65 float yalign;
66 float ratio;
67};
68
69struct _GtkAspectFrameClass
70{
71 GtkWidgetClass parent_class;
72};
73
74enum {
75 PROP_0,
76 PROP_XALIGN,
77 PROP_YALIGN,
78 PROP_RATIO,
79 PROP_OBEY_CHILD,
80 PROP_CHILD
81};
82
83static void gtk_aspect_frame_dispose (GObject *object);
84static void gtk_aspect_frame_set_property (GObject *object,
85 guint prop_id,
86 const GValue *value,
87 GParamSpec *pspec);
88static void gtk_aspect_frame_get_property (GObject *object,
89 guint prop_id,
90 GValue *value,
91 GParamSpec *pspec);
92static void gtk_aspect_frame_size_allocate (GtkWidget *widget,
93 int width,
94 int height,
95 int baseline);
96static void gtk_aspect_frame_measure (GtkWidget *widget,
97 GtkOrientation orientation,
98 int for_size,
99 int *minimum,
100 int *natural,
101 int *minimum_baseline,
102 int *natural_baseline);
103
104static void gtk_aspect_frame_compute_expand (GtkWidget *widget,
105 gboolean *hexpand,
106 gboolean *vexpand);
107static GtkSizeRequestMode
108 gtk_aspect_frame_get_request_mode (GtkWidget *widget);
109
110static void gtk_aspect_frame_buildable_init (GtkBuildableIface *iface);
111
112#define MAX_RATIO 10000.0
113#define MIN_RATIO 0.0001
114
115
116G_DEFINE_TYPE_WITH_CODE (GtkAspectFrame, gtk_aspect_frame, GTK_TYPE_WIDGET,
117 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
118 gtk_aspect_frame_buildable_init))
119
120
121static void
122gtk_aspect_frame_class_init (GtkAspectFrameClass *class)
123{
124 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
125 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
126
127 gobject_class->dispose = gtk_aspect_frame_dispose;
128 gobject_class->set_property = gtk_aspect_frame_set_property;
129 gobject_class->get_property = gtk_aspect_frame_get_property;
130
131 widget_class->measure = gtk_aspect_frame_measure;
132 widget_class->size_allocate = gtk_aspect_frame_size_allocate;
133 widget_class->compute_expand = gtk_aspect_frame_compute_expand;
134 widget_class->get_request_mode = gtk_aspect_frame_get_request_mode;
135
136 /**
137 * GtkAspectFrame:xalign: (attributes org.gtk.Property.get=gtk_aspect_frame_get_xalign org.gtk.Property.set=gtk_aspect_frame_set_xalign)
138 *
139 * The horizontal alignment of the child.
140 */
141 g_object_class_install_property (oclass: gobject_class,
142 property_id: PROP_XALIGN,
143 pspec: g_param_spec_float (name: "xalign",
144 P_("Horizontal Alignment"),
145 P_("X alignment of the child"),
146 minimum: 0.0, maximum: 1.0, default_value: 0.5,
147 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
148 /**
149 * GtkAspectFrame:yalign: (attributes org.gtk.Property.get=gtk_aspect_frame_get_yalign org.gtk.Property.set=gtk_aspect_frame_set_yalign)
150 *
151 * The vertical alignment of the child.
152 */
153 g_object_class_install_property (oclass: gobject_class,
154 property_id: PROP_YALIGN,
155 pspec: g_param_spec_float (name: "yalign",
156 P_("Vertical Alignment"),
157 P_("Y alignment of the child"),
158 minimum: 0.0, maximum: 1.0, default_value: 0.5,
159 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
160 /**
161 * GtkAspectFrame:ratio: (attributes org.gtk.Property.get=gtk_aspect_frame_get_ratio org.gtk.Property.set=gtk_aspect_frame_set_ratio)
162 *
163 * The aspect ratio to be used by the `GtkAspectFrame`.
164 *
165 * This property is only used if
166 * [property@Gtk.AspectFrame:obey-child] is set to %FALSE.
167 */
168 g_object_class_install_property (oclass: gobject_class,
169 property_id: PROP_RATIO,
170 pspec: g_param_spec_float (name: "ratio",
171 P_("Ratio"),
172 P_("Aspect ratio if obey_child is FALSE"),
173 MIN_RATIO, MAX_RATIO, default_value: 1.0,
174 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
175 /**
176 * GtkAspectFrame:obey-child: (attributes org.gtk.Property.get=gtk_aspect_frame_get_obey_child org.gtk.Property.set=gtk_aspect_frame_set_obey_child)
177 *
178 * Whether the `GtkAspectFrame` should use the aspect ratio of its child.
179 */
180 g_object_class_install_property (oclass: gobject_class,
181 property_id: PROP_OBEY_CHILD,
182 pspec: g_param_spec_boolean (name: "obey-child",
183 P_("Obey child"),
184 P_("Force aspect ratio to match that of the frame’s child"),
185 TRUE,
186 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
187 /**
188 * GtkAspectFrame:child: (attributes org.gtk.Property.get=gtk_aspect_frame_get_child org.gtk.Property.set=gtk_aspect_frame_set_child)
189 *
190 * The child widget.
191 */
192 g_object_class_install_property (oclass: gobject_class,
193 property_id: PROP_CHILD,
194 pspec: g_param_spec_object (name: "child",
195 P_("Child"),
196 P_("The child widget"),
197 GTK_TYPE_WIDGET,
198 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
199
200 gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (class), I_("aspectframe"));
201 gtk_widget_class_set_accessible_role (GTK_WIDGET_CLASS (class), accessible_role: GTK_ACCESSIBLE_ROLE_GROUP);
202}
203
204static void
205gtk_aspect_frame_init (GtkAspectFrame *self)
206{
207 self->xalign = 0.5;
208 self->yalign = 0.5;
209 self->ratio = 1.0;
210 self->obey_child = TRUE;
211}
212
213static void
214gtk_aspect_frame_dispose (GObject *object)
215{
216 GtkAspectFrame *self = GTK_ASPECT_FRAME (object);
217
218 g_clear_pointer (&self->child, gtk_widget_unparent);
219
220 G_OBJECT_CLASS (gtk_aspect_frame_parent_class)->dispose (object);
221}
222
223static void
224gtk_aspect_frame_set_property (GObject *object,
225 guint prop_id,
226 const GValue *value,
227 GParamSpec *pspec)
228{
229 GtkAspectFrame *self = GTK_ASPECT_FRAME (object);
230
231 switch (prop_id)
232 {
233 /* g_object_notify is handled by the _frame_set function */
234 case PROP_XALIGN:
235 gtk_aspect_frame_set_xalign (self, xalign: g_value_get_float (value));
236 break;
237 case PROP_YALIGN:
238 gtk_aspect_frame_set_yalign (self, yalign: g_value_get_float (value));
239 break;
240 case PROP_RATIO:
241 gtk_aspect_frame_set_ratio (self, ratio: g_value_get_float (value));
242 break;
243 case PROP_OBEY_CHILD:
244 gtk_aspect_frame_set_obey_child (self, obey_child: g_value_get_boolean (value));
245 break;
246 case PROP_CHILD:
247 gtk_aspect_frame_set_child (self, child: g_value_get_object (value));
248 break;
249 default:
250 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
251 break;
252 }
253}
254
255static void
256gtk_aspect_frame_get_property (GObject *object,
257 guint prop_id,
258 GValue *value,
259 GParamSpec *pspec)
260{
261 GtkAspectFrame *self = GTK_ASPECT_FRAME (object);
262
263 switch (prop_id)
264 {
265 case PROP_XALIGN:
266 g_value_set_float (value, v_float: self->xalign);
267 break;
268 case PROP_YALIGN:
269 g_value_set_float (value, v_float: self->yalign);
270 break;
271 case PROP_RATIO:
272 g_value_set_float (value, v_float: self->ratio);
273 break;
274 case PROP_OBEY_CHILD:
275 g_value_set_boolean (value, v_boolean: self->obey_child);
276 break;
277 case PROP_CHILD:
278 g_value_set_object (value, v_object: gtk_aspect_frame_get_child (self));
279 break;
280 default:
281 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
282 break;
283 }
284}
285
286static GtkBuildableIface *parent_buildable_iface;
287
288static void
289gtk_aspect_frame_buildable_add_child (GtkBuildable *buildable,
290 GtkBuilder *builder,
291 GObject *child,
292 const char *type)
293{
294 if (GTK_IS_WIDGET (child))
295 gtk_aspect_frame_set_child (GTK_ASPECT_FRAME (buildable), GTK_WIDGET (child));
296 else
297 parent_buildable_iface->add_child (buildable, builder, child, type);
298}
299
300static void
301gtk_aspect_frame_buildable_init (GtkBuildableIface *iface)
302{
303 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
304
305 iface->add_child = gtk_aspect_frame_buildable_add_child;
306}
307
308/**
309 * gtk_aspect_frame_new:
310 * @xalign: Horizontal alignment of the child within the parent.
311 * Ranges from 0.0 (left aligned) to 1.0 (right aligned)
312 * @yalign: Vertical alignment of the child within the parent.
313 * Ranges from 0.0 (top aligned) to 1.0 (bottom aligned)
314 * @ratio: The desired aspect ratio.
315 * @obey_child: If %TRUE, @ratio is ignored, and the aspect
316 * ratio is taken from the requistion of the child.
317 *
318 * Create a new `GtkAspectFrame`.
319 *
320 * Returns: the new `GtkAspectFrame`.
321 */
322GtkWidget *
323gtk_aspect_frame_new (float xalign,
324 float yalign,
325 float ratio,
326 gboolean obey_child)
327{
328 GtkAspectFrame *self;
329
330 self = g_object_new (GTK_TYPE_ASPECT_FRAME, NULL);
331
332 self->xalign = CLAMP (xalign, 0.0, 1.0);
333 self->yalign = CLAMP (yalign, 0.0, 1.0);
334 self->ratio = CLAMP (ratio, MIN_RATIO, MAX_RATIO);
335 self->obey_child = obey_child != FALSE;
336
337 return GTK_WIDGET (self);
338}
339
340/**
341 * gtk_aspect_frame_set_xalign: (attributes org.gtk.Method.set_property=xalign)
342 * @self: a `GtkAspectFrame`
343 * @xalign: horizontal alignment, from 0.0 (left aligned) to 1.0 (right aligned)
344 *
345 * Sets the horizontal alignment of the child within the allocation
346 * of the `GtkAspectFrame`.
347 */
348void
349gtk_aspect_frame_set_xalign (GtkAspectFrame *self,
350 float xalign)
351{
352 g_return_if_fail (GTK_IS_ASPECT_FRAME (self));
353
354 xalign = CLAMP (xalign, 0.0, 1.0);
355
356 if (self->xalign == xalign)
357 return;
358
359 self->xalign = xalign;
360
361 g_object_notify (G_OBJECT (self), property_name: "xalign");
362 gtk_widget_queue_resize (GTK_WIDGET (self));
363}
364
365/**
366 * gtk_aspect_frame_get_xalign: (attributes org.gtk.Method.get_property=xalign)
367 * @self: a `GtkAspectFrame`
368 *
369 * Returns the horizontal alignment of the child within the
370 * allocation of the `GtkAspectFrame`.
371 *
372 * Returns: the horizontal alignment
373 */
374float
375gtk_aspect_frame_get_xalign (GtkAspectFrame *self)
376{
377 g_return_val_if_fail (GTK_IS_ASPECT_FRAME (self), 0.5);
378
379 return self->xalign;
380}
381
382/**
383 * gtk_aspect_frame_set_yalign: (attributes org.gtk.Method.set_property=yalign)
384 * @self: a `GtkAspectFrame`
385 * @yalign: horizontal alignment, from 0.0 (top aligned) to 1.0 (bottom aligned)
386 *
387 * Sets the vertical alignment of the child within the allocation
388 * of the `GtkAspectFrame`.
389 */
390void
391gtk_aspect_frame_set_yalign (GtkAspectFrame *self,
392 float yalign)
393{
394 g_return_if_fail (GTK_IS_ASPECT_FRAME (self));
395
396 yalign = CLAMP (yalign, 0.0, 1.0);
397
398 if (self->yalign == yalign)
399 return;
400
401 self->yalign = yalign;
402
403 g_object_notify (G_OBJECT (self), property_name: "yalign");
404 gtk_widget_queue_resize (GTK_WIDGET (self));
405}
406
407/**
408 * gtk_aspect_frame_get_yalign: (attributes org.gtk.Method.get_property=yalign)
409 * @self: a `GtkAspectFrame`
410 *
411 * Returns the vertical alignment of the child within the
412 * allocation of the `GtkAspectFrame`.
413 *
414 * Returns: the vertical alignment
415 */
416float
417gtk_aspect_frame_get_yalign (GtkAspectFrame *self)
418{
419 g_return_val_if_fail (GTK_IS_ASPECT_FRAME (self), 0.5);
420
421 return self->xalign;
422}
423
424/**
425 * gtk_aspect_frame_set_ratio: (attributes org.gtk.Method.set_property=ratio)
426 * @self: a `GtkAspectFrame`
427 * @ratio: aspect ratio of the child
428 *
429 * Sets the desired aspect ratio of the child.
430 */
431void
432gtk_aspect_frame_set_ratio (GtkAspectFrame *self,
433 float ratio)
434{
435 g_return_if_fail (GTK_IS_ASPECT_FRAME (self));
436
437 ratio = CLAMP (ratio, MIN_RATIO, MAX_RATIO);
438
439 if (self->ratio == ratio)
440 return;
441
442 self->ratio = ratio;
443
444 g_object_notify (G_OBJECT (self), property_name: "ratio");
445 gtk_widget_queue_resize (GTK_WIDGET (self));
446}
447
448/**
449 * gtk_aspect_frame_get_ratio: (attributes org.gtk.Method.get_property=ratio)
450 * @self: a `GtkAspectFrame`
451 *
452 * Returns the desired aspect ratio of the child.
453 *
454 * Returns: the desired aspect ratio
455 */
456float
457gtk_aspect_frame_get_ratio (GtkAspectFrame *self)
458{
459 g_return_val_if_fail (GTK_IS_ASPECT_FRAME (self), 1.0);
460
461 return self->ratio;
462}
463
464/**
465 * gtk_aspect_frame_set_obey_child: (attributes org.gtk.Method.set_propery=obey-child)
466 * @self: a `GtkAspectFrame`
467 * @obey_child: If %TRUE, @ratio is ignored, and the aspect
468 * ratio is taken from the requistion of the child.
469 *
470 * Sets whether the aspect ratio of the child's size
471 * request should override the set aspect ratio of
472 * the `GtkAspectFrame`.
473 */
474void
475gtk_aspect_frame_set_obey_child (GtkAspectFrame *self,
476 gboolean obey_child)
477{
478 g_return_if_fail (GTK_IS_ASPECT_FRAME (self));
479
480 if (self->obey_child == obey_child)
481 return;
482
483 self->obey_child = obey_child;
484
485 g_object_notify (G_OBJECT (self), property_name: "obey-child");
486 gtk_widget_queue_resize (GTK_WIDGET (self));
487
488}
489
490/**
491 * gtk_aspect_frame_get_obey_child: (attributes org.gtk.Method.get_property=obey-child)
492 * @self: a `GtkAspectFrame`
493 *
494 * Returns whether the child's size request should override
495 * the set aspect ratio of the `GtkAspectFrame`.
496 *
497 * Returns: whether to obey the child's size request
498 */
499gboolean
500gtk_aspect_frame_get_obey_child (GtkAspectFrame *self)
501{
502 g_return_val_if_fail (GTK_IS_ASPECT_FRAME (self), TRUE);
503
504 return self->obey_child;
505}
506
507static void
508get_full_allocation (GtkAspectFrame *self,
509 GtkAllocation *child_allocation)
510{
511 child_allocation->x = 0;
512 child_allocation->y = 0;
513 child_allocation->width = gtk_widget_get_width (GTK_WIDGET (self));
514 child_allocation->height = gtk_widget_get_height (GTK_WIDGET (self));
515}
516
517static void
518compute_child_allocation (GtkAspectFrame *self,
519 GtkAllocation *child_allocation)
520{
521 double ratio;
522
523 if (self->child && gtk_widget_get_visible (widget: self->child))
524 {
525 GtkAllocation full_allocation;
526
527 if (self->obey_child)
528 {
529 GtkRequisition child_requisition;
530
531 gtk_widget_get_preferred_size (widget: self->child, minimum_size: &child_requisition, NULL);
532 if (child_requisition.height != 0)
533 {
534 ratio = ((double) child_requisition.width /
535 child_requisition.height);
536 if (ratio < MIN_RATIO)
537 ratio = MIN_RATIO;
538 }
539 else if (child_requisition.width != 0)
540 ratio = MAX_RATIO;
541 else
542 ratio = 1.0;
543 }
544 else
545 ratio = self->ratio;
546
547 get_full_allocation (self, child_allocation: &full_allocation);
548
549 if (ratio * full_allocation.height > full_allocation.width)
550 {
551 child_allocation->width = full_allocation.width;
552 child_allocation->height = full_allocation.width / ratio + 0.5;
553 }
554 else
555 {
556 child_allocation->width = ratio * full_allocation.height + 0.5;
557 child_allocation->height = full_allocation.height;
558 }
559
560 child_allocation->x = full_allocation.x + self->xalign * (full_allocation.width - child_allocation->width);
561 child_allocation->y = full_allocation.y + self->yalign * (full_allocation.height - child_allocation->height);
562 }
563 else
564 get_full_allocation (self, child_allocation);
565}
566
567static void
568gtk_aspect_frame_measure (GtkWidget *widget,
569 GtkOrientation orientation,
570 int for_size,
571 int *minimum,
572 int *natural,
573 int *minimum_baseline,
574 int *natural_baseline)
575{
576 GtkAspectFrame *self = GTK_ASPECT_FRAME (widget);
577
578 if (self->child && gtk_widget_get_visible (widget: self->child))
579 {
580 int child_min, child_nat;
581
582 gtk_widget_measure (widget: self->child,
583 orientation, for_size,
584 minimum: &child_min, natural: &child_nat,
585 NULL, NULL);
586
587 *minimum = child_min;
588 *natural = child_nat;
589 }
590 else
591 {
592 *minimum = 0;
593 *natural = 0;
594 }
595}
596
597static void
598gtk_aspect_frame_size_allocate (GtkWidget *widget,
599 int width,
600 int height,
601 int baseline)
602{
603 GtkAspectFrame *self = GTK_ASPECT_FRAME (widget);
604 GtkAllocation new_allocation;
605
606 compute_child_allocation (self, child_allocation: &new_allocation);
607
608 if (self->child && gtk_widget_get_visible (widget: self->child))
609 gtk_widget_size_allocate (widget: self->child, allocation: &new_allocation, baseline: -1);
610}
611
612static void
613gtk_aspect_frame_compute_expand (GtkWidget *widget,
614 gboolean *hexpand,
615 gboolean *vexpand)
616{
617 GtkAspectFrame *self = GTK_ASPECT_FRAME (widget);
618
619 if (self->child)
620 {
621 *hexpand = gtk_widget_compute_expand (widget: self->child, orientation: GTK_ORIENTATION_HORIZONTAL);
622 *vexpand = gtk_widget_compute_expand (widget: self->child, orientation: GTK_ORIENTATION_VERTICAL);
623 }
624 else
625 {
626 *hexpand = FALSE;
627 *vexpand = FALSE;
628 }
629}
630
631static GtkSizeRequestMode
632gtk_aspect_frame_get_request_mode (GtkWidget *widget)
633{
634 GtkAspectFrame *self = GTK_ASPECT_FRAME (widget);
635
636 if (self->child)
637 return gtk_widget_get_request_mode (widget: self->child);
638 else
639 return GTK_SIZE_REQUEST_CONSTANT_SIZE;
640}
641
642/**
643 * gtk_aspect_frame_set_child: (attributes org.gtk.Method.set_property=child)
644 * @self: a `GtkAspectFrame`
645 * @child: (nullable): the child widget
646 *
647 * Sets the child widget of @self.
648 */
649void
650gtk_aspect_frame_set_child (GtkAspectFrame *self,
651 GtkWidget *child)
652{
653 g_return_if_fail (GTK_IS_ASPECT_FRAME (self));
654 g_return_if_fail (child == NULL || GTK_IS_WIDGET (child));
655
656 if (self->child == child)
657 return;
658
659 g_clear_pointer (&self->child, gtk_widget_unparent);
660
661 if (child)
662 {
663 self->child = child;
664 gtk_widget_set_parent (widget: child, GTK_WIDGET (self));
665 }
666
667 g_object_notify (G_OBJECT (self), property_name: "child");
668}
669
670/**
671 * gtk_aspect_frame_get_child: (attributes org.gtk.Method.get_property=child)
672 * @self: a `GtkAspectFrame`
673 *
674 * Gets the child widget of @self.
675 *
676 * Returns: (nullable) (transfer none): the child widget of self@
677 */
678GtkWidget *
679gtk_aspect_frame_get_child (GtkAspectFrame *self)
680{
681 g_return_val_if_fail (GTK_IS_ASPECT_FRAME (self), NULL);
682
683 return self->child;
684}
685

source code of gtk/gtk/gtkaspectframe.c