/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * GtkAspectFrame: Ensure that the child window has a specified aspect ratio * or, if obey_child, has the same aspect ratio as its requested size * * Copyright Owen Taylor 4/9/97 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ /* * Modified by the GTK+ Team and others 1997-2001. See the AUTHORS * file for a list of people on the GTK+ Team. See the ChangeLog * files for a list of changes. These files are distributed with * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ /** * SECTION:gtkaspectframe * @Short_description: A frame that constrains its child to a particular aspect ratio * @Title: GtkAspectFrame * * GtkAspectFrame is useful when you want pack a widget so that it can resize * while retaining the same aspect ratio. For instance, one might be drawing a * small preview of a larger image. * * The frame can respect the aspect ratio of the child widget, or use its own * aspect ratio. * * # CSS nodes * * GtkAspectFrame uses a CSS node with name `frame`. */ #include "config.h" #include "gtkaspectframe.h" #include "gtksizerequest.h" #include "gtkbuildable.h" #include "gtkwidgetprivate.h" #include "gtkprivate.h" #include "gtkintl.h" typedef struct _GtkAspectFrameClass GtkAspectFrameClass; struct _GtkAspectFrame { GtkWidget parent_instance; GtkWidget *child; gboolean obey_child; float xalign; float yalign; float ratio; }; struct _GtkAspectFrameClass { GtkWidgetClass parent_class; }; enum { PROP_0, PROP_XALIGN, PROP_YALIGN, PROP_RATIO, PROP_OBEY_CHILD, PROP_CHILD }; static void gtk_aspect_frame_dispose (GObject *object); static void gtk_aspect_frame_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void gtk_aspect_frame_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void gtk_aspect_frame_size_allocate (GtkWidget *widget, int width, int height, int baseline); static void gtk_aspect_frame_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline); static void gtk_aspect_frame_compute_expand (GtkWidget *widget, gboolean *hexpand, gboolean *vexpand); static GtkSizeRequestMode gtk_aspect_frame_get_request_mode (GtkWidget *widget); static void gtk_aspect_frame_buildable_init (GtkBuildableIface *iface); #define MAX_RATIO 10000.0 #define MIN_RATIO 0.0001 G_DEFINE_TYPE_WITH_CODE (GtkAspectFrame, gtk_aspect_frame, GTK_TYPE_WIDGET, G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_aspect_frame_buildable_init)) static void gtk_aspect_frame_class_init (GtkAspectFrameClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS (class); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); gobject_class->dispose = gtk_aspect_frame_dispose; gobject_class->set_property = gtk_aspect_frame_set_property; gobject_class->get_property = gtk_aspect_frame_get_property; widget_class->measure = gtk_aspect_frame_measure; widget_class->size_allocate = gtk_aspect_frame_size_allocate; widget_class->compute_expand = gtk_aspect_frame_compute_expand; widget_class->get_request_mode = gtk_aspect_frame_get_request_mode; /** * GtkAspectFrame:xalign: * * The horizontal alignment of the #GtkAspectFrame:child widget * of the aspect frame. */ g_object_class_install_property (gobject_class, PROP_XALIGN, g_param_spec_float ("xalign", P_("Horizontal Alignment"), P_("X alignment of the child"), 0.0, 1.0, 0.5, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); /** * GtkAspectFrame:yalign: * * The vertical alignment of the #GtkAspectFrame:child widget * of the aspect frame. */ g_object_class_install_property (gobject_class, PROP_YALIGN, g_param_spec_float ("yalign", P_("Vertical Alignment"), P_("Y alignment of the child"), 0.0, 1.0, 0.5, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); /** * GtkAspectFrame:ratio: * * The aspect ratio to be used by the #GtkAspectFrame. * * This property is only used if #GtkAspectFrame:obey-child is * set to %FALSE. */ g_object_class_install_property (gobject_class, PROP_RATIO, g_param_spec_float ("ratio", P_("Ratio"), P_("Aspect ratio if obey_child is FALSE"), MIN_RATIO, MAX_RATIO, 1.0, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); /** * GtkAspectFrame:obey-child: * * Whether the #GtkAspectFrame should use the aspect ratio of * its #GtkAspectFrame:child widget. */ g_object_class_install_property (gobject_class, PROP_OBEY_CHILD, g_param_spec_boolean ("obey-child", P_("Obey child"), P_("Force aspect ratio to match that of the frame’s child"), TRUE, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); /** * GtkAspectFrame:child: * * The child widget of the #GtkAspectFrame. */ g_object_class_install_property (gobject_class, PROP_CHILD, g_param_spec_object ("child", P_("Child"), P_("The child widget"), GTK_TYPE_WIDGET, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (class), I_("aspectframe")); gtk_widget_class_set_accessible_role (GTK_WIDGET_CLASS (class), GTK_ACCESSIBLE_ROLE_GROUP); } static void gtk_aspect_frame_init (GtkAspectFrame *self) { self->xalign = 0.5; self->yalign = 0.5; self->ratio = 1.0; self->obey_child = TRUE; } static void gtk_aspect_frame_dispose (GObject *object) { GtkAspectFrame *self = GTK_ASPECT_FRAME (object); g_clear_pointer (&self->child, gtk_widget_unparent); G_OBJECT_CLASS (gtk_aspect_frame_parent_class)->dispose (object); } static void gtk_aspect_frame_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkAspectFrame *self = GTK_ASPECT_FRAME (object); switch (prop_id) { /* g_object_notify is handled by the _frame_set function */ case PROP_XALIGN: gtk_aspect_frame_set_xalign (self, g_value_get_float (value)); break; case PROP_YALIGN: gtk_aspect_frame_set_yalign (self, g_value_get_float (value)); break; case PROP_RATIO: gtk_aspect_frame_set_ratio (self, g_value_get_float (value)); break; case PROP_OBEY_CHILD: gtk_aspect_frame_set_obey_child (self, g_value_get_boolean (value)); break; case PROP_CHILD: gtk_aspect_frame_set_child (self, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_aspect_frame_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkAspectFrame *self = GTK_ASPECT_FRAME (object); switch (prop_id) { case PROP_XALIGN: g_value_set_float (value, self->xalign); break; case PROP_YALIGN: g_value_set_float (value, self->yalign); break; case PROP_RATIO: g_value_set_float (value, self->ratio); break; case PROP_OBEY_CHILD: g_value_set_boolean (value, self->obey_child); break; case PROP_CHILD: g_value_set_object (value, gtk_aspect_frame_get_child (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static GtkBuildableIface *parent_buildable_iface; static void gtk_aspect_frame_buildable_add_child (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const char *type) { if (GTK_IS_WIDGET (child)) gtk_aspect_frame_set_child (GTK_ASPECT_FRAME (buildable), GTK_WIDGET (child)); else parent_buildable_iface->add_child (buildable, builder, child, type); } static void gtk_aspect_frame_buildable_init (GtkBuildableIface *iface) { parent_buildable_iface = g_type_interface_peek_parent (iface); iface->add_child = gtk_aspect_frame_buildable_add_child; } /** * gtk_aspect_frame_new: * @xalign: Horizontal alignment of the child within the allocation of * the #GtkAspectFrame. This ranges from 0.0 (left aligned) * to 1.0 (right aligned) * @yalign: Vertical alignment of the child within the allocation of * the #GtkAspectFrame. This ranges from 0.0 (top aligned) * to 1.0 (bottom aligned) * @ratio: The desired aspect ratio. * @obey_child: If %TRUE, @ratio is ignored, and the aspect * ratio is taken from the requistion of the child. * * Create a new #GtkAspectFrame. * * Returns: the new #GtkAspectFrame. */ GtkWidget * gtk_aspect_frame_new (float xalign, float yalign, float ratio, gboolean obey_child) { GtkAspectFrame *self; self = g_object_new (GTK_TYPE_ASPECT_FRAME, NULL); self->xalign = CLAMP (xalign, 0.0, 1.0); self->yalign = CLAMP (yalign, 0.0, 1.0); self->ratio = CLAMP (ratio, MIN_RATIO, MAX_RATIO); self->obey_child = obey_child != FALSE; return GTK_WIDGET (self); } /** * gtk_aspect_frame_set_xalign: * @self: a #GtkAspectFrame * @xalign: horizontal alignment, from 0.0 (left aligned) to 1.0 (right aligned) * * Sets the horizontal alignment of the child within the allocation * of the #GtkAspectFrame. */ void gtk_aspect_frame_set_xalign (GtkAspectFrame *self, float xalign) { g_return_if_fail (GTK_IS_ASPECT_FRAME (self)); xalign = CLAMP (xalign, 0.0, 1.0); if (self->xalign == xalign) return; self->xalign = xalign; g_object_notify (G_OBJECT (self), "xalign"); gtk_widget_queue_resize (GTK_WIDGET (self)); } /** * gtk_aspect_frame_get_xalign: * @self: a #GtkAspectFrame * * Returns the horizontal alignment of the child within the * allocation of the #GtkAspectFrame. * * Returns: the horizontal alignment */ float gtk_aspect_frame_get_xalign (GtkAspectFrame *self) { g_return_val_if_fail (GTK_IS_ASPECT_FRAME (self), 0.5); return self->xalign; } /** * gtk_aspect_frame_set_yalign: * @self: a #GtkAspectFrame * @yalign: horizontal alignment, from 0.0 (top aligned) to 1.0 (bottom aligned) * * Sets the vertical alignment of the child within the allocation * of the #GtkAspectFrame. */ void gtk_aspect_frame_set_yalign (GtkAspectFrame *self, float yalign) { g_return_if_fail (GTK_IS_ASPECT_FRAME (self)); yalign = CLAMP (yalign, 0.0, 1.0); if (self->yalign == yalign) return; self->yalign = yalign; g_object_notify (G_OBJECT (self), "yalign"); gtk_widget_queue_resize (GTK_WIDGET (self)); } /** * gtk_aspect_frame_get_yalign: * @self: a #GtkAspectFrame * * Returns the vertical alignment of the child within the * allocation of the #GtkAspectFrame. * * Returns: the vertical alignment */ float gtk_aspect_frame_get_yalign (GtkAspectFrame *self) { g_return_val_if_fail (GTK_IS_ASPECT_FRAME (self), 0.5); return self->xalign; } /** * gtk_aspect_frame_set_ratio: * @self: a #GtkAspectFrame * @ratio: aspect ratio of the child * * Sets the desired aspect ratio of the child. */ void gtk_aspect_frame_set_ratio (GtkAspectFrame *self, float ratio) { g_return_if_fail (GTK_IS_ASPECT_FRAME (self)); ratio = CLAMP (ratio, MIN_RATIO, MAX_RATIO); if (self->ratio == ratio) return; self->ratio = ratio; g_object_notify (G_OBJECT (self), "ratio"); gtk_widget_queue_resize (GTK_WIDGET (self)); } /** * gtk_aspect_frame_get_ratio: * @self: a #GtkAspectFrame * * Returns the desired aspect ratio of the child set using * gtk_aspect_frame_set_ratio(). * * Returns: the desired aspect ratio */ float gtk_aspect_frame_get_ratio (GtkAspectFrame *self) { g_return_val_if_fail (GTK_IS_ASPECT_FRAME (self), 1.0); return self->ratio; } /** * gtk_aspect_frame_set_obey_child: * @self: a #GtkAspectFrame * @obey_child: If %TRUE, @ratio is ignored, and the aspect * ratio is taken from the requistion of the child. * * Sets whether the aspect ratio of the child's size * request should override the set aspect ratio of * the #GtkAspectFrame. */ void gtk_aspect_frame_set_obey_child (GtkAspectFrame *self, gboolean obey_child) { g_return_if_fail (GTK_IS_ASPECT_FRAME (self)); if (self->obey_child == obey_child) return; self->obey_child = obey_child; g_object_notify (G_OBJECT (self), "obey-child"); gtk_widget_queue_resize (GTK_WIDGET (self)); } /** * gtk_aspect_frame_get_obey_child: * @self: a #GtkAspectFrame * * Returns whether the child's size request should override * the set aspect ratio of the #GtkAspectFrame. * * Returns: whether to obey the child's size request */ gboolean gtk_aspect_frame_get_obey_child (GtkAspectFrame *self) { g_return_val_if_fail (GTK_IS_ASPECT_FRAME (self), TRUE); return self->obey_child; } static void get_full_allocation (GtkAspectFrame *self, GtkAllocation *child_allocation) { child_allocation->x = 0; child_allocation->y = 0; child_allocation->width = gtk_widget_get_width (GTK_WIDGET (self)); child_allocation->height = gtk_widget_get_height (GTK_WIDGET (self)); } static void compute_child_allocation (GtkAspectFrame *self, GtkAllocation *child_allocation) { double ratio; if (self->child && gtk_widget_get_visible (self->child)) { GtkAllocation full_allocation; if (self->obey_child) { GtkRequisition child_requisition; gtk_widget_get_preferred_size (self->child, &child_requisition, NULL); if (child_requisition.height != 0) { ratio = ((double) child_requisition.width / child_requisition.height); if (ratio < MIN_RATIO) ratio = MIN_RATIO; } else if (child_requisition.width != 0) ratio = MAX_RATIO; else ratio = 1.0; } else ratio = self->ratio; get_full_allocation (self, &full_allocation); if (ratio * full_allocation.height > full_allocation.width) { child_allocation->width = full_allocation.width; child_allocation->height = full_allocation.width / ratio + 0.5; } else { child_allocation->width = ratio * full_allocation.height + 0.5; child_allocation->height = full_allocation.height; } child_allocation->x = full_allocation.x + self->xalign * (full_allocation.width - child_allocation->width); child_allocation->y = full_allocation.y + self->yalign * (full_allocation.height - child_allocation->height); } else get_full_allocation (self, child_allocation); } static void gtk_aspect_frame_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { GtkAspectFrame *self = GTK_ASPECT_FRAME (widget); if (self->child && gtk_widget_get_visible (self->child)) { int child_min, child_nat; gtk_widget_measure (self->child, orientation, for_size, &child_min, &child_nat, NULL, NULL); *minimum = child_min; *natural = child_nat; } else { *minimum = 0; *natural = 0; } } static void gtk_aspect_frame_size_allocate (GtkWidget *widget, int width, int height, int baseline) { GtkAspectFrame *self = GTK_ASPECT_FRAME (widget); GtkAllocation new_allocation; compute_child_allocation (self, &new_allocation); if (self->child && gtk_widget_get_visible (self->child)) gtk_widget_size_allocate (self->child, &new_allocation, -1); } static void gtk_aspect_frame_compute_expand (GtkWidget *widget, gboolean *hexpand, gboolean *vexpand) { GtkAspectFrame *self = GTK_ASPECT_FRAME (widget); if (self->child) { *hexpand = gtk_widget_compute_expand (self->child, GTK_ORIENTATION_HORIZONTAL); *vexpand = gtk_widget_compute_expand (self->child, GTK_ORIENTATION_VERTICAL); } else { *hexpand = FALSE; *vexpand = FALSE; } } static GtkSizeRequestMode gtk_aspect_frame_get_request_mode (GtkWidget *widget) { GtkAspectFrame *self = GTK_ASPECT_FRAME (widget); if (self->child) return gtk_widget_get_request_mode (self->child); else return GTK_SIZE_REQUEST_CONSTANT_SIZE; } /** * gtk_aspect_frame_set_child: * @self: a #GtkAspectFrame * @child: (allow-none): the child widget * * Sets the child widget of @self. */ void gtk_aspect_frame_set_child (GtkAspectFrame *self, GtkWidget *child) { g_return_if_fail (GTK_IS_ASPECT_FRAME (self)); g_return_if_fail (child == NULL || GTK_IS_WIDGET (child)); if (self->child == child) return; g_clear_pointer (&self->child, gtk_widget_unparent); if (child) { self->child = child; gtk_widget_set_parent (child, GTK_WIDGET (self)); } g_object_notify (G_OBJECT (self), "child"); } /** * gtk_aspect_frame_get_child: * @self: a #GtkAspectFrame * * Gets the child widget of @self. * * Returns: (nullable) (transfer none): the child widget of self@ */ GtkWidget * gtk_aspect_frame_get_child (GtkAspectFrame *self) { g_return_val_if_fail (GTK_IS_ASPECT_FRAME (self), NULL); return self->child; }