src/output/cairo.c (xr_chart_renderer): Set the chart size to 0.8 of the smallest...
[pspp] / src / ui / gui / pspp-widget-facade.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2011, 2012, 2014 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 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
27
28 static void
29 inset_rectangle (const GdkRectangle *src,
30                  const GtkBorder *inset,
31                  GdkRectangle *dst)
32 {
33   dst->x = src->x + inset->left;
34   dst->y = src->y + inset->top;
35   dst->width = MAX (1, src->width - inset->left - inset->right);
36   dst->height = MAX (1, src->height - inset->top - inset->bottom);
37 }
38
39 static void
40 thicken_border (gint x, gint y, GtkBorder *border)
41 {
42   border->left += x;
43   border->right += x;
44   border->top += y;
45   border->bottom += y;
46 }
47
48 GtkStyle *
49 facade_get_style (GtkWidget *base,
50                   GType type1,
51                   ...)
52 {
53   GString path, class_path;
54   GType final_type;
55   GtkStyle *style;
56   va_list args;
57   GType type;
58
59   gtk_widget_path (base, NULL, &path.str, NULL);
60   path.len = path.allocated_len = strlen (path.str);
61
62   gtk_widget_class_path (base, NULL, &class_path.str, NULL);
63   class_path.len = class_path.allocated_len = strlen (class_path.str);
64
65   va_start (args, type1);
66   for (type = final_type = type1; type != 0; type = va_arg (args, GType))
67     {
68       const gchar *type_name = g_type_name (type);
69       g_string_append_printf (&path, ".%s", type_name);
70       g_string_append_printf (&class_path, ".%s", type_name);
71       final_type = type;
72     }
73   va_end (args);
74
75   style = gtk_rc_get_style_by_paths (gtk_widget_get_settings (base),
76                                      path.str, class_path.str, final_type);
77
78   free (path.str);
79   free (class_path.str);
80
81   return style;
82 }
83
84 void
85 facade_hbox_get_base_size_request (gint border_width,
86                                    gint spacing,
87                                    gint n_children,
88                                    GtkRequisition *request)
89 {
90   request->width = border_width * 2;
91   if (n_children > 1)
92     request->width += spacing * (n_children - 1);
93
94   request->height = border_width * 2;
95 }
96
97 void
98 facade_hbox_add_child_size_request (gint hbox_border_width,
99                                     const GtkRequisition *child_request,
100                                     gint child_padding,
101                                     GtkRequisition *request)
102 {
103   request->width += child_request->width + child_padding * 2;
104   request->height = MAX (request->height,
105                          hbox_border_width * 2 + child_request->height);
106 }
107
108 void
109 facade_arrow_get_size_request (gint xpad,
110                                gint ypad,
111                                GtkRequisition *request)
112 {
113 #define MIN_ARROW_SIZE 15
114   request->width = MIN_ARROW_SIZE + xpad * 2;
115   request->height = MIN_ARROW_SIZE + ypad * 2;
116 }
117
118 void
119 facade_alignment_get_size_request (gint border_width,
120                                    gint padding_left,
121                                    gint padding_right,
122                                    gint padding_top,
123                                    gint padding_bottom,
124                                    const GtkRequisition *child_request,
125                                    GtkRequisition *request)
126 {
127   request->width = (border_width * 2 + padding_left + padding_right
128                     + child_request->width);
129   request->height = (border_width * 2 + padding_top + padding_bottom
130                      + child_request->height);
131 }
132
133 void
134 facade_label_get_size_request (gint xpad,
135                                gint ypad,
136                                GtkWidget *base,
137                                const char *text,
138                                GtkRequisition *request)
139 {
140   PangoLayout *layout;
141
142   layout = facade_label_get_layout (base, text);
143   facade_label_get_size_request_from_layout (xpad, ypad, layout, request);
144   g_object_unref (layout);
145 }
146
147 void
148 facade_label_get_size_request_from_layout (gint xpad,
149                                            gint ypad,
150                                            PangoLayout *layout,
151                                            GtkRequisition *request)
152 {
153   PangoRectangle logical_rect;
154
155   pango_layout_get_extents (layout, NULL, &logical_rect);
156   request->width = xpad * 2 + PANGO_PIXELS (logical_rect.width);
157   request->height = ypad * 2 + PANGO_PIXELS (logical_rect.height);
158 }
159
160 PangoLayout *
161 facade_label_get_layout (GtkWidget *base,
162                          const char *text)
163 {
164   PangoAlignment alignment;
165   PangoLayout *layout;
166   gboolean rtl;
167
168   rtl = gtk_widget_get_direction (base) == GTK_TEXT_DIR_RTL;
169   alignment = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
170
171   layout = gtk_widget_create_pango_layout (base, text);
172   pango_layout_set_alignment (layout, alignment);
173
174   return layout;
175 }
176
177 static void
178 facade_button_get_inner_border (GtkStyle *button_style,
179                                 GtkBorder *inner_border)
180 {
181   GtkBorder *tmp_border;
182
183   gtk_style_get (button_style, GTK_TYPE_BUTTON,
184                  "inner-border", &tmp_border,
185                  NULL);
186
187   if (tmp_border)
188     {
189       *inner_border = *tmp_border;
190       gtk_border_free (tmp_border);
191     }
192   else
193     {
194       static const GtkBorder default_inner_border = { 1, 1, 1, 1 };
195       *inner_border = default_inner_border;
196     }
197 }
198
199 void
200 facade_button_get_size_request (gint border_width,
201                                 GtkWidget *base,
202                                 GtkStyle *button_style,
203                                 const GtkRequisition *child_request,
204                                 GtkRequisition *request)
205 {
206   GtkBorder inner_border;
207   gint common_width;
208   gint focus_width;
209   gint focus_pad;
210
211   gtk_style_get (button_style, GTK_TYPE_BUTTON,
212                  "focus-line-width", &focus_width,
213                  "focus-padding", &focus_pad,
214                  NULL);
215   facade_button_get_inner_border (button_style, &inner_border);
216
217   common_width = 2 * (border_width + focus_width + focus_pad);
218   request->width = (common_width
219                     + 2 * button_style->xthickness
220                     + inner_border.left + inner_border.right
221                     + child_request->width);
222   request->height = (common_width
223                      + 2 * button_style->ythickness
224                      + inner_border.top + inner_border.bottom
225                      + child_request->height);
226 }
227
228 void
229 facade_button_get_focus_inset (gint border_width,
230                                GtkWidget *base,
231                                GtkStyle *button_style,
232                                GtkBorder *focus_inset)
233 {
234   facade_button_get_inner_border (button_style, focus_inset);
235   thicken_border (border_width + button_style->xthickness,
236                   border_width + button_style->ythickness,
237                   focus_inset);
238 }
239
240 static void
241 facade_button_get_label_inset (gint border_width,
242                                GtkWidget *base,
243                                GtkStyle *button_style,
244                                GtkBorder *label_inset)
245 {
246   gint focus_width;
247   gint focus_pad;
248
249   facade_button_get_focus_inset (border_width, base, button_style,
250                                  label_inset);
251
252   gtk_style_get (button_style, GTK_TYPE_BUTTON,
253                  "focus-line-width", &focus_width,
254                  "focus-padding", &focus_pad,
255                  NULL);
256   thicken_border (focus_width + focus_pad,
257                   focus_width + focus_pad,
258                   label_inset);
259 }
260
261 static void
262 get_layout_location (GtkWidget *base,
263                      const GdkRectangle *label_area,
264                      PangoLayout *layout,
265                      gint xpad,
266                      gint ypad,
267                      gfloat xalign,
268                      gfloat yalign,
269                      gint *x,
270                      gint *y)
271 {
272   PangoRectangle logical;
273   GtkRequisition req;
274
275   if (gtk_widget_get_direction (base) == GTK_TEXT_DIR_RTL)
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                       cairo_t *cr,
317                       const GdkRectangle *button_area,
318                       gint border_width,
319                       GtkStyle *button_style,
320                       GtkStateType state_type,
321
322                       GtkStyle *label_style,
323                       const gchar *label,
324                       gint xpad,
325                       gint ypad,
326                       gfloat xalign,
327                       gfloat yalign)
328 {
329   GdkRectangle label_area;
330   PangoLayout *layout;
331   GtkBorder inset;
332   gint x, y;
333
334   /* Paint the button. */
335   gtk_paint_box (button_style, cr,
336                  state_type,
337                  GTK_SHADOW_OUT, base, "button",
338                  button_area->x + border_width,
339                  button_area->y + border_width,
340                  button_area->width - border_width * 2,
341                  button_area->height - border_width * 2);
342
343   /* Figure out where the label should go. */
344   facade_button_get_label_inset (border_width, base, button_style, &inset);
345   inset_rectangle (button_area, &inset, &label_area);
346
347   /* Paint the label. */
348   layout = facade_label_get_layout (base, label);
349   get_layout_location (base, &label_area, layout, xpad, ypad, xalign, yalign,
350                        &x, &y);
351
352   gtk_paint_layout (label_style, cr, state_type, FALSE,
353                     base, "label", x, y, layout);
354
355   g_object_unref (layout);
356 }
357