psppire-cell-renderer-button: Do not update 'event->window'.
[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 #define IDLE_ID_STRING "psppire-cell-renderer-button-idle-id"
296
297 static gboolean
298 psppire_cell_renderer_button_initial_click (gpointer data)
299 {
300   GtkButton *button = data;
301
302   g_object_steal_data (G_OBJECT (button), IDLE_ID_STRING);
303   gtk_button_clicked (button);
304   return FALSE;
305 }
306
307 static void
308 psppire_cell_renderer_button_on_destroy (GObject *object, gpointer user_data)
309 {
310   guint idle_id;
311
312   idle_id = GPOINTER_TO_INT (g_object_steal_data (object, IDLE_ID_STRING));
313   if (idle_id != 0)
314     g_source_remove (idle_id);
315 }
316
317 static void
318 psppire_cell_renderer_button_double_click (GtkButton *button,
319                                            PsppireCellRendererButton *cell_button)
320 {
321   gchar *path;
322
323   if (g_object_get_data (G_OBJECT (button), IDLE_ID_STRING))
324     psppire_cell_renderer_button_initial_click (button);
325
326   g_object_get (button, "path", &path, NULL);
327   g_signal_emit_by_name (cell_button, "double-clicked", path);
328   g_free (path);
329 }
330
331 static gboolean
332 psppire_cell_renderer_button_press_event (GtkButton      *button,
333                                           GdkEventButton *event,
334                                           gpointer        data)
335 {
336   PsppireCellRendererButton *cell_button = data;
337
338   if (event->button == 3)
339     {
340       /* Allow right-click events to propagate upward in the widget hierarchy.
341          Otherwise right-click menus, that trigger on a button-press-event on
342          the containing PsppSheetView, will pop up if the button is rendered as
343          a facade but not if the button widget exists.
344
345          We have to translate the event's data by hand to be relative to the
346          parent window, because the normal GObject signal propagation mechanism
347          won't do it for us.  (This might be a hint that we're doing this
348          wrong.) */
349       gdk_window_coords_to_parent (event->window,
350                                    event->x, event->y,
351                                    &event->x, &event->y);
352       g_signal_stop_emission_by_name (button, "button-press-event");
353       return FALSE;
354     }
355
356   if (cell_button->click_time != 0)
357     {
358       GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (button));
359       GtkSettings *settings = gtk_settings_get_for_screen (screen);
360       gint double_click_distance;
361       gint double_click_time;
362
363       g_object_get (settings,
364                     "gtk-double-click-time", &double_click_time,
365                     "gtk-double-click-distance", &double_click_distance,
366                     NULL);
367
368       if (event->type == GDK_BUTTON_PRESS
369           && event->button == 1
370           && event->time <= cell_button->click_time + double_click_time
371           && ABS (event->x_root - cell_button->click_x) <= double_click_distance
372           && ABS (event->y_root - cell_button->click_y) <= double_click_distance)
373         {
374           psppire_cell_renderer_button_double_click (button, cell_button);
375           return TRUE;
376         }
377
378       cell_button->click_time = 0;
379     }
380
381   if (event->type == GDK_2BUTTON_PRESS)
382     {
383       psppire_cell_renderer_button_double_click (button, cell_button);
384       return TRUE;
385     }
386
387   return FALSE;
388 }
389
390 static GtkCellEditable *
391 psppire_cell_renderer_button_start_editing (GtkCellRenderer      *cell,
392                                             GdkEvent             *event,
393                                             GtkWidget            *widget,
394                                             const gchar          *path,
395                                             GdkRectangle         *background_area,
396                                             GdkRectangle         *cell_area,
397                                             GtkCellRendererState  flags)
398 {
399   PsppireCellRendererButton *cell_button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
400   gfloat xalign, yalign;
401
402   gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
403   cell_button->button = g_object_new (PSPPIRE_TYPE_BUTTON_EDITABLE,
404                                       "label", cell_button->label,
405                                       "xalign", xalign,
406                                       "yalign", yalign,
407                                       "path", path,
408                                       "slash", cell_button->slash,
409                                       NULL);
410
411   g_signal_connect (G_OBJECT (cell_button->button), "clicked",
412                     G_CALLBACK (psppire_cell_renderer_button_clicked),
413                     cell);
414   g_signal_connect (G_OBJECT (cell_button->button), "button-press-event",
415                     G_CALLBACK (psppire_cell_renderer_button_press_event),
416                     cell);
417
418   gtk_widget_show (cell_button->button);
419
420   if (event != NULL && event->any.type == GDK_BUTTON_RELEASE)
421     {
422       guint idle_id;
423
424       cell_button->click_time = event->button.time;
425       cell_button->click_x = event->button.x_root;
426       cell_button->click_y = event->button.y_root;
427       idle_id = g_idle_add (psppire_cell_renderer_button_initial_click,
428                             cell_button->button);
429       g_object_set_data (G_OBJECT (cell_button->button), IDLE_ID_STRING,
430                          GINT_TO_POINTER (idle_id));
431       g_signal_connect (G_OBJECT (cell_button->button), "destroy",
432                         G_CALLBACK (psppire_cell_renderer_button_on_destroy),
433                         NULL);
434     }
435   else
436     {
437       cell_button->click_time = 0;
438       cell_button->click_x = 0;
439       cell_button->click_y = 0;
440     }
441
442   return GTK_CELL_EDITABLE (cell_button->button);
443 }
444
445 static void
446 psppire_cell_renderer_button_class_init (PsppireCellRendererButtonClass *class)
447 {
448   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
449   GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
450
451   gobject_class->set_property = psppire_cell_renderer_button_set_property;
452   gobject_class->get_property = psppire_cell_renderer_button_get_property;
453   gobject_class->finalize = psppire_cell_renderer_button_finalize;
454   gobject_class->dispose = psppire_cell_renderer_button_dispose;
455
456   cell_class->get_size = psppire_cell_renderer_button_get_size;
457   cell_class->render = psppire_cell_renderer_button_render;
458   cell_class->start_editing = psppire_cell_renderer_button_start_editing;
459
460   g_signal_new ("clicked",
461                 G_TYPE_FROM_CLASS (gobject_class),
462                 G_SIGNAL_RUN_LAST,
463                 0,
464                 NULL, NULL,
465                 g_cclosure_marshal_VOID__STRING,
466                 G_TYPE_NONE,
467                 1, G_TYPE_STRING);
468
469   g_signal_new ("double-clicked",
470                 G_TYPE_FROM_CLASS (gobject_class),
471                 G_SIGNAL_RUN_LAST,
472                 0,
473                 NULL, NULL,
474                 g_cclosure_marshal_VOID__STRING,
475                 G_TYPE_NONE,
476                 1, G_TYPE_STRING);
477
478   g_object_class_install_property (gobject_class,
479                                    PROP_EDITABLE,
480                                    g_param_spec_boolean ("editable",
481                                                          "Editable",
482                                                          "Whether the button may be clicked.",
483                                                          FALSE,
484                                                          G_PARAM_READWRITE));
485
486   g_object_class_install_property (gobject_class,
487                                    PROP_LABEL,
488                                    g_param_spec_string ("label",
489                                                         "Label",
490                                                         "Text to appear in button.",
491                                                         "",
492                                                         G_PARAM_READWRITE));
493
494   g_object_class_install_property (gobject_class,
495                                    PROP_SLASH,
496                                    g_param_spec_boolean ("slash",
497                                                          _("Diagonal slash"),
498                                                          _("Whether to draw a diagonal slash across the button."),
499                                                          FALSE,
500                                                          G_PARAM_READWRITE));
501
502 }
503
504 static void
505 psppire_cell_renderer_button_init (PsppireCellRendererButton *obj)
506 {
507   obj->editable = FALSE;
508   obj->label = g_strdup ("");
509   obj->border_width = 0;
510   obj->xpad = 0;
511   obj->ypad = 0;
512
513   obj->slash = FALSE;
514
515   obj->button = NULL;
516
517   obj->button_style = NULL;
518   obj->label_style = NULL;
519   obj->base = NULL;
520   obj->style_set_handler = 0;
521   obj->dispose_has_run = FALSE;
522 }
523
524 static void
525 psppire_cell_renderer_button_finalize (GObject *obj)
526 {
527   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
528
529   g_free (button->label);
530 }
531
532 static void
533 psppire_cell_renderer_button_dispose (GObject *obj)
534 {
535   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
536
537   if (button->dispose_has_run)
538     return;
539   
540   button->dispose_has_run = TRUE;
541
542   /* When called with NULL, as we are doing here, update_style_cache
543      does nothing more than to drop references */
544   update_style_cache (button, NULL);
545
546   G_OBJECT_CLASS (psppire_cell_renderer_button_parent_class)->dispose (obj);
547 }
548
549 GtkCellRenderer *
550 psppire_cell_renderer_button_new (void)
551 {
552   return GTK_CELL_RENDERER (g_object_new (PSPPIRE_TYPE_CELL_RENDERER_BUTTON, NULL));
553 }
554
555 void
556 psppire_cell_renderer_button_set_slash (PsppireCellRendererButton *button,
557                                         gboolean slash)
558 {
559   g_return_if_fail (button != NULL);
560   button->slash = slash;
561 }
562
563 gboolean
564 psppire_cell_renderer_button_get_slash (const PsppireCellRendererButton *button)
565 {
566   g_return_val_if_fail (button != NULL, FALSE);
567   return button->slash;
568 }