1 /* LIBGIMP - The GIMP Library
2  * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
3  *
4  * gimpframe.c
5  * Copyright (C) 2004  Sven Neumann <sven@gimp.org>
6  *
7  * This library is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 3 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library.  If not, see
19  * <https://www.gnu.org/licenses/>.
20  */
21 
22 #include "config.h"
23 
24 #include <string.h>
25 
26 #include <gtk/gtk.h>
27 
28 #include "gimpwidgetstypes.h"
29 
30 #include "gimp3migration.h"
31 #include "gimpframe.h"
32 
33 
34 /**
35  * SECTION: gimpframe
36  * @title: GimpFrame
37  * @short_description: A widget providing a HIG-compliant subclass
38  *                     of #GtkFrame.
39  *
40  * A widget providing a HIG-compliant subclass of #GtkFrame.
41  **/
42 
43 
44 #define DEFAULT_LABEL_SPACING       6
45 #define DEFAULT_LABEL_BOLD          TRUE
46 
47 #define GIMP_FRAME_INDENT_KEY       "gimp-frame-indent"
48 #define GIMP_FRAME_IN_EXPANDER_KEY  "gimp-frame-in-expander"
49 
50 
51 static void      gimp_frame_size_request        (GtkWidget      *widget,
52                                                  GtkRequisition *requisition);
53 static void      gimp_frame_size_allocate       (GtkWidget      *widget,
54                                                  GtkAllocation  *allocation);
55 static void      gimp_frame_style_set           (GtkWidget      *widget,
56                                                  GtkStyle       *previous);
57 static gboolean  gimp_frame_expose_event        (GtkWidget      *widget,
58                                                  GdkEventExpose *event);
59 static void      gimp_frame_child_allocate      (GtkFrame       *frame,
60                                                  GtkAllocation  *allocation);
61 static void      gimp_frame_label_widget_notify (GtkFrame       *frame);
62 static gint      gimp_frame_get_indent          (GtkWidget      *widget);
63 static gint      gimp_frame_get_label_spacing   (GtkFrame       *frame);
64 
65 
G_DEFINE_TYPE(GimpFrame,gimp_frame,GTK_TYPE_FRAME)66 G_DEFINE_TYPE (GimpFrame, gimp_frame, GTK_TYPE_FRAME)
67 
68 #define parent_class gimp_frame_parent_class
69 
70 
71 static void
72 gimp_frame_class_init (GimpFrameClass *klass)
73 {
74   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
75 
76   widget_class->size_request  = gimp_frame_size_request;
77   widget_class->size_allocate = gimp_frame_size_allocate;
78   widget_class->style_set     = gimp_frame_style_set;
79   widget_class->expose_event  = gimp_frame_expose_event;
80 
81   gtk_widget_class_install_style_property (widget_class,
82                                            g_param_spec_boolean ("label-bold",
83                                                                  "Label Bold",
84                                                                  "Whether the frame's label should be bold",
85                                                                  DEFAULT_LABEL_BOLD,
86                                                                  G_PARAM_READABLE));
87   gtk_widget_class_install_style_property (widget_class,
88                                            g_param_spec_int ("label-spacing",
89                                                              "Label Spacing",
90                                                              "The spacing between the label and the frame content",
91                                                              0,
92                                                              G_MAXINT,
93                                                              DEFAULT_LABEL_SPACING,
94                                                              G_PARAM_READABLE));
95 }
96 
97 
98 static void
gimp_frame_init(GimpFrame * frame)99 gimp_frame_init (GimpFrame *frame)
100 {
101   g_signal_connect (frame, "notify::label-widget",
102                     G_CALLBACK (gimp_frame_label_widget_notify),
103                     NULL);
104 }
105 
106 static void
gimp_frame_size_request(GtkWidget * widget,GtkRequisition * requisition)107 gimp_frame_size_request (GtkWidget      *widget,
108                          GtkRequisition *requisition)
109 {
110   GtkFrame       *frame        = GTK_FRAME (widget);
111   GtkWidget      *label_widget = gtk_frame_get_label_widget (frame);
112   GtkWidget      *child        = gtk_bin_get_child (GTK_BIN (widget));
113   GtkRequisition  child_requisition;
114   gint            border_width;
115 
116   if (label_widget && gtk_widget_get_visible (label_widget))
117     {
118       gtk_widget_size_request (label_widget, requisition);
119     }
120   else
121     {
122       requisition->width  = 0;
123       requisition->height = 0;
124     }
125 
126   requisition->height += gimp_frame_get_label_spacing (frame);
127 
128   if (child && gtk_widget_get_visible (child))
129     {
130       gint indent = gimp_frame_get_indent (widget);
131 
132       gtk_widget_size_request (child, &child_requisition);
133 
134       requisition->width = MAX (requisition->width,
135                                 child_requisition.width + indent);
136       requisition->height += child_requisition.height;
137     }
138 
139   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
140 
141   requisition->width  += 2 * border_width;
142   requisition->height += 2 * border_width;
143 }
144 
145 static void
gimp_frame_size_allocate(GtkWidget * widget,GtkAllocation * allocation)146 gimp_frame_size_allocate (GtkWidget     *widget,
147                           GtkAllocation *allocation)
148 {
149   GtkFrame      *frame        = GTK_FRAME (widget);
150   GtkWidget     *label_widget = gtk_frame_get_label_widget (frame);
151   GtkWidget     *child        = gtk_bin_get_child (GTK_BIN (widget));
152   GtkAllocation  child_allocation;
153 
154   /* must not chain up here */
155   gtk_widget_set_allocation (widget, allocation);
156 
157   gimp_frame_child_allocate (frame, &child_allocation);
158 
159   if (child && gtk_widget_get_visible (child))
160     gtk_widget_size_allocate (child, &child_allocation);
161 
162   if (label_widget && gtk_widget_get_visible (label_widget))
163     {
164       GtkAllocation   label_allocation;
165       GtkRequisition  label_requisition;
166       gint            border_width;
167 
168       border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
169 
170       gtk_widget_get_child_requisition (label_widget, &label_requisition);
171 
172       label_allocation.x      = allocation->x + border_width;
173       label_allocation.y      = allocation->y + border_width;
174       label_allocation.width  = MAX (label_requisition.width,
175                                      allocation->width - 2 * border_width);
176       label_allocation.height = label_requisition.height;
177 
178       gtk_widget_size_allocate (label_widget, &label_allocation);
179     }
180 }
181 
182 static void
gimp_frame_child_allocate(GtkFrame * frame,GtkAllocation * child_allocation)183 gimp_frame_child_allocate (GtkFrame      *frame,
184                            GtkAllocation *child_allocation)
185 {
186   GtkWidget     *widget       = GTK_WIDGET (frame);
187   GtkWidget     *label_widget = gtk_frame_get_label_widget (frame);
188   GtkAllocation  allocation;
189   gint           border_width;
190   gint           spacing      = 0;
191   gint           indent       = gimp_frame_get_indent (widget);
192 
193   gtk_widget_get_allocation (widget, &allocation);
194 
195   border_width = gtk_container_get_border_width (GTK_CONTAINER (frame));
196 
197   if (label_widget && gtk_widget_get_visible (label_widget))
198     {
199       GtkRequisition  child_requisition;
200 
201       gtk_widget_get_child_requisition (label_widget, &child_requisition);
202       spacing += child_requisition.height;
203     }
204 
205   spacing += gimp_frame_get_label_spacing (frame);
206 
207   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
208     child_allocation->x    = border_width + indent;
209   else
210     child_allocation->x    = border_width;
211 
212   child_allocation->y      = border_width + spacing;
213   child_allocation->width  = MAX (1,
214                                   allocation.width - 2 * border_width - indent);
215   child_allocation->height = MAX (1,
216                                   allocation.height -
217                                   child_allocation->y - border_width);
218 
219   child_allocation->x += allocation.x;
220   child_allocation->y += allocation.y;
221 }
222 
223 static void
gimp_frame_style_set(GtkWidget * widget,GtkStyle * previous)224 gimp_frame_style_set (GtkWidget *widget,
225                       GtkStyle  *previous)
226 {
227   /*  font changes invalidate the indentation  */
228   g_object_set_data (G_OBJECT (widget), GIMP_FRAME_INDENT_KEY, NULL);
229 
230   /*  for "label_bold"  */
231   gimp_frame_label_widget_notify (GTK_FRAME (widget));
232 }
233 
234 static gboolean
gimp_frame_expose_event(GtkWidget * widget,GdkEventExpose * event)235 gimp_frame_expose_event (GtkWidget      *widget,
236                          GdkEventExpose *event)
237 {
238   if (gtk_widget_is_drawable (widget))
239     {
240       GtkWidgetClass *widget_class = g_type_class_peek_parent (parent_class);
241 
242       return widget_class->expose_event (widget, event);
243     }
244 
245   return FALSE;
246 }
247 
248 static void
gimp_frame_label_widget_notify(GtkFrame * frame)249 gimp_frame_label_widget_notify (GtkFrame *frame)
250 {
251   GtkWidget *label_widget = gtk_frame_get_label_widget (frame);
252 
253   if (label_widget)
254     {
255       GtkLabel *label = NULL;
256 
257       if (GTK_IS_LABEL (label_widget))
258         {
259           gfloat xalign, yalign;
260 
261           label = GTK_LABEL (label_widget);
262 
263           gtk_frame_get_label_align (frame, &xalign, &yalign);
264           gtk_label_set_xalign (GTK_LABEL (label), xalign);
265           gtk_label_set_yalign (GTK_LABEL (label), yalign);
266         }
267       else if (GTK_IS_BIN (label_widget))
268         {
269           GtkWidget *child = gtk_bin_get_child (GTK_BIN (label_widget));
270 
271           if (GTK_IS_LABEL (child))
272             label = GTK_LABEL (child);
273         }
274 
275       if (label)
276         {
277           PangoAttrList  *attrs = pango_attr_list_new ();
278           PangoAttribute *attr;
279           gboolean        bold;
280 
281           gtk_widget_style_get (GTK_WIDGET (frame), "label_bold", &bold, NULL);
282 
283           attr = pango_attr_weight_new (bold ?
284                                         PANGO_WEIGHT_BOLD :
285                                         PANGO_WEIGHT_NORMAL);
286           attr->start_index = 0;
287           attr->end_index   = -1;
288           pango_attr_list_insert (attrs, attr);
289 
290           gtk_label_set_attributes (label, attrs);
291 
292           pango_attr_list_unref (attrs);
293         }
294     }
295 }
296 
297 static gint
gimp_frame_get_indent(GtkWidget * widget)298 gimp_frame_get_indent (GtkWidget *widget)
299 {
300   gint width = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
301                                                    GIMP_FRAME_INDENT_KEY));
302 
303   if (! width)
304     {
305       PangoLayout *layout;
306 
307       /*  the HIG suggests to use four spaces so do just that  */
308       layout = gtk_widget_create_pango_layout (widget, "    ");
309       pango_layout_get_pixel_size (layout, &width, NULL);
310       g_object_unref (layout);
311 
312       g_object_set_data (G_OBJECT (widget),
313                          GIMP_FRAME_INDENT_KEY, GINT_TO_POINTER (width));
314     }
315 
316   return width;
317 }
318 
319 static gint
gimp_frame_get_label_spacing(GtkFrame * frame)320 gimp_frame_get_label_spacing (GtkFrame *frame)
321 {
322   GtkWidget *label_widget = gtk_frame_get_label_widget (frame);
323   gint       spacing      = 0;
324 
325   if ((label_widget && gtk_widget_get_visible (label_widget)) ||
326       (g_object_get_data (G_OBJECT (frame), GIMP_FRAME_IN_EXPANDER_KEY)))
327     {
328       gtk_widget_style_get (GTK_WIDGET (frame),
329                             "label_spacing", &spacing,
330                             NULL);
331     }
332 
333   return spacing;
334 }
335 
336 /**
337  * gimp_frame_new:
338  * @label: text to set as the frame's title label (or %NULL for no title)
339  *
340  * Creates a #GimpFrame widget. A #GimpFrame is a HIG-compliant
341  * variant of #GtkFrame. It doesn't render a frame at all but
342  * otherwise behaves like a frame. The frame's title is rendered in
343  * bold and the frame content is indented four spaces as suggested by
344  * the GNOME HIG (see https://developer.gnome.org/hig/stable/).
345  *
346  * Return value: a new #GimpFrame widget
347  *
348  * Since: 2.2
349  **/
350 GtkWidget *
gimp_frame_new(const gchar * label)351 gimp_frame_new (const gchar *label)
352 {
353   GtkWidget *frame;
354   gboolean   expander = FALSE;
355 
356   /*  somewhat hackish, should perhaps be an object property of GimpFrame  */
357   if (label && strcmp (label, "<expander>") == 0)
358     {
359       expander = TRUE;
360       label    = NULL;
361     }
362 
363   frame = g_object_new (GIMP_TYPE_FRAME,
364                         "label", label,
365                         NULL);
366 
367   if (expander)
368     g_object_set_data (G_OBJECT (frame),
369                        GIMP_FRAME_IN_EXPANDER_KEY, (gpointer) TRUE);
370 
371   return frame;
372 }
373