psppire-cell-renderer-button: Don't use gdk_window_coords_to_parent().
[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       gint x, y;
350
351       gdk_window_get_position (event->window, &x, &y);
352       event->x += x;
353       event->y += y;
354       g_signal_stop_emission_by_name (button, "button-press-event");
355       return FALSE;
356     }
357
358   if (cell_button->click_time != 0)
359     {
360       GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (button));
361       GtkSettings *settings = gtk_settings_get_for_screen (screen);
362       gint double_click_distance;
363       gint double_click_time;
364
365       g_object_get (settings,
366                     "gtk-double-click-time", &double_click_time,
367                     "gtk-double-click-distance", &double_click_distance,
368                     NULL);
369
370       if (event->type == GDK_BUTTON_PRESS
371           && event->button == 1
372           && event->time <= cell_button->click_time + double_click_time
373           && ABS (event->x_root - cell_button->click_x) <= double_click_distance
374           && ABS (event->y_root - cell_button->click_y) <= double_click_distance)
375         {
376           psppire_cell_renderer_button_double_click (button, cell_button);
377           return TRUE;
378         }
379
380       cell_button->click_time = 0;
381     }
382
383   if (event->type == GDK_2BUTTON_PRESS)
384     {
385       psppire_cell_renderer_button_double_click (button, cell_button);
386       return TRUE;
387     }
388
389   return FALSE;
390 }
391
392 static GtkCellEditable *
393 psppire_cell_renderer_button_start_editing (GtkCellRenderer      *cell,
394                                             GdkEvent             *event,
395                                             GtkWidget            *widget,
396                                             const gchar          *path,
397                                             GdkRectangle         *background_area,
398                                             GdkRectangle         *cell_area,
399                                             GtkCellRendererState  flags)
400 {
401   PsppireCellRendererButton *cell_button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
402   gfloat xalign, yalign;
403
404   gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
405   cell_button->button = g_object_new (PSPPIRE_TYPE_BUTTON_EDITABLE,
406                                       "label", cell_button->label,
407                                       "xalign", xalign,
408                                       "yalign", yalign,
409                                       "path", path,
410                                       "slash", cell_button->slash,
411                                       NULL);
412
413   g_signal_connect (G_OBJECT (cell_button->button), "clicked",
414                     G_CALLBACK (psppire_cell_renderer_button_clicked),
415                     cell);
416   g_signal_connect (G_OBJECT (cell_button->button), "button-press-event",
417                     G_CALLBACK (psppire_cell_renderer_button_press_event),
418                     cell);
419
420   gtk_widget_show (cell_button->button);
421
422   if (event != NULL && event->any.type == GDK_BUTTON_RELEASE)
423     {
424       guint idle_id;
425
426       cell_button->click_time = event->button.time;
427       cell_button->click_x = event->button.x_root;
428       cell_button->click_y = event->button.y_root;
429       idle_id = g_idle_add (psppire_cell_renderer_button_initial_click,
430                             cell_button->button);
431       g_object_set_data (G_OBJECT (cell_button->button), IDLE_ID_STRING,
432                          GINT_TO_POINTER (idle_id));
433       g_signal_connect (G_OBJECT (cell_button->button), "destroy",
434                         G_CALLBACK (psppire_cell_renderer_button_on_destroy),
435                         NULL);
436     }
437   else
438     {
439       cell_button->click_time = 0;
440       cell_button->click_x = 0;
441       cell_button->click_y = 0;
442     }
443
444   return GTK_CELL_EDITABLE (cell_button->button);
445 }
446
447 static void
448 psppire_cell_renderer_button_class_init (PsppireCellRendererButtonClass *class)
449 {
450   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
451   GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
452
453   gobject_class->set_property = psppire_cell_renderer_button_set_property;
454   gobject_class->get_property = psppire_cell_renderer_button_get_property;
455   gobject_class->finalize = psppire_cell_renderer_button_finalize;
456   gobject_class->dispose = psppire_cell_renderer_button_dispose;
457
458   cell_class->get_size = psppire_cell_renderer_button_get_size;
459   cell_class->render = psppire_cell_renderer_button_render;
460   cell_class->start_editing = psppire_cell_renderer_button_start_editing;
461
462   g_signal_new ("clicked",
463                 G_TYPE_FROM_CLASS (gobject_class),
464                 G_SIGNAL_RUN_LAST,
465                 0,
466                 NULL, NULL,
467                 g_cclosure_marshal_VOID__STRING,
468                 G_TYPE_NONE,
469                 1, G_TYPE_STRING);
470
471   g_signal_new ("double-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_object_class_install_property (gobject_class,
481                                    PROP_EDITABLE,
482                                    g_param_spec_boolean ("editable",
483                                                          "Editable",
484                                                          "Whether the button may be clicked.",
485                                                          FALSE,
486                                                          G_PARAM_READWRITE));
487
488   g_object_class_install_property (gobject_class,
489                                    PROP_LABEL,
490                                    g_param_spec_string ("label",
491                                                         "Label",
492                                                         "Text to appear in button.",
493                                                         "",
494                                                         G_PARAM_READWRITE));
495
496   g_object_class_install_property (gobject_class,
497                                    PROP_SLASH,
498                                    g_param_spec_boolean ("slash",
499                                                          _("Diagonal slash"),
500                                                          _("Whether to draw a diagonal slash across the button."),
501                                                          FALSE,
502                                                          G_PARAM_READWRITE));
503
504 }
505
506 static void
507 psppire_cell_renderer_button_init (PsppireCellRendererButton *obj)
508 {
509   obj->editable = FALSE;
510   obj->label = g_strdup ("");
511   obj->border_width = 0;
512   obj->xpad = 0;
513   obj->ypad = 0;
514
515   obj->slash = FALSE;
516
517   obj->button = NULL;
518
519   obj->button_style = NULL;
520   obj->label_style = NULL;
521   obj->base = NULL;
522   obj->style_set_handler = 0;
523   obj->dispose_has_run = FALSE;
524 }
525
526 static void
527 psppire_cell_renderer_button_finalize (GObject *obj)
528 {
529   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
530
531   g_free (button->label);
532 }
533
534 static void
535 psppire_cell_renderer_button_dispose (GObject *obj)
536 {
537   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
538
539   if (button->dispose_has_run)
540     return;
541   
542   button->dispose_has_run = TRUE;
543
544   /* When called with NULL, as we are doing here, update_style_cache
545      does nothing more than to drop references */
546   update_style_cache (button, NULL);
547
548   G_OBJECT_CLASS (psppire_cell_renderer_button_parent_class)->dispose (obj);
549 }
550
551 GtkCellRenderer *
552 psppire_cell_renderer_button_new (void)
553 {
554   return GTK_CELL_RENDERER (g_object_new (PSPPIRE_TYPE_CELL_RENDERER_BUTTON, NULL));
555 }
556
557 void
558 psppire_cell_renderer_button_set_slash (PsppireCellRendererButton *button,
559                                         gboolean slash)
560 {
561   g_return_if_fail (button != NULL);
562   button->slash = slash;
563 }
564
565 gboolean
566 psppire_cell_renderer_button_get_slash (const PsppireCellRendererButton *button)
567 {
568   g_return_val_if_fail (button != NULL, FALSE);
569   return button->slash;
570 }