Merge "master" into "psppsheet" to obtain bug fixes from "master".
[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
74   switch (prop_id)
75     {
76     case PROP_EDITABLE:
77       obj->editable = g_value_get_boolean (value);
78       if (obj->editable)
79         GTK_CELL_RENDERER (obj)->mode = GTK_CELL_RENDERER_MODE_EDITABLE;
80       else
81         GTK_CELL_RENDERER (obj)->mode = GTK_CELL_RENDERER_MODE_INERT;
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                                      GdkDrawable          *window,
186                                      GtkWidget            *widget,
187                                      GdkRectangle         *background_area,
188                                      GdkRectangle         *cell_area,
189                                      GdkRectangle         *expose_area,
190                                      GtkCellRendererState  flags)
191 {
192   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
193   GtkStateType state_type;
194
195   if (!button->editable || !cell->sensitive)
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 (widget) == GTK_STATE_INSENSITIVE)
209         state_type = GTK_STATE_INSENSITIVE;
210       else
211         state_type = GTK_STATE_NORMAL;
212     }
213
214   update_style_cache (button, widget);
215   facade_button_render (widget, window, expose_area,
216                         cell_area, button->border_width, button->button_style,
217                         state_type,
218                         button->label_style, button->label, button->xpad,
219                         button->ypad, cell->xalign, cell->yalign);
220
221   if (button->slash)
222     gdk_draw_line (window, button->button_style->black_gc,
223                    cell_area->x,
224                    cell_area->y + cell_area->height,
225                    cell_area->x + cell_area->width,
226                    cell_area->y);
227 }
228
229 static void
230 psppire_cell_renderer_button_get_size (GtkCellRenderer      *cell,
231                                        GtkWidget            *widget,
232                                        GdkRectangle         *cell_area,
233                                        gint                 *x_offset,
234                                        gint                 *y_offset,
235                                        gint                 *width,
236                                        gint                 *height)
237 {
238   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
239
240   update_style_cache (button, widget);
241   if (cell_area != NULL)
242     {
243       /* The caller is really asking for the placement of the focus rectangle.
244          The focus rectangle should surround the whole label area, so calculate
245          that area. */
246       GtkBorder inset;
247
248       facade_button_get_focus_inset (button->border_width, widget,
249                                      button->button_style, &inset);
250
251       if (x_offset)
252         *x_offset = inset.left;
253       if (y_offset)
254         *y_offset = inset.top;
255       if (width)
256         *width = MAX (1, cell_area->width - inset.left - inset.right);
257       if (height)
258         *height = MAX (1, cell_area->height - inset.top - inset.bottom);
259     }
260   else
261     {
262       /* The caller is asking for the preferred size of the cell. */
263       GtkRequisition label_req;
264       GtkRequisition request;
265
266       facade_label_get_size_request (button->xpad, button->ypad,
267                                      widget, button->label, &label_req);
268       facade_button_get_size_request (button->border_width, widget,
269                                       button->button_style, &label_req,
270                                       &request);
271
272       if (x_offset)
273         *x_offset = 0;
274       if (y_offset)
275         *y_offset = 0;
276       if (width)
277         *width = request.width;
278       if (height)
279         *height = request.height;
280     }
281 }
282
283 static void
284 psppire_cell_renderer_button_clicked (GtkButton *button,
285                                       gpointer   data)
286 {
287   PsppireCellRendererButton *cell_button = data;
288   gchar *path;
289
290   g_object_get (button, "path", &path, NULL);
291   g_signal_emit_by_name (cell_button, "clicked", path);
292   g_free (path);
293 }
294
295 static gboolean
296 psppire_cell_renderer_button_focus_out_event (GtkWidget *widget,
297                                               GdkEvent  *event,
298                                               gpointer   data)
299 {
300   PsppireCellRendererButton *cell_button = data;
301
302   g_signal_handlers_disconnect_by_func (widget,
303                                         psppire_cell_renderer_button_focus_out_event,
304                                         data);
305   g_signal_handlers_disconnect_by_func (widget,
306                                         psppire_cell_renderer_button_clicked,
307                                         data);
308
309   gtk_cell_renderer_stop_editing (GTK_CELL_RENDERER (cell_button), FALSE);
310
311   return FALSE;
312 }
313
314 #define IDLE_ID_STRING "psppire-cell-renderer-button-idle-id"
315
316 static gboolean
317 psppire_cell_renderer_button_initial_click (gpointer data)
318 {
319   GtkButton *button = data;
320
321   gtk_button_clicked (button);
322   g_object_steal_data (G_OBJECT (button), IDLE_ID_STRING);
323   return FALSE;
324 }
325
326 static void
327 psppire_cell_renderer_button_on_destroy (GObject *object, gpointer user_data)
328 {
329   guint idle_id;
330
331   idle_id = GPOINTER_TO_INT (g_object_steal_data (object, IDLE_ID_STRING));
332   if (idle_id != 0)
333     g_source_remove (idle_id);
334 }
335
336 static void
337 psppire_cell_renderer_button_double_click (GtkButton *button,
338                                            PsppireCellRendererButton *cell_button)
339 {
340   gchar *path;
341
342   if (g_object_get_data (G_OBJECT (button), IDLE_ID_STRING))
343     psppire_cell_renderer_button_initial_click (button);
344
345   g_object_get (button, "path", &path, NULL);
346   g_signal_emit_by_name (cell_button, "double-clicked", path);
347   g_free (path);
348 }
349
350 static gboolean
351 psppire_cell_renderer_button_press_event (GtkButton      *button,
352                                           GdkEventButton *event,
353                                           gpointer        data)
354 {
355   PsppireCellRendererButton *cell_button = data;
356
357   if (event->button == 3)
358     {
359       /* Allow right-click events to propagate upward in the widget hierarchy.
360          Otherwise right-click menus, that trigger on a button-press-event on
361          the containing PsppSheetView, will pop up if the button is rendered as
362          a facade but not if the button widget exists.  */
363       g_signal_stop_emission_by_name (button, "button-press-event");
364       return FALSE;
365     }
366
367   if (cell_button->click_time != 0)
368     {
369       GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (button));
370       GtkSettings *settings = gtk_settings_get_for_screen (screen);
371       gint double_click_distance;
372       gint double_click_time;
373
374       g_object_get (settings,
375                     "gtk-double-click-time", &double_click_time,
376                     "gtk-double-click-distance", &double_click_distance,
377                     NULL);
378
379       if (event->type == GDK_BUTTON_PRESS
380           && event->button == 1
381           && event->time <= cell_button->click_time + double_click_time
382           && ABS (event->x_root - cell_button->click_x) <= double_click_distance
383           && ABS (event->y_root - cell_button->click_y) <= double_click_distance)
384         {
385           psppire_cell_renderer_button_double_click (button, cell_button);
386           return TRUE;
387         }
388
389       cell_button->click_time = 0;
390     }
391
392   if (event->type == GDK_2BUTTON_PRESS)
393     {
394       psppire_cell_renderer_button_double_click (button, cell_button);
395       return TRUE;
396     }
397
398   return FALSE;
399 }
400
401 static GtkCellEditable *
402 psppire_cell_renderer_button_start_editing (GtkCellRenderer      *cell,
403                                             GdkEvent             *event,
404                                             GtkWidget            *widget,
405                                             const gchar          *path,
406                                             GdkRectangle         *background_area,
407                                             GdkRectangle         *cell_area,
408                                             GtkCellRendererState  flags)
409 {
410   PsppireCellRendererButton *cell_button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
411   gfloat xalign, yalign;
412
413   gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
414   cell_button->button = g_object_new (PSPPIRE_TYPE_BUTTON_EDITABLE,
415                                       "label", cell_button->label,
416                                       "xalign", xalign,
417                                       "yalign", yalign,
418                                       "path", path,
419                                       "slash", cell_button->slash,
420                                       NULL);
421
422   g_signal_connect (G_OBJECT (cell_button->button), "focus-out-event",
423                     G_CALLBACK (psppire_cell_renderer_button_focus_out_event),
424                     cell);
425   g_signal_connect (G_OBJECT (cell_button->button), "clicked",
426                     G_CALLBACK (psppire_cell_renderer_button_clicked),
427                     cell);
428   g_signal_connect (G_OBJECT (cell_button->button), "button-press-event",
429                     G_CALLBACK (psppire_cell_renderer_button_press_event),
430                     cell);
431
432   gtk_widget_show (cell_button->button);
433
434   if (event != NULL && event->any.type == GDK_BUTTON_RELEASE)
435     {
436       guint idle_id;
437
438       cell_button->click_time = event->button.time;
439       cell_button->click_x = event->button.x_root;
440       cell_button->click_y = event->button.y_root;
441       idle_id = g_idle_add (psppire_cell_renderer_button_initial_click,
442                             cell_button->button);
443       g_object_set_data (G_OBJECT (cell_button->button), IDLE_ID_STRING,
444                          GINT_TO_POINTER (idle_id));
445       g_signal_connect (G_OBJECT (cell_button->button), "destroy",
446                         G_CALLBACK (psppire_cell_renderer_button_on_destroy),
447                         NULL);
448     }
449   else
450     {
451       cell_button->click_time = 0;
452       cell_button->click_x = 0;
453       cell_button->click_y = 0;
454     }
455
456   return GTK_CELL_EDITABLE (cell_button->button);
457 }
458
459 static void
460 psppire_cell_renderer_button_class_init (PsppireCellRendererButtonClass *class)
461 {
462   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
463   GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
464
465   gobject_class->set_property = psppire_cell_renderer_button_set_property;
466   gobject_class->get_property = psppire_cell_renderer_button_get_property;
467   gobject_class->finalize = psppire_cell_renderer_button_finalize;
468   gobject_class->dispose = psppire_cell_renderer_button_dispose;
469
470   cell_class->get_size = psppire_cell_renderer_button_get_size;
471   cell_class->render = psppire_cell_renderer_button_render;
472   cell_class->start_editing = psppire_cell_renderer_button_start_editing;
473
474   g_signal_new ("clicked",
475                 G_TYPE_FROM_CLASS (gobject_class),
476                 G_SIGNAL_RUN_LAST,
477                 0,
478                 NULL, NULL,
479                 g_cclosure_marshal_VOID__STRING,
480                 G_TYPE_NONE,
481                 1, G_TYPE_STRING);
482
483   g_signal_new ("double-clicked",
484                 G_TYPE_FROM_CLASS (gobject_class),
485                 G_SIGNAL_RUN_LAST,
486                 0,
487                 NULL, NULL,
488                 g_cclosure_marshal_VOID__STRING,
489                 G_TYPE_NONE,
490                 1, G_TYPE_STRING);
491
492   g_object_class_install_property (gobject_class,
493                                    PROP_EDITABLE,
494                                    g_param_spec_boolean ("editable",
495                                                          "Editable",
496                                                          "Whether the button may be clicked.",
497                                                          FALSE,
498                                                          G_PARAM_READWRITE));
499
500   g_object_class_install_property (gobject_class,
501                                    PROP_LABEL,
502                                    g_param_spec_string ("label",
503                                                         "Label",
504                                                         "Text to appear in button.",
505                                                         "",
506                                                         G_PARAM_READWRITE));
507
508   g_object_class_install_property (gobject_class,
509                                    PROP_SLASH,
510                                    g_param_spec_boolean ("slash",
511                                                          _("Diagonal slash"),
512                                                          _("Whether to draw a diagonal slash across the button."),
513                                                          FALSE,
514                                                          G_PARAM_READWRITE));
515
516 }
517
518 static void
519 psppire_cell_renderer_button_init (PsppireCellRendererButton *obj)
520 {
521   obj->editable = FALSE;
522   obj->label = g_strdup ("");
523   obj->border_width = 0;
524   obj->xpad = 0;
525   obj->ypad = 0;
526
527   obj->slash = FALSE;
528
529   obj->button = NULL;
530
531   obj->button_style = NULL;
532   obj->label_style = NULL;
533   obj->base = NULL;
534   obj->style_set_handler = 0;
535   obj->dispose_has_run = FALSE;
536 }
537
538 static void
539 psppire_cell_renderer_button_finalize (GObject *obj)
540 {
541   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
542
543   g_free (button->label);
544 }
545
546 static void
547 psppire_cell_renderer_button_dispose (GObject *obj)
548 {
549   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
550
551   if (button->dispose_has_run)
552     return;
553   
554   button->dispose_has_run = TRUE;
555
556   /* When called with NULL, as we are doing here, update_style_cache
557      does nothing more than to drop references */
558   update_style_cache (button, NULL);
559
560   G_OBJECT_CLASS (psppire_cell_renderer_button_parent_class)->dispose (obj);
561 }
562
563 GtkCellRenderer *
564 psppire_cell_renderer_button_new (void)
565 {
566   return GTK_CELL_RENDERER (g_object_new (PSPPIRE_TYPE_CELL_RENDERER_BUTTON, NULL));
567 }
568
569 void
570 psppire_cell_renderer_button_set_slash (PsppireCellRendererButton *button,
571                                         gboolean slash)
572 {
573   g_return_if_fail (button != NULL);
574   button->slash = slash;
575 }
576
577 gboolean
578 psppire_cell_renderer_button_get_slash (const PsppireCellRendererButton *button)
579 {
580   g_return_val_if_fail (button != NULL, FALSE);
581   return button->slash;
582 }