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