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 #include <string.h>
27 #include "gtkframe.h"
28 #include "gtklabel.h"
29 #include "gtkprivate.h"
30 #include "gtktypebuiltins.h"
31 #include "gtkintl.h"
32 #include "gtkbuildable.h"
33 #include "gtkwidgetpath.h"
34 #include "gtkcssnodeprivate.h"
35 #include "gtkcsscustomgadgetprivate.h"
36 #include "gtkwidgetprivate.h"
37 #include "gtkcontainerprivate.h"
38 #include "gtkstylecontextprivate.h"
39 #include "gtkcssstylepropertyprivate.h"
40 #include "gtklabel.h"
41 
42 #include "a11y/gtkframeaccessible.h"
43 
44 /**
45  * SECTION:gtkframe
46  * @Short_description: A bin with a decorative frame and optional label
47  * @Title: GtkFrame
48  *
49  * The frame widget is a bin that surrounds its child with a decorative
50  * frame and an optional label. If present, the label is drawn in a gap
51  * in the top side of the frame. The position of the label can be
52  * controlled with gtk_frame_set_label_align().
53  *
54  * # GtkFrame as GtkBuildable
55  *
56  * The GtkFrame implementation of the #GtkBuildable interface supports
57  * placing a child in the label position by specifying “label” as the
58  * “type” attribute of a `<child>` element. A normal content child can
59  * be specified without specifying a `<child>` type attribute.
60  *
61  * An example of a UI definition fragment with `GtkFrame`:
62  *
63  * |[<!-- language="xml" -->
64  * <object class="GtkFrame">
65  *   <child type="label">
66  *     <object class="GtkLabel" id="frame-label"/>
67  *   </child>
68  *   <child>
69  *     <object class="GtkEntry" id="frame-content"/>
70  *   </child>
71  * </object>
72  * ]|
73  *
74  * # CSS nodes
75  *
76  * |[<!-- language="plain" -->
77  * frame
78  * ├── border[.flat]
79  * ├── <label widget>
80  * ╰── <child>
81  * ]|
82  *
83  * GtkFrame has a main CSS node named “frame” and a subnode named “border”. The
84  * “border” node is used to draw the visible border. You can set the appearance
85  * of the border using CSS properties like “border-style” on the “border” node.
86  *
87  * The border node can be given the style class “.flat”, which is used by themes
88  * to disable drawing of the border. To do this from code, call
89  * gtk_frame_set_shadow_type() with %GTK_SHADOW_NONE to add the “.flat” class or
90  * any other shadow type to remove it.
91  */
92 
93 
94 #define LABEL_PAD 1
95 #define LABEL_SIDE_PAD 2
96 
97 struct _GtkFramePrivate
98 {
99   /* Properties */
100   GtkWidget *label_widget;
101 
102   GtkCssGadget *gadget;
103   GtkCssGadget *border_gadget;
104 
105   gint16 shadow_type;
106   gfloat label_xalign;
107   gfloat label_yalign;
108   /* Properties */
109 
110   GtkAllocation child_allocation;
111   GtkAllocation label_allocation;
112 };
113 
114 enum {
115   PROP_0,
116   PROP_LABEL,
117   PROP_LABEL_XALIGN,
118   PROP_LABEL_YALIGN,
119   PROP_SHADOW_TYPE,
120   PROP_LABEL_WIDGET,
121   LAST_PROP
122 };
123 
124 static GParamSpec *frame_props[LAST_PROP];
125 
126 static void gtk_frame_finalize     (GObject      *object);
127 static void gtk_frame_set_property (GObject      *object,
128 				    guint         param_id,
129 				    const GValue *value,
130 				    GParamSpec   *pspec);
131 static void gtk_frame_get_property (GObject     *object,
132 				    guint        param_id,
133 				    GValue      *value,
134 				    GParamSpec  *pspec);
135 static gboolean gtk_frame_draw      (GtkWidget      *widget,
136 				     cairo_t        *cr);
137 static void gtk_frame_size_allocate (GtkWidget      *widget,
138 				     GtkAllocation  *allocation);
139 static void gtk_frame_remove        (GtkContainer   *container,
140 				     GtkWidget      *child);
141 static void gtk_frame_forall        (GtkContainer   *container,
142 				     gboolean	     include_internals,
143 			             GtkCallback     callback,
144 			             gpointer        callback_data);
145 
146 static void gtk_frame_compute_child_allocation      (GtkFrame      *frame,
147 						     GtkAllocation *child_allocation);
148 static void gtk_frame_real_compute_child_allocation (GtkFrame      *frame,
149 						     GtkAllocation *child_allocation);
150 
151 /* GtkBuildable */
152 static void gtk_frame_buildable_init                (GtkBuildableIface *iface);
153 static void gtk_frame_buildable_add_child           (GtkBuildable *buildable,
154 						     GtkBuilder   *builder,
155 						     GObject      *child,
156 						     const gchar  *type);
157 
158 static void gtk_frame_get_preferred_width           (GtkWidget           *widget,
159                                                      gint                *minimum_size,
160 						     gint                *natural_size);
161 static void gtk_frame_get_preferred_height          (GtkWidget           *widget,
162 						     gint                *minimum_size,
163 						     gint                *natural_size);
164 static void gtk_frame_get_preferred_height_for_width(GtkWidget           *layout,
165 						     gint                 width,
166 						     gint                *minimum_height,
167 						     gint                *natural_height);
168 static void gtk_frame_get_preferred_width_for_height(GtkWidget           *layout,
169 						     gint                 width,
170 						     gint                *minimum_height,
171 						     gint                *natural_height);
172 static void gtk_frame_state_flags_changed (GtkWidget     *widget,
173                                            GtkStateFlags  previous_state);
174 
175 static void     gtk_frame_measure        (GtkCssGadget        *gadget,
176                                           GtkOrientation       orientation,
177                                           gint                 for_size,
178                                           gint                *minimum_size,
179                                           gint                *natural_size,
180                                           gint                *minimum_baseline,
181                                           gint                *natural_baseline,
182                                           gpointer             data);
183 static void     gtk_frame_measure_border (GtkCssGadget        *gadget,
184                                           GtkOrientation       orientation,
185                                           gint                 for_size,
186                                           gint                *minimum_size,
187                                           gint                *natural_size,
188                                           gint                *minimum_baseline,
189                                           gint                *natural_baseline,
190                                           gpointer             data);
191 static void     gtk_frame_allocate       (GtkCssGadget        *gadget,
192                                           const GtkAllocation *allocation,
193                                           int                  baseline,
194                                           GtkAllocation       *out_clip,
195                                           gpointer             data);
196 static void     gtk_frame_allocate_border (GtkCssGadget        *gadget,
197                                           const GtkAllocation *allocation,
198                                           int                  baseline,
199                                           GtkAllocation       *out_clip,
200                                           gpointer             data);
201 static gboolean gtk_frame_render         (GtkCssGadget        *gadget,
202                                           cairo_t             *cr,
203                                           int                  x,
204                                           int                  y,
205                                           int                  width,
206                                           int                  height,
207                                           gpointer             data);
208 
209 
G_DEFINE_TYPE_WITH_CODE(GtkFrame,gtk_frame,GTK_TYPE_BIN,G_ADD_PRIVATE (GtkFrame)G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,gtk_frame_buildable_init))210 G_DEFINE_TYPE_WITH_CODE (GtkFrame, gtk_frame, GTK_TYPE_BIN,
211                          G_ADD_PRIVATE (GtkFrame)
212 			 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
213 						gtk_frame_buildable_init))
214 
215 static void
216 gtk_frame_class_init (GtkFrameClass *class)
217 {
218   GObjectClass *gobject_class;
219   GtkWidgetClass *widget_class;
220   GtkContainerClass *container_class;
221 
222   gobject_class = (GObjectClass*) class;
223   widget_class = GTK_WIDGET_CLASS (class);
224   container_class = GTK_CONTAINER_CLASS (class);
225 
226   gobject_class->finalize = gtk_frame_finalize;
227   gobject_class->set_property = gtk_frame_set_property;
228   gobject_class->get_property = gtk_frame_get_property;
229 
230   frame_props[PROP_LABEL] =
231       g_param_spec_string ("label",
232                            P_("Label"),
233                            P_("Text of the frame's label"),
234                            NULL,
235                            GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
236 
237   frame_props[PROP_LABEL_XALIGN] =
238       g_param_spec_float ("label-xalign",
239                           P_("Label xalign"),
240                           P_("The horizontal alignment of the label"),
241                           0.0, 1.0,
242                           0.0,
243                           GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
244 
245   frame_props[PROP_LABEL_YALIGN] =
246       g_param_spec_float ("label-yalign",
247                           P_("Label yalign"),
248                           P_("The vertical alignment of the label"),
249                           0.0, 1.0,
250                           0.5,
251                           GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
252 
253   frame_props[PROP_SHADOW_TYPE] =
254       g_param_spec_enum ("shadow-type",
255                          P_("Frame shadow"),
256                          P_("Appearance of the frame border"),
257 			GTK_TYPE_SHADOW_TYPE,
258 			GTK_SHADOW_ETCHED_IN,
259                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
260 
261   frame_props[PROP_LABEL_WIDGET] =
262       g_param_spec_object ("label-widget",
263                            P_("Label widget"),
264                            P_("A widget to display in place of the usual frame label"),
265                            GTK_TYPE_WIDGET,
266                            GTK_PARAM_READWRITE);
267 
268   g_object_class_install_properties (gobject_class, LAST_PROP, frame_props);
269 
270   widget_class->draw                           = gtk_frame_draw;
271   widget_class->size_allocate                  = gtk_frame_size_allocate;
272   widget_class->get_preferred_width            = gtk_frame_get_preferred_width;
273   widget_class->get_preferred_height           = gtk_frame_get_preferred_height;
274   widget_class->get_preferred_height_for_width = gtk_frame_get_preferred_height_for_width;
275   widget_class->get_preferred_width_for_height = gtk_frame_get_preferred_width_for_height;
276   widget_class->state_flags_changed            = gtk_frame_state_flags_changed;
277 
278   container_class->remove = gtk_frame_remove;
279   container_class->forall = gtk_frame_forall;
280 
281   gtk_container_class_handle_border_width (container_class);
282 
283   class->compute_child_allocation = gtk_frame_real_compute_child_allocation;
284 
285   gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_FRAME_ACCESSIBLE);
286   gtk_widget_class_set_css_name (widget_class, "frame");
287 }
288 
289 static void
gtk_frame_buildable_init(GtkBuildableIface * iface)290 gtk_frame_buildable_init (GtkBuildableIface *iface)
291 {
292   iface->add_child = gtk_frame_buildable_add_child;
293 }
294 
295 static void
gtk_frame_buildable_add_child(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const gchar * type)296 gtk_frame_buildable_add_child (GtkBuildable *buildable,
297 			       GtkBuilder   *builder,
298 			       GObject      *child,
299 			       const gchar  *type)
300 {
301   if (type && strcmp (type, "label") == 0)
302     gtk_frame_set_label_widget (GTK_FRAME (buildable), GTK_WIDGET (child));
303   else if (!type)
304     gtk_container_add (GTK_CONTAINER (buildable), GTK_WIDGET (child));
305   else
306     GTK_BUILDER_WARN_INVALID_CHILD_TYPE (GTK_FRAME (buildable), type);
307 }
308 
309 static void
gtk_frame_init(GtkFrame * frame)310 gtk_frame_init (GtkFrame *frame)
311 {
312   GtkFramePrivate *priv;
313   GtkCssNode *widget_node;
314 
315   frame->priv = gtk_frame_get_instance_private (frame);
316   priv = frame->priv;
317 
318   priv->label_widget = NULL;
319   priv->shadow_type = GTK_SHADOW_ETCHED_IN;
320   priv->label_xalign = 0.0;
321   priv->label_yalign = 0.5;
322 
323   widget_node = gtk_widget_get_css_node (GTK_WIDGET (frame));
324   priv->gadget = gtk_css_custom_gadget_new_for_node (widget_node,
325                                                      GTK_WIDGET (frame),
326                                                      gtk_frame_measure,
327                                                      gtk_frame_allocate,
328                                                      gtk_frame_render,
329                                                      NULL,
330                                                      NULL);
331   priv->border_gadget = gtk_css_custom_gadget_new ("border",
332                                                        GTK_WIDGET (frame),
333                                                        priv->gadget,
334                                                        NULL,
335                                                        gtk_frame_measure_border,
336                                                        gtk_frame_allocate_border,
337                                                        NULL,
338                                                        NULL,
339                                                        NULL);
340 
341   gtk_css_gadget_set_state (priv->border_gadget, gtk_widget_get_state_flags (GTK_WIDGET (frame)));
342 }
343 
344 static void
gtk_frame_finalize(GObject * object)345 gtk_frame_finalize (GObject *object)
346 {
347   GtkFrame *frame = GTK_FRAME (object);
348   GtkFramePrivate *priv = frame->priv;
349 
350   g_clear_object (&priv->border_gadget);
351   g_clear_object (&priv->gadget);
352 
353   G_OBJECT_CLASS (gtk_frame_parent_class)->finalize (object);
354 }
355 
356 static void
gtk_frame_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)357 gtk_frame_set_property (GObject         *object,
358 			guint            prop_id,
359 			const GValue    *value,
360 			GParamSpec      *pspec)
361 {
362   GtkFrame *frame = GTK_FRAME (object);
363   GtkFramePrivate *priv = frame->priv;
364 
365   switch (prop_id)
366     {
367     case PROP_LABEL:
368       gtk_frame_set_label (frame, g_value_get_string (value));
369       break;
370     case PROP_LABEL_XALIGN:
371       gtk_frame_set_label_align (frame, g_value_get_float (value),
372 				 priv->label_yalign);
373      break;
374     case PROP_LABEL_YALIGN:
375       gtk_frame_set_label_align (frame, priv->label_xalign,
376 				 g_value_get_float (value));
377       break;
378     case PROP_SHADOW_TYPE:
379       gtk_frame_set_shadow_type (frame, g_value_get_enum (value));
380       break;
381     case PROP_LABEL_WIDGET:
382       gtk_frame_set_label_widget (frame, g_value_get_object (value));
383       break;
384     default:
385       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
386       break;
387     }
388 }
389 
390 static void
gtk_frame_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)391 gtk_frame_get_property (GObject         *object,
392 			guint            prop_id,
393 			GValue          *value,
394 			GParamSpec      *pspec)
395 {
396   GtkFrame *frame = GTK_FRAME (object);
397   GtkFramePrivate *priv = frame->priv;
398 
399   switch (prop_id)
400     {
401     case PROP_LABEL:
402       g_value_set_string (value, gtk_frame_get_label (frame));
403       break;
404     case PROP_LABEL_XALIGN:
405       g_value_set_float (value, priv->label_xalign);
406       break;
407     case PROP_LABEL_YALIGN:
408       g_value_set_float (value, priv->label_yalign);
409       break;
410     case PROP_SHADOW_TYPE:
411       g_value_set_enum (value, priv->shadow_type);
412       break;
413     case PROP_LABEL_WIDGET:
414       g_value_set_object (value,
415                           priv->label_widget ?
416                           G_OBJECT (priv->label_widget) : NULL);
417       break;
418     default:
419       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
420       break;
421     }
422 }
423 
424 /**
425  * gtk_frame_new:
426  * @label: (allow-none): the text to use as the label of the frame
427  *
428  * Creates a new #GtkFrame, with optional label @label.
429  * If @label is %NULL, the label is omitted.
430  *
431  * Returns: a new #GtkFrame widget
432  **/
433 GtkWidget*
gtk_frame_new(const gchar * label)434 gtk_frame_new (const gchar *label)
435 {
436   return g_object_new (GTK_TYPE_FRAME, "label", label, NULL);
437 }
438 
439 static void
gtk_frame_remove(GtkContainer * container,GtkWidget * child)440 gtk_frame_remove (GtkContainer *container,
441 		  GtkWidget    *child)
442 {
443   GtkFrame *frame = GTK_FRAME (container);
444   GtkFramePrivate *priv = frame->priv;
445 
446   if (priv->label_widget == child)
447     gtk_frame_set_label_widget (frame, NULL);
448   else
449     GTK_CONTAINER_CLASS (gtk_frame_parent_class)->remove (container, child);
450 }
451 
452 static void
gtk_frame_forall(GtkContainer * container,gboolean include_internals,GtkCallback callback,gpointer callback_data)453 gtk_frame_forall (GtkContainer *container,
454 		  gboolean      include_internals,
455 		  GtkCallback   callback,
456 		  gpointer      callback_data)
457 {
458   GtkBin *bin = GTK_BIN (container);
459   GtkFrame *frame = GTK_FRAME (container);
460   GtkFramePrivate *priv = frame->priv;
461   GtkWidget *child;
462 
463   child = gtk_bin_get_child (bin);
464   if (child)
465     (* callback) (child, callback_data);
466 
467   if (priv->label_widget)
468     (* callback) (priv->label_widget, callback_data);
469 }
470 
471 /**
472  * gtk_frame_set_label:
473  * @frame: a #GtkFrame
474  * @label: (allow-none): the text to use as the label of the frame
475  *
476  * Removes the current #GtkFrame:label-widget. If @label is not %NULL, creates a
477  * new #GtkLabel with that text and adds it as the #GtkFrame:label-widget.
478  **/
479 void
gtk_frame_set_label(GtkFrame * frame,const gchar * label)480 gtk_frame_set_label (GtkFrame *frame,
481 		     const gchar *label)
482 {
483   g_return_if_fail (GTK_IS_FRAME (frame));
484 
485   if (!label)
486     {
487       gtk_frame_set_label_widget (frame, NULL);
488     }
489   else
490     {
491       GtkWidget *child = gtk_label_new (label);
492       gtk_widget_show (child);
493 
494       gtk_frame_set_label_widget (frame, child);
495     }
496 }
497 
498 /**
499  * gtk_frame_get_label:
500  * @frame: a #GtkFrame
501  *
502  * If the frame’s label widget is a #GtkLabel, returns the
503  * text in the label widget. (The frame will have a #GtkLabel
504  * for the label widget if a non-%NULL argument was passed
505  * to gtk_frame_new().)
506  *
507  * Returns: (nullable): the text in the label, or %NULL if there
508  *               was no label widget or the lable widget was not
509  *               a #GtkLabel. This string is owned by GTK+ and
510  *               must not be modified or freed.
511  **/
512 const gchar *
gtk_frame_get_label(GtkFrame * frame)513 gtk_frame_get_label (GtkFrame *frame)
514 {
515   GtkFramePrivate *priv;
516 
517   g_return_val_if_fail (GTK_IS_FRAME (frame), NULL);
518 
519   priv = frame->priv;
520 
521   if (GTK_IS_LABEL (priv->label_widget))
522     return gtk_label_get_text (GTK_LABEL (priv->label_widget));
523   else
524     return NULL;
525 }
526 
527 /**
528  * gtk_frame_set_label_widget:
529  * @frame: a #GtkFrame
530  * @label_widget: (nullable): the new label widget
531  *
532  * Sets the #GtkFrame:label-widget for the frame. This is the widget that
533  * will appear embedded in the top edge of the frame as a title.
534  **/
535 void
gtk_frame_set_label_widget(GtkFrame * frame,GtkWidget * label_widget)536 gtk_frame_set_label_widget (GtkFrame  *frame,
537 			    GtkWidget *label_widget)
538 {
539   GtkFramePrivate *priv;
540   gboolean need_resize = FALSE;
541 
542   g_return_if_fail (GTK_IS_FRAME (frame));
543   g_return_if_fail (label_widget == NULL || GTK_IS_WIDGET (label_widget));
544   g_return_if_fail (label_widget == NULL || gtk_widget_get_parent (label_widget) == NULL);
545 
546   priv = frame->priv;
547 
548   if (priv->label_widget == label_widget)
549     return;
550 
551   if (priv->label_widget)
552     {
553       need_resize = gtk_widget_get_visible (priv->label_widget);
554       gtk_widget_unparent (priv->label_widget);
555     }
556 
557   priv->label_widget = label_widget;
558 
559   if (label_widget)
560     {
561       priv->label_widget = label_widget;
562       gtk_widget_set_parent (label_widget, GTK_WIDGET (frame));
563       need_resize |= gtk_widget_get_visible (label_widget);
564     }
565 
566   if (gtk_widget_get_visible (GTK_WIDGET (frame)) && need_resize)
567     gtk_widget_queue_resize (GTK_WIDGET (frame));
568 
569   g_object_freeze_notify (G_OBJECT (frame));
570   g_object_notify_by_pspec (G_OBJECT (frame), frame_props[PROP_LABEL_WIDGET]);
571   g_object_notify_by_pspec (G_OBJECT (frame),  frame_props[PROP_LABEL]);
572   g_object_thaw_notify (G_OBJECT (frame));
573 }
574 
575 /**
576  * gtk_frame_get_label_widget:
577  * @frame: a #GtkFrame
578  *
579  * Retrieves the label widget for the frame. See
580  * gtk_frame_set_label_widget().
581  *
582  * Returns: (nullable) (transfer none): the label widget, or %NULL if
583  * there is none.
584  **/
585 GtkWidget *
gtk_frame_get_label_widget(GtkFrame * frame)586 gtk_frame_get_label_widget (GtkFrame *frame)
587 {
588   g_return_val_if_fail (GTK_IS_FRAME (frame), NULL);
589 
590   return frame->priv->label_widget;
591 }
592 
593 /**
594  * gtk_frame_set_label_align:
595  * @frame: a #GtkFrame
596  * @xalign: The position of the label along the top edge
597  *   of the widget. A value of 0.0 represents left alignment;
598  *   1.0 represents right alignment.
599  * @yalign: The y alignment of the label. A value of 0.0 aligns under
600  *   the frame; 1.0 aligns above the frame. If the values are exactly
601  *   0.0 or 1.0 the gap in the frame won’t be painted because the label
602  *   will be completely above or below the frame.
603  *
604  * Sets the alignment of the frame widget’s label. The
605  * default values for a newly created frame are 0.0 and 0.5.
606  **/
607 void
gtk_frame_set_label_align(GtkFrame * frame,gfloat xalign,gfloat yalign)608 gtk_frame_set_label_align (GtkFrame *frame,
609 			   gfloat    xalign,
610 			   gfloat    yalign)
611 {
612   GtkFramePrivate *priv;
613 
614   g_return_if_fail (GTK_IS_FRAME (frame));
615 
616   priv = frame->priv;
617 
618   xalign = CLAMP (xalign, 0.0, 1.0);
619   yalign = CLAMP (yalign, 0.0, 1.0);
620 
621   g_object_freeze_notify (G_OBJECT (frame));
622   if (xalign != priv->label_xalign)
623     {
624       priv->label_xalign = xalign;
625       g_object_notify_by_pspec (G_OBJECT (frame), frame_props[PROP_LABEL_XALIGN]);
626     }
627 
628   if (yalign != priv->label_yalign)
629     {
630       priv->label_yalign = yalign;
631       g_object_notify_by_pspec (G_OBJECT (frame), frame_props[PROP_LABEL_YALIGN]);
632     }
633 
634   g_object_thaw_notify (G_OBJECT (frame));
635   gtk_widget_queue_resize (GTK_WIDGET (frame));
636 }
637 
638 /**
639  * gtk_frame_get_label_align:
640  * @frame: a #GtkFrame
641  * @xalign: (out) (allow-none): location to store X alignment of
642  *     frame’s label, or %NULL
643  * @yalign: (out) (allow-none): location to store X alignment of
644  *     frame’s label, or %NULL
645  *
646  * Retrieves the X and Y alignment of the frame’s label. See
647  * gtk_frame_set_label_align().
648  **/
649 void
gtk_frame_get_label_align(GtkFrame * frame,gfloat * xalign,gfloat * yalign)650 gtk_frame_get_label_align (GtkFrame *frame,
651 		           gfloat   *xalign,
652 			   gfloat   *yalign)
653 {
654   GtkFramePrivate *priv;
655 
656   g_return_if_fail (GTK_IS_FRAME (frame));
657 
658   priv = frame->priv;
659 
660   if (xalign)
661     *xalign = priv->label_xalign;
662   if (yalign)
663     *yalign = priv->label_yalign;
664 }
665 
666 /**
667  * gtk_frame_set_shadow_type:
668  * @frame: a #GtkFrame
669  * @type: the new #GtkShadowType
670  *
671  * Sets the #GtkFrame:shadow-type for @frame, i.e. whether it is drawn without
672  * (%GTK_SHADOW_NONE) or with (other values) a visible border. Values other than
673  * %GTK_SHADOW_NONE are treated identically by GtkFrame. The chosen type is
674  * applied by removing or adding the .flat class to the CSS node named border.
675  **/
676 void
gtk_frame_set_shadow_type(GtkFrame * frame,GtkShadowType type)677 gtk_frame_set_shadow_type (GtkFrame      *frame,
678 			   GtkShadowType  type)
679 {
680   GtkFramePrivate *priv;
681 
682   g_return_if_fail (GTK_IS_FRAME (frame));
683 
684   priv = frame->priv;
685 
686   if ((GtkShadowType) priv->shadow_type != type)
687     {
688       priv->shadow_type = type;
689 
690       if (type == GTK_SHADOW_NONE)
691         gtk_css_gadget_add_class (priv->border_gadget, GTK_STYLE_CLASS_FLAT);
692       else
693         gtk_css_gadget_remove_class (priv->border_gadget, GTK_STYLE_CLASS_FLAT);
694 
695       g_object_notify_by_pspec (G_OBJECT (frame), frame_props[PROP_SHADOW_TYPE]);
696     }
697 }
698 
699 /**
700  * gtk_frame_get_shadow_type:
701  * @frame: a #GtkFrame
702  *
703  * Retrieves the shadow type of the frame. See
704  * gtk_frame_set_shadow_type().
705  *
706  * Returns: the current shadow type of the frame.
707  **/
708 GtkShadowType
gtk_frame_get_shadow_type(GtkFrame * frame)709 gtk_frame_get_shadow_type (GtkFrame *frame)
710 {
711   g_return_val_if_fail (GTK_IS_FRAME (frame), GTK_SHADOW_ETCHED_IN);
712 
713   return frame->priv->shadow_type;
714 }
715 
716 static gboolean
gtk_frame_draw(GtkWidget * widget,cairo_t * cr)717 gtk_frame_draw (GtkWidget *widget,
718 		cairo_t   *cr)
719 {
720   gtk_css_gadget_draw (GTK_FRAME (widget)->priv->gadget, cr);
721 
722   return FALSE;
723 }
724 
725 static gboolean
gtk_frame_render(GtkCssGadget * gadget,cairo_t * cr,int x,int y,int width,int height,gpointer data)726 gtk_frame_render (GtkCssGadget *gadget,
727                   cairo_t      *cr,
728                   int           x,
729                   int           y,
730                   int           width,
731                   int           height,
732                   gpointer      data)
733 {
734   GtkWidget *widget;
735   GtkFramePrivate *priv;
736   gint xc, yc, w, h;
737   GtkAllocation allocation;
738 
739   widget = gtk_css_gadget_get_owner (gadget);
740   priv = GTK_FRAME (widget)->priv;
741 
742   cairo_save (cr);
743 
744   gtk_widget_get_allocation (widget, &allocation);
745 
746   /* We want to use the standard gadget drawing for the border,
747    * so we clip out the label allocation in order to get the
748    * frame gap.
749    */
750   xc = priv->label_allocation.x - allocation.x;
751   yc = y;
752   w = priv->label_allocation.width;
753   h = priv->label_allocation.height;
754 
755   if (GTK_IS_LABEL (priv->label_widget))
756     {
757       PangoRectangle ink_rect;
758 
759       /* Shrink the clip area for labels, so we get unclipped
760        * frames for the yalign 0.0 and 1.0 cases.
761        */
762       pango_layout_get_pixel_extents (gtk_label_get_layout (GTK_LABEL (priv->label_widget)), &ink_rect, NULL);
763       yc += (h - ink_rect.height) / 2;
764       h = ink_rect.height;
765     }
766 
767   cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
768   cairo_rectangle (cr, xc + w, yc, - w, h);
769   cairo_set_fill_rule (cr, CAIRO_FILL_RULE_WINDING);
770   cairo_clip (cr);
771 
772   gtk_css_gadget_draw (priv->border_gadget, cr);
773 
774   cairo_restore (cr);
775 
776   GTK_WIDGET_CLASS (gtk_frame_parent_class)->draw (widget, cr);
777 
778   return FALSE;
779 }
780 
781 static void
gtk_frame_size_allocate(GtkWidget * widget,GtkAllocation * allocation)782 gtk_frame_size_allocate (GtkWidget     *widget,
783 			 GtkAllocation *allocation)
784 {
785   GtkAllocation clip;
786 
787   gtk_widget_set_allocation (widget, allocation);
788 
789   gtk_css_gadget_allocate (GTK_FRAME (widget)->priv->gadget,
790                            allocation,
791                            gtk_widget_get_allocated_baseline (widget),
792                            &clip);
793 
794   gtk_widget_set_clip (widget, &clip);
795 }
796 
797 static void
gtk_frame_allocate(GtkCssGadget * gadget,const GtkAllocation * allocation,int baseline,GtkAllocation * out_clip,gpointer data)798 gtk_frame_allocate (GtkCssGadget        *gadget,
799                     const GtkAllocation *allocation,
800                     int                  baseline,
801                     GtkAllocation       *out_clip,
802                     gpointer             data)
803 {
804   GtkWidget *widget;
805   GtkFrame *frame;
806   GtkFramePrivate *priv;
807   GtkAllocation new_allocation;
808   GtkAllocation frame_allocation;
809   gint height_extra;
810   GtkAllocation clip;
811 
812   widget = gtk_css_gadget_get_owner (gadget);
813   frame = GTK_FRAME (widget);
814   priv = frame->priv;
815 
816   gtk_frame_compute_child_allocation (frame, &new_allocation);
817   priv->child_allocation = new_allocation;
818 
819   if (priv->label_widget &&
820       gtk_widget_get_visible (priv->label_widget))
821     {
822       gint nat_width, width, height;
823       gfloat xalign;
824 
825       if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
826 	xalign = priv->label_xalign;
827       else
828 	xalign = 1 - priv->label_xalign;
829 
830       gtk_widget_get_preferred_width (priv->label_widget, NULL, &nat_width);
831       width = MIN (new_allocation.width, nat_width);
832       gtk_widget_get_preferred_height_for_width (priv->label_widget, width, &height, NULL);
833 
834       priv->label_allocation.x = new_allocation.x + (new_allocation.width - width) * xalign;
835       priv->label_allocation.y = new_allocation.y - height;
836       priv->label_allocation.height = height;
837       priv->label_allocation.width = width;
838 
839       gtk_widget_size_allocate (priv->label_widget, &priv->label_allocation);
840 
841       height_extra = (1 - priv->label_yalign) * height;
842     }
843   else
844     height_extra = 0;
845 
846   frame_allocation.x = priv->child_allocation.x;
847   frame_allocation.y = priv->child_allocation.y - height_extra;
848   frame_allocation.width = priv->child_allocation.width;
849   frame_allocation.height = priv->child_allocation.height + height_extra;
850 
851   gtk_css_gadget_allocate (priv->border_gadget,
852                            &frame_allocation,
853                            -1,
854                            &clip);
855 
856   gtk_container_get_children_clip (GTK_CONTAINER (widget), out_clip);
857   gdk_rectangle_union (out_clip, &clip, out_clip);
858 }
859 
860 static void
gtk_frame_allocate_border(GtkCssGadget * gadget,const GtkAllocation * allocation,int baseline,GtkAllocation * out_clip,gpointer data)861 gtk_frame_allocate_border (GtkCssGadget        *gadget,
862                            const GtkAllocation *allocation,
863                            int                  baseline,
864                            GtkAllocation       *out_clip,
865                            gpointer             data)
866 {
867   GtkWidget *widget;
868   GtkFramePrivate *priv;
869   GtkWidget *child;
870   GtkAllocation child_allocation;
871   gint height_extra;
872 
873   widget = gtk_css_gadget_get_owner (gadget);
874   priv = GTK_FRAME (widget)->priv;
875 
876   if (priv->label_widget &&
877       gtk_widget_get_visible (priv->label_widget))
878     height_extra = priv->label_allocation.height * (1 - priv->label_yalign);
879   else
880     height_extra = 0;
881 
882   child_allocation = *allocation;
883   child_allocation.y += height_extra;
884   child_allocation.height -= height_extra;
885 
886   child = gtk_bin_get_child (GTK_BIN (widget));
887   if (child && gtk_widget_get_visible (child))
888     gtk_widget_size_allocate (child, &child_allocation);
889 
890   *out_clip = *allocation;
891 }
892 
893 static void
gtk_frame_compute_child_allocation(GtkFrame * frame,GtkAllocation * child_allocation)894 gtk_frame_compute_child_allocation (GtkFrame      *frame,
895 				    GtkAllocation *child_allocation)
896 {
897   g_return_if_fail (GTK_IS_FRAME (frame));
898   g_return_if_fail (child_allocation != NULL);
899 
900   GTK_FRAME_GET_CLASS (frame)->compute_child_allocation (frame, child_allocation);
901 }
902 
903 static void
gtk_frame_real_compute_child_allocation(GtkFrame * frame,GtkAllocation * child_allocation)904 gtk_frame_real_compute_child_allocation (GtkFrame      *frame,
905 					 GtkAllocation *child_allocation)
906 {
907   GtkFramePrivate *priv = frame->priv;
908   GtkAllocation allocation;
909   gint height;
910 
911   gtk_css_gadget_get_content_allocation (priv->gadget, &allocation, NULL);
912 
913   if (priv->label_widget)
914     {
915       gint nat_width, width;
916 
917       gtk_widget_get_preferred_width (priv->label_widget, NULL, &nat_width);
918       width = MIN (allocation.width, nat_width);
919       gtk_widget_get_preferred_height_for_width (priv->label_widget, width, &height, NULL);
920     }
921   else
922     height = 0;
923 
924   child_allocation->x = allocation.x;
925   child_allocation->y = allocation.y + height;
926   child_allocation->width = MAX (1, allocation.width);
927   child_allocation->height = MAX (1, allocation.height - height);
928 }
929 
930 static void
gtk_frame_measure(GtkCssGadget * gadget,GtkOrientation orientation,int for_size,int * minimum,int * natural,int * minimum_baseline,int * natural_baseline,gpointer data)931 gtk_frame_measure (GtkCssGadget   *gadget,
932                    GtkOrientation  orientation,
933                    int             for_size,
934                    int            *minimum,
935                    int            *natural,
936                    int            *minimum_baseline,
937                    int            *natural_baseline,
938                    gpointer        data)
939 {
940   GtkWidget *widget;
941   GtkFrame *frame;
942   GtkFramePrivate *priv;
943   gint child_min, child_nat;
944 
945   widget = gtk_css_gadget_get_owner (gadget);
946   frame = GTK_FRAME (widget);
947   priv = frame->priv;
948 
949   gtk_css_gadget_get_preferred_size (priv->border_gadget,
950                                      orientation,
951                                      for_size,
952                                      &child_min,
953                                      &child_nat,
954                                      NULL, NULL);
955 
956   *minimum = child_min;
957   *natural = child_nat;
958 
959   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
960     {
961       if (orientation == GTK_ORIENTATION_HORIZONTAL)
962         {
963           gtk_widget_get_preferred_width (priv->label_widget, &child_min, &child_nat);
964           *minimum = MAX (child_min, *minimum);
965           *natural = MAX (child_nat, *natural);
966         }
967       else
968         {
969           if (for_size > 0)
970             gtk_widget_get_preferred_height_for_width (priv->label_widget,
971                                                        for_size, &child_min, &child_nat);
972           else
973             gtk_widget_get_preferred_height (priv->label_widget, &child_min, &child_nat);
974 
975           *minimum += child_min;
976           *natural += child_nat;
977         }
978     }
979 }
980 
981 static void
gtk_frame_measure_border(GtkCssGadget * gadget,GtkOrientation orientation,int for_size,int * minimum,int * natural,int * minimum_baseline,int * natural_baseline,gpointer data)982 gtk_frame_measure_border (GtkCssGadget   *gadget,
983                           GtkOrientation  orientation,
984                           int             for_size,
985                           int            *minimum,
986                           int            *natural,
987                           int            *minimum_baseline,
988                           int            *natural_baseline,
989                           gpointer        data)
990 {
991   GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
992   GtkWidget *child;
993   int child_min, child_nat;
994 
995   child = gtk_bin_get_child (GTK_BIN (widget));
996   if (child && gtk_widget_get_visible (child))
997     {
998       if (orientation == GTK_ORIENTATION_HORIZONTAL)
999         {
1000           gtk_widget_get_preferred_width (child, &child_min, &child_nat);
1001         }
1002       else
1003         {
1004           if (for_size > 0)
1005             gtk_widget_get_preferred_height_for_width (child, for_size, &child_min, &child_nat);
1006           else
1007             gtk_widget_get_preferred_height (child, &child_min, &child_nat);
1008         }
1009 
1010       *minimum = child_min;
1011       *natural = child_nat;
1012     }
1013   else
1014     {
1015       *minimum = 0;
1016       *natural = 0;
1017     }
1018 }
1019 
1020 
1021 static void
gtk_frame_get_preferred_width(GtkWidget * widget,gint * minimum,gint * natural)1022 gtk_frame_get_preferred_width (GtkWidget *widget,
1023                                gint      *minimum,
1024                                gint      *natural)
1025 {
1026   gtk_css_gadget_get_preferred_size (GTK_FRAME (widget)->priv->gadget,
1027                                      GTK_ORIENTATION_HORIZONTAL,
1028                                      -1,
1029                                      minimum, natural,
1030                                      NULL, NULL);
1031 }
1032 
1033 static void
gtk_frame_get_preferred_width_for_height(GtkWidget * widget,gint height,gint * minimum,gint * natural)1034 gtk_frame_get_preferred_width_for_height (GtkWidget *widget,
1035                                           gint       height,
1036                                           gint      *minimum,
1037                                           gint      *natural)
1038 {
1039   gtk_css_gadget_get_preferred_size (GTK_FRAME (widget)->priv->gadget,
1040                                      GTK_ORIENTATION_HORIZONTAL,
1041                                      height,
1042                                      minimum, natural,
1043                                      NULL, NULL);
1044 }
1045 
1046 static void
gtk_frame_get_preferred_height(GtkWidget * widget,gint * minimum,gint * natural)1047 gtk_frame_get_preferred_height (GtkWidget *widget,
1048                                 gint      *minimum,
1049                                 gint      *natural)
1050 {
1051   gtk_css_gadget_get_preferred_size (GTK_FRAME (widget)->priv->gadget,
1052                                      GTK_ORIENTATION_VERTICAL,
1053                                      -1,
1054                                      minimum, natural,
1055                                      NULL, NULL);
1056 }
1057 
1058 
1059 static void
gtk_frame_get_preferred_height_for_width(GtkWidget * widget,gint width,gint * minimum,gint * natural)1060 gtk_frame_get_preferred_height_for_width (GtkWidget *widget,
1061                                           gint       width,
1062                                           gint      *minimum,
1063                                           gint      *natural)
1064 {
1065   gtk_css_gadget_get_preferred_size (GTK_FRAME (widget)->priv->gadget,
1066                                      GTK_ORIENTATION_VERTICAL,
1067                                      width,
1068                                      minimum, natural,
1069                                      NULL, NULL);
1070 }
1071 
1072 static void
gtk_frame_state_flags_changed(GtkWidget * widget,GtkStateFlags previous_state)1073 gtk_frame_state_flags_changed (GtkWidget     *widget,
1074                                GtkStateFlags  previous_state)
1075 {
1076   gtk_css_gadget_set_state (GTK_FRAME (widget)->priv->border_gadget, gtk_widget_get_state_flags (widget));
1077 
1078   GTK_WIDGET_CLASS (gtk_frame_parent_class)->state_flags_changed (widget, previous_state);
1079 }
1080