psppire-cell-renderer-button: Add "slash" property.
[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_destroy (GtkObject *);
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 on_destroy (GtkObject                 *base,
139             PsppireCellRendererButton *button)
140 {
141   update_style_cache (button, NULL);
142 }
143
144 static void
145 update_style_cache (PsppireCellRendererButton *button,
146                     GtkWidget                 *widget)
147 {
148   if (button->base == widget)
149     return;
150
151   /* Clear old cache. */
152   if (button->button_style)
153     {
154       g_object_unref (button->button_style);
155       button->button_style = NULL;
156     }
157   if (button->label_style)
158     {
159       g_object_unref (button->label_style);
160       button->label_style = NULL;
161     }
162   if (button->base != NULL)
163     {
164       if (button->style_set_handler)
165         {
166           g_signal_handler_disconnect (button->base,
167                                        button->style_set_handler);
168           button->style_set_handler = 0;
169         }
170       if (button->destroy_handler)
171         {
172           g_signal_handler_disconnect (button->base, button->destroy_handler);
173           button->destroy_handler = 0;
174         }
175       g_object_unref (button->base);
176       button->base = NULL;
177     }
178
179   /* Populate cache. */
180   if (widget)
181     {
182       button->button_style = facade_get_style (widget, GTK_TYPE_BUTTON, 0);
183       button->label_style = facade_get_style (widget, GTK_TYPE_BUTTON,
184                                               GTK_TYPE_LABEL, 0);
185       button->base = widget;
186       button->style_set_handler = g_signal_connect (widget, "style-set",
187                                                     G_CALLBACK (on_style_set),
188                                                     button);
189       button->destroy_handler = g_signal_connect (widget, "destroy",
190                                                   G_CALLBACK (on_destroy),
191                                                   button);
192
193       g_object_ref (widget);
194       g_object_ref (button->button_style);
195       g_object_ref (button->label_style);
196     }
197 }
198
199 static void
200 psppire_cell_renderer_button_render (GtkCellRenderer      *cell,
201                                      GdkDrawable          *window,
202                                      GtkWidget            *widget,
203                                      GdkRectangle         *background_area,
204                                      GdkRectangle         *cell_area,
205                                      GdkRectangle         *expose_area,
206                                      GtkCellRendererState  flags)
207 {
208   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
209   GtkStateType state_type;
210
211   if (!button->editable || !cell->sensitive)
212     state_type = GTK_STATE_INSENSITIVE;
213   else if (flags & GTK_CELL_RENDERER_SELECTED)
214     {
215       if (gtk_widget_has_focus (widget))
216         state_type = GTK_STATE_SELECTED;
217       else
218         state_type = GTK_STATE_ACTIVE;
219     }
220   else if (flags & GTK_CELL_RENDERER_PRELIT)
221     state_type = GTK_STATE_PRELIGHT;
222   else
223     {
224       if (gtk_widget_get_state (widget) == GTK_STATE_INSENSITIVE)
225         state_type = GTK_STATE_INSENSITIVE;
226       else
227         state_type = GTK_STATE_NORMAL;
228     }
229
230   update_style_cache (button, widget);
231   facade_button_render (widget, window, expose_area,
232                         cell_area, button->border_width, button->button_style,
233                         state_type,
234                         button->label_style, button->label, button->xpad,
235                         button->ypad, cell->xalign, cell->yalign);
236
237   if (button->slash)
238     gdk_draw_line (window, button->button_style->black_gc,
239                    cell_area->x,
240                    cell_area->y + cell_area->height,
241                    cell_area->x + cell_area->width,
242                    cell_area->y);
243 }
244
245 static void
246 psppire_cell_renderer_button_get_size (GtkCellRenderer      *cell,
247                                        GtkWidget            *widget,
248                                        GdkRectangle         *cell_area,
249                                        gint                 *x_offset,
250                                        gint                 *y_offset,
251                                        gint                 *width,
252                                        gint                 *height)
253 {
254   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
255
256   update_style_cache (button, widget);
257   if (cell_area != NULL)
258     {
259       /* The caller is really asking for the placement of the focus rectangle.
260          The focus rectangle should surround the whole label area, so calculate
261          that area. */
262       GtkBorder inset;
263
264       facade_button_get_focus_inset (button->border_width, widget,
265                                      button->button_style, &inset);
266
267       if (x_offset)
268         *x_offset = inset.left;
269       if (y_offset)
270         *y_offset = inset.top;
271       if (width)
272         *width = MAX (1, cell_area->width - inset.left - inset.right);
273       if (height)
274         *height = MAX (1, cell_area->height - inset.top - inset.bottom);
275     }
276   else
277     {
278       /* The caller is asking for the preferred size of the cell. */
279       GtkRequisition label_req;
280       GtkRequisition request;
281
282       facade_label_get_size_request (button->xpad, button->ypad,
283                                      widget, button->label, &label_req);
284       facade_button_get_size_request (button->border_width, widget,
285                                       button->button_style, &label_req,
286                                       &request);
287
288       if (x_offset)
289         *x_offset = 0;
290       if (y_offset)
291         *y_offset = 0;
292       if (width)
293         *width = request.width;
294       if (height)
295         *height = request.height;
296     }
297 }
298
299 static void
300 psppire_cell_renderer_button_clicked (GtkButton *button,
301                                       gpointer   data)
302 {
303   PsppireCellRendererButton *cell_button = data;
304   gchar *path;
305
306   g_object_get (button, "path", &path, NULL);
307   g_signal_emit_by_name (cell_button, "clicked", path);
308   g_free (path);
309 }
310
311 static gboolean
312 psppire_cell_renderer_button_focus_out_event (GtkWidget *widget,
313                                               GdkEvent  *event,
314                                               gpointer   data)
315 {
316   PsppireCellRendererButton *cell_button = data;
317
318   g_signal_handlers_disconnect_by_func (widget,
319                                         psppire_cell_renderer_button_focus_out_event,
320                                         data);
321   g_signal_handlers_disconnect_by_func (widget,
322                                         psppire_cell_renderer_button_clicked,
323                                         data);
324
325   gtk_cell_renderer_stop_editing (GTK_CELL_RENDERER (cell_button), FALSE);
326
327   return FALSE;
328 }
329
330 #define IDLE_ID_STRING "psppire-cell-renderer-button-idle-id"
331
332 static gboolean
333 psppire_cell_renderer_button_initial_click (gpointer data)
334 {
335   GtkButton *button = data;
336
337   gtk_button_clicked (button);
338   g_object_steal_data (G_OBJECT (button), IDLE_ID_STRING);
339   return FALSE;
340 }
341
342 static void
343 psppire_cell_renderer_button_on_destroy (GObject *object, gpointer user_data)
344 {
345   guint idle_id;
346
347   idle_id = GPOINTER_TO_INT (g_object_steal_data (object, IDLE_ID_STRING));
348   if (idle_id != 0)
349     g_source_remove (idle_id);
350 }
351
352 static void
353 psppire_cell_renderer_button_double_click (GtkButton *button,
354                                            PsppireCellRendererButton *cell_button)
355 {
356   gchar *path;
357
358   if (g_object_get_data (G_OBJECT (button), IDLE_ID_STRING))
359     psppire_cell_renderer_button_initial_click (button);
360
361   g_object_get (button, "path", &path, NULL);
362   g_signal_emit_by_name (cell_button, "double-clicked", path);
363   g_free (path);
364 }
365
366 static gboolean
367 psppire_cell_renderer_button_press_event (GtkButton      *button,
368                                           GdkEventButton *event,
369                                           gpointer        data)
370 {
371   PsppireCellRendererButton *cell_button = data;
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                                       "slash", cell_button->slash,
426                                       NULL);
427
428   g_signal_connect (G_OBJECT (cell_button->button), "focus-out-event",
429                     G_CALLBACK (psppire_cell_renderer_button_focus_out_event),
430                     cell);
431   g_signal_connect (G_OBJECT (cell_button->button), "clicked",
432                     G_CALLBACK (psppire_cell_renderer_button_clicked),
433                     cell);
434   g_signal_connect (G_OBJECT (cell_button->button), "button-press-event",
435                     G_CALLBACK (psppire_cell_renderer_button_press_event),
436                     cell);
437
438   gtk_widget_show (cell_button->button);
439
440   if (event != NULL && event->any.type == GDK_BUTTON_RELEASE)
441     {
442       guint idle_id;
443
444       cell_button->click_time = event->button.time;
445       cell_button->click_x = event->button.x_root;
446       cell_button->click_y = event->button.y_root;
447       idle_id = g_idle_add (psppire_cell_renderer_button_initial_click,
448                             cell_button->button);
449       g_object_set_data (G_OBJECT (cell_button->button), IDLE_ID_STRING,
450                          GINT_TO_POINTER (idle_id));
451       g_signal_connect (G_OBJECT (cell_button->button), "destroy",
452                         G_CALLBACK (psppire_cell_renderer_button_on_destroy),
453                         NULL);
454     }
455   else
456     {
457       cell_button->click_time = 0;
458       cell_button->click_x = 0;
459       cell_button->click_y = 0;
460     }
461
462   return GTK_CELL_EDITABLE (cell_button->button);
463 }
464
465 static void
466 psppire_cell_renderer_button_class_init (PsppireCellRendererButtonClass *class)
467 {
468   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
469   GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS (class);
470   GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
471
472   gobject_class->set_property = psppire_cell_renderer_button_set_property;
473   gobject_class->get_property = psppire_cell_renderer_button_get_property;
474   gobject_class->finalize = psppire_cell_renderer_button_finalize;
475
476   gtk_object_class->destroy = psppire_cell_renderer_button_destroy;
477
478   cell_class->get_size = psppire_cell_renderer_button_get_size;
479   cell_class->render = psppire_cell_renderer_button_render;
480   cell_class->start_editing = psppire_cell_renderer_button_start_editing;
481
482   g_signal_new ("clicked",
483                 G_TYPE_FROM_CLASS (gobject_class),
484                 G_SIGNAL_RUN_LAST,
485                 0,
486                 NULL, NULL,
487                 g_cclosure_marshal_VOID__STRING,
488                 G_TYPE_NONE,
489                 1, G_TYPE_STRING);
490
491   g_signal_new ("double-clicked",
492                 G_TYPE_FROM_CLASS (gobject_class),
493                 G_SIGNAL_RUN_LAST,
494                 0,
495                 NULL, NULL,
496                 g_cclosure_marshal_VOID__STRING,
497                 G_TYPE_NONE,
498                 1, G_TYPE_STRING);
499
500   g_object_class_install_property (gobject_class,
501                                    PROP_EDITABLE,
502                                    g_param_spec_boolean ("editable",
503                                                          "Editable",
504                                                          "Whether the button may be clicked.",
505                                                          FALSE,
506                                                          G_PARAM_READWRITE));
507
508   g_object_class_install_property (gobject_class,
509                                    PROP_LABEL,
510                                    g_param_spec_string ("label",
511                                                         "Label",
512                                                         "Text to appear in button.",
513                                                         "",
514                                                         G_PARAM_READWRITE));
515
516   g_object_class_install_property (gobject_class,
517                                    PROP_SLASH,
518                                    g_param_spec_boolean ("slash",
519                                                          _("Diagonal slash"),
520                                                          _("Whether to draw a diagonal slash across the button."),
521                                                          FALSE,
522                                                          G_PARAM_READWRITE));
523
524 }
525
526 static void
527 psppire_cell_renderer_button_init (PsppireCellRendererButton *obj)
528 {
529   obj->editable = FALSE;
530   obj->label = g_strdup ("");
531   obj->border_width = 0;
532   obj->xpad = 0;
533   obj->ypad = 0;
534
535   obj->slash = FALSE;
536
537   obj->button = NULL;
538
539   obj->button_style = NULL;
540   obj->label_style = NULL;
541   obj->base = NULL;
542   obj->style_set_handler = 0;
543   obj->destroy_handler = 0;
544 }
545
546 static void
547 psppire_cell_renderer_button_finalize (GObject *obj)
548 {
549   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
550
551   g_free (button->label);
552 }
553
554 static void
555 psppire_cell_renderer_button_destroy (GtkObject *obj)
556 {
557   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
558
559   update_style_cache (button, NULL);
560
561   GTK_OBJECT_CLASS (psppire_cell_renderer_button_parent_class)->destroy (obj);
562 }
563
564 GtkCellRenderer *
565 psppire_cell_renderer_button_new (void)
566 {
567   return GTK_CELL_RENDERER (g_object_new (PSPPIRE_TYPE_CELL_RENDERER_BUTTON, NULL));
568 }
569
570 void
571 psppire_cell_renderer_button_set_slash (PsppireCellRendererButton *button,
572                                         gboolean slash)
573 {
574   g_return_if_fail (button != NULL);
575   button->slash = slash;
576 }
577
578 gboolean
579 psppire_cell_renderer_button_get_slash (const PsppireCellRendererButton *button)
580 {
581   g_return_val_if_fail (button != NULL, FALSE);
582   return button->slash;
583 }