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