src/output/cairo.c (xr_chart_renderer): Set the chart size to 0.8 of the smallest...
[pspp] / src / ui / gui / psppire-cell-renderer-button.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 "ui/gui/psppire-cell-renderer-button.h"
20
21 #include <math.h>
22 #include <string.h>
23
24 #include "ui/gui/psppire-button-editable.h"
25 #include "ui/gui/pspp-widget-facade.h"
26
27 #include "gl/configmake.h"
28 #include "gl/relocatable.h"
29
30 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
31
32 #define P_(msgid) (msgid)
33
34 static void psppire_cell_renderer_button_dispose (GObject *);
35 static void psppire_cell_renderer_button_finalize (GObject *);
36
37 static void update_style_cache (PsppireCellRendererButton *button,
38                                 GtkWidget                 *widget);
39
40 static void psppire_cell_renderer_button_load_gtkrc (void);
41
42
43 G_DEFINE_TYPE_EXTENDED (PsppireCellRendererButton,
44                         psppire_cell_renderer_button,
45                         GTK_TYPE_CELL_RENDERER,
46                         0,
47                         psppire_cell_renderer_button_load_gtkrc ());
48
49 static void
50 psppire_cell_renderer_button_load_gtkrc (void)
51 {
52   const char *gtkrc_file;
53
54   gtkrc_file = relocate (PKGDATADIR "/psppire.gtkrc");
55   gtk_rc_add_default_file (gtkrc_file);
56   gtk_rc_parse (gtkrc_file);
57 }
58
59 enum
60   {
61     PROP_0,
62     PROP_EDITABLE,
63     PROP_LABEL,
64     PROP_SLASH
65   };
66
67 static void
68 psppire_cell_renderer_button_set_property (GObject      *object,
69                                            guint         prop_id,
70                                            const GValue *value,
71                                            GParamSpec   *pspec)
72 {
73   PsppireCellRendererButton *obj = PSPPIRE_CELL_RENDERER_BUTTON (object);
74   switch (prop_id)
75     {
76     case PROP_EDITABLE:
77       obj->editable = g_value_get_boolean (value);
78       if (obj->editable)
79         g_object_set (obj, "mode", GTK_CELL_RENDERER_MODE_EDITABLE, NULL);
80       else
81         g_object_set (obj, "mode", GTK_CELL_RENDERER_MODE_INERT, NULL);
82       break;
83
84     case PROP_LABEL:
85       g_free (obj->label);
86       obj->label = g_value_dup_string (value);
87       break;
88
89     case PROP_SLASH:
90       psppire_cell_renderer_button_set_slash (obj,
91                                               g_value_get_boolean (value));
92       break;
93
94     default:
95       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
96       break;
97     }
98 }
99
100 static void
101 psppire_cell_renderer_button_get_property (GObject      *object,
102                                            guint         prop_id,
103                                            GValue       *value,
104                                            GParamSpec   *pspec)
105 {
106   PsppireCellRendererButton *obj = PSPPIRE_CELL_RENDERER_BUTTON (object);
107
108   switch (prop_id)
109     {
110     case PROP_EDITABLE:
111       g_value_set_boolean (value, obj->editable);
112       break;
113
114     case PROP_LABEL:
115       g_value_set_string (value, obj->label);
116       break;
117
118     case PROP_SLASH:
119       g_value_set_boolean (value,
120                            psppire_cell_renderer_button_get_slash (obj));
121       break;
122
123     default:
124       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
125       break;
126     }
127 }
128
129 static void
130 on_style_set (GtkWidget                 *base,
131               GtkStyle                  *previous_style,
132               PsppireCellRendererButton *button)
133 {
134   update_style_cache (button, NULL);
135 }
136
137 static void
138 update_style_cache (PsppireCellRendererButton *button,
139                     GtkWidget                 *widget)
140 {
141   if (button->base == widget)
142     return;
143
144   /* Clear old cache. */
145   if (button->button_style)
146     {
147       g_object_unref (button->button_style);
148       button->button_style = NULL;
149     }
150   if (button->label_style)
151     {
152       g_object_unref (button->label_style);
153       button->label_style = NULL;
154     }
155   if (button->base != NULL)
156     {
157       if (button->style_set_handler)
158         {
159           g_signal_handler_disconnect (button->base,
160                                        button->style_set_handler);
161           button->style_set_handler = 0;
162         }
163       g_object_unref (button->base);
164       button->base = NULL;
165     }
166
167   /* Populate cache. */
168   if (widget)
169     {
170       button->button_style = facade_get_style (widget, GTK_TYPE_BUTTON, 0);
171       button->label_style = facade_get_style (widget, GTK_TYPE_BUTTON,
172                                               GTK_TYPE_LABEL, 0);
173       button->base = widget;
174       button->style_set_handler = g_signal_connect (widget, "style-set",
175                                                     G_CALLBACK (on_style_set),
176                                                     button);
177       g_object_ref (widget);
178       g_object_ref (button->button_style);
179       g_object_ref (button->label_style);
180     }
181 }
182
183 static void
184 psppire_cell_renderer_button_render (GtkCellRenderer      *cell,
185                                      cairo_t              *cr,
186                                      GtkWidget            *widget,
187                                      const GdkRectangle         *background_area,
188                                      const GdkRectangle         *cell_area,
189                                      GtkCellRendererState  flags)
190 {
191   GtkStateType state_type;
192   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
193   gfloat xalign, yalign;
194
195   if (!button->editable || ! gtk_cell_renderer_get_sensitive (cell))
196     state_type = GTK_STATE_INSENSITIVE;
197   else if (flags & GTK_CELL_RENDERER_SELECTED)
198     {
199       if (gtk_widget_has_focus (widget))
200         state_type = GTK_STATE_SELECTED;
201       else
202         state_type = GTK_STATE_ACTIVE;
203     }
204   else if (flags & GTK_CELL_RENDERER_PRELIT)
205     state_type = GTK_STATE_PRELIGHT;
206   else
207     {
208       if (gtk_widget_get_state_flags (widget) == GTK_STATE_FLAG_INSENSITIVE)
209         state_type = GTK_STATE_INSENSITIVE;
210       else
211         state_type = GTK_STATE_NORMAL;
212     }
213
214   gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
215
216
217   update_style_cache (button, widget);
218
219   facade_button_render (widget, cr,
220                         cell_area, button->border_width, button->button_style,
221                         state_type,
222                         button->label_style, button->label, button->xpad,
223                         button->ypad, xalign, yalign);
224
225   if (button->slash)
226     {
227       cairo_set_line_width (cr, 1.0);
228       cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
229       cairo_move_to (cr,
230                      cell_area->x,
231                      cell_area->y + cell_area->height);
232
233       cairo_line_to (cr,
234                      cell_area->x + cell_area->width,
235                      cell_area->y);
236       cairo_stroke (cr);
237     }
238 }
239
240 static void
241 psppire_cell_renderer_button_get_size (GtkCellRenderer      *cell,
242                                        GtkWidget            *widget,
243                                        const GdkRectangle   *cell_area,
244                                        gint                 *x_offset,
245                                        gint                 *y_offset,
246                                        gint                 *width,
247                                        gint                 *height)
248 {
249   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
250
251   update_style_cache (button, widget);
252   if (cell_area != NULL)
253     {
254       /* The caller is really asking for the placement of the focus rectangle.
255          The focus rectangle should surround the whole label area, so calculate
256          that area. */
257       GtkBorder inset;
258
259       facade_button_get_focus_inset (button->border_width, widget,
260                                      button->button_style, &inset);
261
262       if (x_offset)
263         *x_offset = inset.left;
264       if (y_offset)
265         *y_offset = inset.top;
266       if (width)
267         *width = MAX (1, cell_area->width - inset.left - inset.right);
268       if (height)
269         *height = MAX (1, cell_area->height - inset.top - inset.bottom);
270     }
271   else
272     {
273       /* The caller is asking for the preferred size of the cell. */
274       GtkRequisition label_req;
275       GtkRequisition request;
276
277       facade_label_get_size_request (button->xpad, button->ypad,
278                                      widget, button->label, &label_req);
279       facade_button_get_size_request (button->border_width, widget,
280                                       button->button_style, &label_req,
281                                       &request);
282
283       if (x_offset)
284         *x_offset = 0;
285       if (y_offset)
286         *y_offset = 0;
287       if (width)
288         *width = request.width;
289       if (height)
290         *height = request.height;
291     }
292 }
293
294 static void
295 psppire_cell_renderer_button_clicked (GtkButton *button,
296                                       gpointer   data)
297 {
298   PsppireCellRendererButton *cell_button = data;
299   gchar *path;
300
301   g_object_get (button, "path", &path, NULL);
302   g_signal_emit_by_name (cell_button, "clicked", path);
303   g_free (path);
304 }
305
306 #define IDLE_ID_STRING "psppire-cell-renderer-button-idle-id"
307
308 static gboolean
309 psppire_cell_renderer_button_initial_click (gpointer data)
310 {
311   GtkButton *button = data;
312
313   g_object_steal_data (G_OBJECT (button), IDLE_ID_STRING);
314   gtk_button_clicked (button);
315   return FALSE;
316 }
317
318 static void
319 psppire_cell_renderer_button_on_destroy (GObject *object, gpointer user_data)
320 {
321   guint idle_id;
322
323   idle_id = GPOINTER_TO_INT (g_object_steal_data (object, IDLE_ID_STRING));
324   if (idle_id != 0)
325     g_source_remove (idle_id);
326 }
327
328 static void
329 psppire_cell_renderer_button_double_click (GtkButton *button,
330                                            PsppireCellRendererButton *cell_button)
331 {
332   gchar *path;
333
334   if (g_object_get_data (G_OBJECT (button), IDLE_ID_STRING))
335     psppire_cell_renderer_button_initial_click (button);
336
337   g_object_get (button, "path", &path, NULL);
338   g_signal_emit_by_name (cell_button, "double-clicked", path);
339   g_free (path);
340 }
341
342 static gboolean
343 psppire_cell_renderer_button_press_event (GtkButton      *button,
344                                           GdkEventButton *event,
345                                           gpointer        data)
346 {
347   PsppireCellRendererButton *cell_button = data;
348
349   if (event->button == 3)
350     {
351       /* Allow right-click events to propagate upward in the widget hierarchy.
352          Otherwise right-click menus, that trigger on a button-press-event on
353          the containing PsppSheetView, will pop up if the button is rendered as
354          a facade but not if the button widget exists.
355
356          We have to translate the event's data by hand to be relative to the
357          parent window, because the normal GObject signal propagation mechanism
358          won't do it for us.  (This might be a hint that we're doing this
359          wrong.) */
360       gint x, y;
361
362       gdk_window_get_position (event->window, &x, &y);
363       event->x += x;
364       event->y += y;
365       g_signal_stop_emission_by_name (button, "button-press-event");
366       return FALSE;
367     }
368
369   if (cell_button->click_time != 0)
370     {
371       GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (button));
372       GtkSettings *settings = gtk_settings_get_for_screen (screen);
373       gint double_click_distance;
374       gint double_click_time;
375
376       g_object_get (settings,
377                     "gtk-double-click-time", &double_click_time,
378                     "gtk-double-click-distance", &double_click_distance,
379                     NULL);
380
381       if (event->type == GDK_BUTTON_PRESS
382           && event->button == 1
383           && event->time <= cell_button->click_time + double_click_time
384           && ABS (event->x_root - cell_button->click_x) <= double_click_distance
385           && ABS (event->y_root - cell_button->click_y) <= double_click_distance)
386         {
387           psppire_cell_renderer_button_double_click (button, cell_button);
388           return TRUE;
389         }
390
391       cell_button->click_time = 0;
392     }
393
394   if (event->type == GDK_2BUTTON_PRESS)
395     {
396       psppire_cell_renderer_button_double_click (button, cell_button);
397       return TRUE;
398     }
399
400   return FALSE;
401 }
402
403 static GtkCellEditable *
404 psppire_cell_renderer_button_start_editing (GtkCellRenderer      *cell,
405                                             GdkEvent             *event,
406                                             GtkWidget            *widget,
407                                             const gchar          *path,
408                                             const GdkRectangle   *background_area,
409                                             const GdkRectangle   *cell_area,
410                                             GtkCellRendererState  flags)
411 {
412   PsppireCellRendererButton *cell_button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
413   gfloat xalign, yalign;
414
415   gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
416   cell_button->button = g_object_new (PSPPIRE_TYPE_BUTTON_EDITABLE,
417                                       "label", cell_button->label,
418                                       "xalign", xalign,
419                                       "yalign", yalign,
420                                       "path", path,
421                                       NULL);
422
423   g_signal_connect (G_OBJECT (cell_button->button), "clicked",
424                     G_CALLBACK (psppire_cell_renderer_button_clicked),
425                     cell);
426   g_signal_connect (G_OBJECT (cell_button->button), "button-press-event",
427                     G_CALLBACK (psppire_cell_renderer_button_press_event),
428                     cell);
429
430   gtk_widget_show (cell_button->button);
431
432   if (event != NULL && event->any.type == GDK_BUTTON_RELEASE)
433     {
434       guint idle_id;
435
436       cell_button->click_time = event->button.time;
437       cell_button->click_x = event->button.x_root;
438       cell_button->click_y = event->button.y_root;
439       idle_id = g_idle_add (psppire_cell_renderer_button_initial_click,
440                             cell_button->button);
441       g_object_set_data (G_OBJECT (cell_button->button), IDLE_ID_STRING,
442                          GINT_TO_POINTER (idle_id));
443       g_signal_connect (G_OBJECT (cell_button->button), "destroy",
444                         G_CALLBACK (psppire_cell_renderer_button_on_destroy),
445                         NULL);
446     }
447   else
448     {
449       cell_button->click_time = 0;
450       cell_button->click_x = 0;
451       cell_button->click_y = 0;
452     }
453
454   return GTK_CELL_EDITABLE (cell_button->button);
455 }
456
457 static void
458 psppire_cell_renderer_button_class_init (PsppireCellRendererButtonClass *class)
459 {
460   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
461   GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
462
463   gobject_class->set_property = psppire_cell_renderer_button_set_property;
464   gobject_class->get_property = psppire_cell_renderer_button_get_property;
465   gobject_class->finalize = psppire_cell_renderer_button_finalize;
466   gobject_class->dispose = psppire_cell_renderer_button_dispose;
467
468   cell_class->get_size = psppire_cell_renderer_button_get_size;
469   cell_class->render = psppire_cell_renderer_button_render;
470   cell_class->start_editing = psppire_cell_renderer_button_start_editing;
471
472   g_signal_new ("clicked",
473                 G_TYPE_FROM_CLASS (gobject_class),
474                 G_SIGNAL_RUN_LAST,
475                 0,
476                 NULL, NULL,
477                 g_cclosure_marshal_VOID__STRING,
478                 G_TYPE_NONE,
479                 1, G_TYPE_STRING);
480
481   g_signal_new ("double-clicked",
482                 G_TYPE_FROM_CLASS (gobject_class),
483                 G_SIGNAL_RUN_LAST,
484                 0,
485                 NULL, NULL,
486                 g_cclosure_marshal_VOID__STRING,
487                 G_TYPE_NONE,
488                 1, G_TYPE_STRING);
489
490   g_object_class_install_property (gobject_class,
491                                    PROP_EDITABLE,
492                                    g_param_spec_boolean ("editable",
493                                                          P_("Editable"),
494                                                          P_("Whether the button may be clicked."),
495                                                          FALSE,
496                                                          G_PARAM_READWRITE));
497
498   g_object_class_install_property (gobject_class,
499                                    PROP_LABEL,
500                                    g_param_spec_string ("label",
501                                                         P_("Label"),
502                                                         P_("Text to appear in button."),
503                                                         "",
504                                                         G_PARAM_READWRITE));
505
506   g_object_class_install_property (gobject_class,
507                                    PROP_SLASH,
508                                    g_param_spec_boolean ("slash",
509                                                          P_("Diagonal slash"),
510                                                          P_("Whether to draw a diagonal slash across the button."),
511                                                          FALSE,
512                                                          G_PARAM_READWRITE));
513
514 }
515
516 static void
517 psppire_cell_renderer_button_init (PsppireCellRendererButton *obj)
518 {
519   obj->editable = FALSE;
520   obj->label = g_strdup ("");
521   obj->border_width = 0;
522   obj->xpad = 0;
523   obj->ypad = 0;
524
525   obj->slash = FALSE;
526
527   obj->button = NULL;
528
529   obj->button_style = NULL;
530   obj->label_style = NULL;
531   obj->base = NULL;
532   obj->style_set_handler = 0;
533   obj->dispose_has_run = FALSE;
534 }
535
536 static void
537 psppire_cell_renderer_button_finalize (GObject *obj)
538 {
539   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
540
541   g_free (button->label);
542 }
543
544 static void
545 psppire_cell_renderer_button_dispose (GObject *obj)
546 {
547   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
548
549   if (button->dispose_has_run)
550     return;
551
552   button->dispose_has_run = TRUE;
553
554   /* When called with NULL, as we are doing here, update_style_cache
555      does nothing more than to drop references */
556   update_style_cache (button, NULL);
557
558   G_OBJECT_CLASS (psppire_cell_renderer_button_parent_class)->dispose (obj);
559 }
560
561 GtkCellRenderer *
562 psppire_cell_renderer_button_new (void)
563 {
564   return GTK_CELL_RENDERER (g_object_new (PSPPIRE_TYPE_CELL_RENDERER_BUTTON, NULL));
565 }
566
567 void
568 psppire_cell_renderer_button_set_slash (PsppireCellRendererButton *button,
569                                         gboolean slash)
570 {
571   g_return_if_fail (button != NULL);
572   button->slash = slash;
573 }
574
575 gboolean
576 psppire_cell_renderer_button_get_slash (const PsppireCellRendererButton *button)
577 {
578   g_return_val_if_fail (button != NULL, FALSE);
579   return button->slash;
580 }