pspp-widget-facade: New code to measure and render some GTK+ widgets.
[pspp] / src / ui / gui / pspp-widget-facade.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2011, 2012 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program 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
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "pspp-widget-facade.h"
20
21 #include <math.h>
22 #include <stdarg.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 static void
27 inset_rectangle (const GdkRectangle *src,
28                  const GtkBorder *inset,
29                  GdkRectangle *dst)
30 {
31   dst->x = src->x + inset->left;
32   dst->y = src->y + inset->top;
33   dst->width = MAX (1, src->width - inset->left - inset->right);
34   dst->height = MAX (1, src->height - inset->top - inset->bottom);
35 }
36
37 static void
38 thicken_border (gint x, gint y, GtkBorder *border)
39 {
40   border->left += x;
41   border->right += x;
42   border->top += y;
43   border->bottom += y;
44 }
45
46 GtkStyle *
47 facade_get_style (GtkWidget *base,
48                   GType type1,
49                   ...)
50 {
51   GString path, class_path;
52   GType final_type;
53   GtkStyle *style;
54   va_list args;
55   GType type;
56
57   gtk_widget_path (base, NULL, &path.str, NULL);
58   path.len = path.allocated_len = strlen (path.str);
59
60   gtk_widget_class_path (base, NULL, &class_path.str, NULL);
61   class_path.len = class_path.allocated_len = strlen (class_path.str);
62
63   va_start (args, type1);
64   for (type = final_type = type1; type != 0; type = va_arg (args, GType))
65     {
66       const gchar *type_name = g_type_name (type);
67       g_string_append_printf (&path, ".%s", type_name);
68       g_string_append_printf (&class_path, ".%s", type_name);
69       final_type = type;
70     }
71   va_end (args);
72
73   style = gtk_rc_get_style_by_paths (gtk_widget_get_settings (base),
74                                      path.str, class_path.str, final_type);
75
76   free (path.str);
77   free (class_path.str);
78
79   return style;
80 }
81
82 void
83 facade_hbox_get_base_size_request (gint border_width,
84                                    gint spacing,
85                                    gint n_children,
86                                    GtkRequisition *request)
87 {
88   request->width = border_width * 2;
89   if (n_children > 1)
90     request->width += spacing * (n_children - 1);
91
92   request->height = border_width * 2;
93 }
94
95 void
96 facade_hbox_add_child_size_request (gint hbox_border_width,
97                                     const GtkRequisition *child_request,
98                                     gint child_padding,
99                                     GtkRequisition *request)
100 {
101   request->width += child_request->width + child_padding * 2;
102   request->height = MAX (request->height,
103                          hbox_border_width * 2 + child_request->height);
104 }
105
106 void
107 facade_arrow_get_size_request (gint xpad,
108                                gint ypad,
109                                GtkRequisition *request)
110 {
111 #define MIN_ARROW_SIZE 15
112   request->width = MIN_ARROW_SIZE + xpad * 2;
113   request->height = MIN_ARROW_SIZE + ypad * 2;
114 }
115
116 void
117 facade_alignment_get_size_request (gint border_width,
118                                    gint padding_left,
119                                    gint padding_right,
120                                    gint padding_top,
121                                    gint padding_bottom,
122                                    const GtkRequisition *child_request,
123                                    GtkRequisition *request)
124 {
125   request->width = (border_width * 2 + padding_left + padding_right
126                     + child_request->width);
127   request->height = (border_width * 2 + padding_top + padding_bottom
128                      + child_request->height);
129 }
130
131 void
132 facade_label_get_size_request (gint xpad,
133                                gint ypad,
134                                GtkWidget *base,
135                                const char *text,
136                                GtkRequisition *request)
137 {
138   PangoLayout *layout;
139
140   layout = facade_label_get_layout (base, text);
141   facade_label_get_size_request_from_layout (xpad, ypad, layout, request);
142   g_object_unref (layout);
143 }
144
145 void
146 facade_label_get_size_request_from_layout (gint xpad,
147                                            gint ypad,
148                                            PangoLayout *layout,
149                                            GtkRequisition *request)
150 {
151   PangoRectangle logical_rect;
152
153   pango_layout_get_extents (layout, NULL, &logical_rect);
154   request->width = xpad * 2 + PANGO_PIXELS (logical_rect.width);
155   request->height = ypad * 2 + PANGO_PIXELS (logical_rect.height);
156 }
157
158 PangoLayout *
159 facade_label_get_layout (GtkWidget *base,
160                          const char *text)
161 {
162   PangoAlignment alignment;
163   PangoLayout *layout;
164   gboolean rtl;
165
166   rtl = gtk_widget_get_direction (base) == GTK_TEXT_DIR_RTL;
167   alignment = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
168
169   layout = gtk_widget_create_pango_layout (base, text);
170   pango_layout_set_alignment (layout, alignment);
171
172   return layout;
173 }
174
175 static void
176 facade_button_get_inner_border (GtkStyle *button_style,
177                                 GtkBorder *inner_border)
178 {
179   GtkBorder *tmp_border;
180
181   gtk_style_get (button_style, GTK_TYPE_BUTTON,
182                  "inner-border", &tmp_border,
183                  NULL);
184
185   if (tmp_border)
186     {
187       *inner_border = *tmp_border;
188       gtk_border_free (tmp_border);
189     }
190   else
191     {
192       static const GtkBorder default_inner_border = { 1, 1, 1, 1 };
193       *inner_border = default_inner_border;
194     }
195 }
196
197 void
198 facade_button_get_size_request (gint border_width,
199                                 GtkWidget *base,
200                                 GtkStyle *button_style,
201                                 const GtkRequisition *child_request,
202                                 GtkRequisition *request)
203 {
204   GtkBorder inner_border;
205   gint common_width;
206   gint focus_width;
207   gint focus_pad;
208
209   gtk_style_get (button_style, GTK_TYPE_BUTTON,
210                  "focus-line-width", &focus_width,
211                  "focus-padding", &focus_pad,
212                  NULL);
213   facade_button_get_inner_border (button_style, &inner_border);
214
215   common_width = 2 * (border_width + focus_width + focus_pad);
216   request->width = (common_width
217                     + 2 * button_style->xthickness
218                     + inner_border.left + inner_border.right
219                     + child_request->width);
220   request->height = (common_width
221                      + 2 * button_style->ythickness
222                      + inner_border.top + inner_border.bottom
223                      + child_request->height);
224 }
225
226 void
227 facade_button_get_focus_inset (gint border_width,
228                                GtkWidget *base,
229                                GtkStyle *button_style,
230                                GtkBorder *focus_inset)
231 {
232   facade_button_get_inner_border (button_style, focus_inset);
233   thicken_border (border_width + button_style->xthickness,
234                   border_width + button_style->ythickness,
235                   focus_inset);
236 }
237
238 static void
239 facade_button_get_label_inset (gint border_width,
240                                GtkWidget *base,
241                                GtkStyle *button_style,
242                                GtkBorder *label_inset)
243 {
244   gint focus_width;
245   gint focus_pad;
246
247   facade_button_get_focus_inset (border_width, base, button_style,
248                                  label_inset);
249
250   gtk_style_get (button_style, GTK_TYPE_BUTTON,
251                  "focus-line-width", &focus_width,
252                  "focus-padding", &focus_pad,
253                  NULL);
254   thicken_border (focus_width + focus_pad,
255                   focus_width + focus_pad,
256                   label_inset);
257 }
258
259 static void
260 get_layout_location (GtkWidget *base,
261                      const GdkRectangle *label_area,
262                      PangoLayout *layout,
263                      gint xpad,
264                      gint ypad,
265                      gfloat xalign,
266                      gfloat yalign,
267                      gint *x,
268                      gint *y)
269 {
270   PangoRectangle logical;
271   GtkRequisition req;
272
273   if (gtk_widget_get_direction (base) == GTK_TEXT_DIR_LTR)
274     xalign = xalign;
275   else
276     xalign = 1.0 - xalign;
277
278   pango_layout_get_pixel_extents (layout, NULL, &logical);
279
280   facade_label_get_size_request_from_layout (xpad, ypad, layout, &req);
281
282   *x = floor (label_area->x + xpad + xalign * (label_area->width - req.width));
283
284   if (gtk_widget_get_direction (base) == GTK_TEXT_DIR_LTR)
285     *x = MAX (*x, label_area->x + xpad);
286   else
287     *x = MIN (*x, label_area->x + label_area->width - xpad);
288   *x -= logical.x;
289
290   /* bgo#315462 - For single-line labels, *do* align the requisition with
291    * respect to the allocation, even if we are under-allocated.  For multi-line
292    * labels, always show the top of the text when they are under-allocated.
293    * The rationale is this:
294    *
295    * - Single-line labels appear in GtkButtons, and it is very easy to get them
296    *   to be smaller than their requisition.  The button may clip the label,
297    *   but the label will still be able to show most of itself and the focus
298    *   rectangle.  Also, it is fairly easy to read a single line of clipped
299    *   text.
300    *
301    * - Multi-line labels should not be clipped to showing "something in the
302    *   middle".  You want to read the first line, at least, to get some
303    *   context.
304    */
305   if (pango_layout_get_line_count (layout) == 1)
306     *y = floor (label_area->y + ypad
307                 + (label_area->height - req.height) * yalign);
308   else
309     *y = floor (label_area->y + ypad
310                 + MAX (((label_area->height - req.height) * yalign),
311                        0));
312 }
313
314 void
315 facade_button_render (GtkWidget *base,
316                       GdkDrawable *window,
317                       GdkRectangle *expose_area,
318
319                       GdkRectangle *button_area,
320                       gint border_width,
321                       GtkStyle *button_style,
322                       GtkStateType state_type,
323
324                       GtkStyle *label_style,
325                       const gchar *label,
326                       gint xpad,
327                       gint ypad,
328                       gfloat xalign,
329                       gfloat yalign)
330 {
331   GdkRectangle label_area;
332   PangoLayout *layout;
333   GtkBorder inset;
334   gint x, y;
335
336   /* Paint the button. */
337   gtk_paint_box (button_style, window,
338                  state_type,
339                  GTK_SHADOW_OUT, expose_area, base, "button",
340                  button_area->x + border_width,
341                  button_area->y + border_width,
342                  button_area->width - border_width * 2,
343                  button_area->height - border_width * 2);
344
345   /* Figure out where the label should go. */
346   facade_button_get_label_inset (border_width, base, button_style, &inset);
347   inset_rectangle (button_area, &inset, &label_area);
348
349   /* Paint the label. */
350   layout = facade_label_get_layout (base, label);
351   get_layout_location (base, &label_area, layout, xpad, ypad, xalign, yalign,
352                        &x, &y);
353   gtk_paint_layout (label_style, window, state_type, FALSE, expose_area,
354                     base, "label", x, y, layout);
355   g_object_unref (layout);
356 }
357