psppire-cell-renderer-button: New cell renderer for GtkButton.
[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   };
64
65 static void
66 psppire_cell_renderer_button_set_property (GObject      *object,
67                                            guint         prop_id,
68                                            const GValue *value,
69                                            GParamSpec   *pspec)
70 {
71   PsppireCellRendererButton *obj = PSPPIRE_CELL_RENDERER_BUTTON (object);
72
73   switch (prop_id)
74     {
75     case PROP_EDITABLE:
76       obj->editable = g_value_get_boolean (value);
77       if (obj->editable)
78         GTK_CELL_RENDERER (obj)->mode = GTK_CELL_RENDERER_MODE_EDITABLE;
79       else
80         GTK_CELL_RENDERER (obj)->mode = GTK_CELL_RENDERER_MODE_INERT;
81       break;
82
83     case PROP_LABEL:
84       g_free (obj->label);
85       obj->label = g_value_dup_string (value);
86       break;
87
88     default:
89       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
90       break;
91     }
92 }
93
94 static void
95 psppire_cell_renderer_button_get_property (GObject      *object,
96                                            guint         prop_id,
97                                            GValue       *value,
98                                            GParamSpec   *pspec)
99 {
100   PsppireCellRendererButton *obj = PSPPIRE_CELL_RENDERER_BUTTON (object);
101
102   switch (prop_id)
103     {
104     case PROP_EDITABLE:
105       g_value_set_boolean (value, obj->editable);
106       break;
107
108     case PROP_LABEL:
109       g_value_set_string (value, obj->label);
110       break;
111
112     default:
113       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
114       break;
115     }
116 }
117
118 static void
119 on_style_set (GtkWidget                 *base,
120               GtkStyle                  *previous_style,
121               PsppireCellRendererButton *button)
122 {
123   update_style_cache (button, NULL);
124 }
125
126 static void
127 on_destroy (GtkObject                 *base,
128             PsppireCellRendererButton *button)
129 {
130   update_style_cache (button, NULL);
131 }
132
133 static void
134 update_style_cache (PsppireCellRendererButton *button,
135                     GtkWidget                 *widget)
136 {
137   if (button->base == widget)
138     return;
139
140   /* Clear old cache. */
141   if (button->button_style)
142     {
143       g_object_unref (button->button_style);
144       button->button_style = NULL;
145     }
146   if (button->label_style)
147     {
148       g_object_unref (button->label_style);
149       button->label_style = NULL;
150     }
151   if (button->base != NULL)
152     {
153       if (button->style_set_handler)
154         {
155           g_signal_handler_disconnect (button->base,
156                                        button->style_set_handler);
157           button->style_set_handler = 0;
158         }
159       if (button->destroy_handler)
160         {
161           g_signal_handler_disconnect (button->base, button->destroy_handler);
162           button->destroy_handler = 0;
163         }
164       g_object_unref (button->base);
165       button->base = NULL;
166     }
167
168   /* Populate cache. */
169   if (widget)
170     {
171       button->button_style = facade_get_style (widget, GTK_TYPE_BUTTON, 0);
172       button->label_style = facade_get_style (widget, GTK_TYPE_BUTTON,
173                                               GTK_TYPE_LABEL, 0);
174       button->base = widget;
175       button->style_set_handler = g_signal_connect (widget, "style-set",
176                                                     G_CALLBACK (on_style_set),
177                                                     button);
178       button->destroy_handler = g_signal_connect (widget, "destroy",
179                                                   G_CALLBACK (on_destroy),
180                                                   button);
181
182       g_object_ref (widget);
183       g_object_ref (button->button_style);
184       g_object_ref (button->label_style);
185     }
186 }
187
188 static void
189 psppire_cell_renderer_button_render (GtkCellRenderer      *cell,
190                                      GdkDrawable          *window,
191                                      GtkWidget            *widget,
192                                      GdkRectangle         *background_area,
193                                      GdkRectangle         *cell_area,
194                                      GdkRectangle         *expose_area,
195                                      GtkCellRendererState  flags)
196 {
197   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
198   GtkStateType state_type;
199
200   if (!button->editable || !cell->sensitive)
201     state_type = GTK_STATE_INSENSITIVE;
202   else if (flags & GTK_CELL_RENDERER_SELECTED)
203     {
204       if (gtk_widget_has_focus (widget))
205         state_type = GTK_STATE_SELECTED;
206       else
207         state_type = GTK_STATE_ACTIVE;
208     }
209   else if (flags & GTK_CELL_RENDERER_PRELIT)
210     state_type = GTK_STATE_PRELIGHT;
211   else
212     {
213       if (gtk_widget_get_state (widget) == GTK_STATE_INSENSITIVE)
214         state_type = GTK_STATE_INSENSITIVE;
215       else
216         state_type = GTK_STATE_NORMAL;
217     }
218
219   update_style_cache (button, widget);
220   facade_button_render (widget, window, expose_area,
221                         cell_area, button->border_width, button->button_style,
222                         state_type,
223                         button->label_style, button->label, button->xpad,
224                         button->ypad, cell->xalign, cell->yalign);
225 }
226
227 static void
228 psppire_cell_renderer_button_get_size (GtkCellRenderer      *cell,
229                                        GtkWidget            *widget,
230                                        GdkRectangle         *cell_area,
231                                        gint                 *x_offset,
232                                        gint                 *y_offset,
233                                        gint                 *width,
234                                        gint                 *height)
235 {
236   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
237
238   update_style_cache (button, widget);
239   if (cell_area != NULL)
240     {
241       /* The caller is really asking for the placement of the focus rectangle.
242          The focus rectangle should surround the whole label area, so calculate
243          that area. */
244       GtkBorder inset;
245
246       facade_button_get_focus_inset (button->border_width, widget,
247                                      button->button_style, &inset);
248
249       if (x_offset)
250         *x_offset = inset.left;
251       if (y_offset)
252         *y_offset = inset.top;
253       if (width)
254         *width = MAX (1, cell_area->width - inset.left - inset.right);
255       if (height)
256         *height = MAX (1, cell_area->height - inset.top - inset.bottom);
257     }
258   else
259     {
260       /* The caller is asking for the preferred size of the cell. */
261       GtkRequisition label_req;
262       GtkRequisition request;
263
264       facade_label_get_size_request (button->xpad, button->ypad,
265                                      widget, button->label, &label_req);
266       facade_button_get_size_request (button->border_width, widget,
267                                       button->button_style, &label_req,
268                                       &request);
269
270       if (x_offset)
271         *x_offset = 0;
272       if (y_offset)
273         *y_offset = 0;
274       if (width)
275         *width = request.width;
276       if (height)
277         *height = request.height;
278     }
279 }
280
281 static void
282 psppire_cell_renderer_button_clicked (GtkButton *button,
283                                       gpointer   data)
284 {
285   PsppireCellRendererButton *cell_button = data;
286   gchar *path;
287
288   g_object_get (button, "path", &path, NULL);
289   g_signal_emit_by_name (cell_button, "clicked", path);
290   g_free (path);
291 }
292
293 static gboolean
294 psppire_cell_renderer_button_focus_out_event (GtkWidget *widget,
295                                               GdkEvent  *event,
296                                               gpointer   data)
297 {
298   PsppireCellRendererButton *cell_button = data;
299
300   g_signal_handlers_disconnect_by_func (widget,
301                                         psppire_cell_renderer_button_focus_out_event,
302                                         data);
303   g_signal_handlers_disconnect_by_func (widget,
304                                         psppire_cell_renderer_button_clicked,
305                                         data);
306
307   gtk_cell_renderer_stop_editing (GTK_CELL_RENDERER (cell_button), FALSE);
308
309   return FALSE;
310 }
311
312 #define IDLE_ID_STRING "psppire-cell-renderer-button-idle-id"
313
314 static gboolean
315 psppire_cell_renderer_button_initial_click (gpointer data)
316 {
317   GtkButton *button = data;
318
319   gtk_button_clicked (button);
320   g_object_steal_data (G_OBJECT (button), IDLE_ID_STRING);
321   return FALSE;
322 }
323
324 static void
325 psppire_cell_renderer_button_on_destroy (GObject *object, gpointer user_data)
326 {
327   guint idle_id;
328
329   idle_id = GPOINTER_TO_INT (g_object_steal_data (object, IDLE_ID_STRING));
330   if (idle_id != 0)
331     g_source_remove (idle_id);
332 }
333
334 static void
335 psppire_cell_renderer_button_double_click (GtkButton *button,
336                                            PsppireCellRendererButton *cell_button)
337 {
338   gchar *path;
339
340   if (g_object_get_data (G_OBJECT (button), IDLE_ID_STRING))
341     psppire_cell_renderer_button_initial_click (button);
342
343   g_object_get (button, "path", &path, NULL);
344   g_signal_emit_by_name (cell_button, "double-clicked", path);
345   g_free (path);
346 }
347
348 static gboolean
349 psppire_cell_renderer_button_press_event (GtkButton      *button,
350                                           GdkEventButton *event,
351                                           gpointer        data)
352 {
353   PsppireCellRendererButton *cell_button = data;
354
355   if (cell_button->click_time != 0)
356     {
357       GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (button));
358       GtkSettings *settings = gtk_settings_get_for_screen (screen);
359       gint double_click_distance;
360       gint double_click_time;
361
362       g_object_get (settings,
363                     "gtk-double-click-time", &double_click_time,
364                     "gtk-double-click-distance", &double_click_distance,
365                     NULL);
366
367       if (event->type == GDK_BUTTON_PRESS
368           && event->button == 1
369           && event->time <= cell_button->click_time + double_click_time
370           && ABS (event->x_root - cell_button->click_x) <= double_click_distance
371           && ABS (event->y_root - cell_button->click_y) <= double_click_distance)
372         {
373           psppire_cell_renderer_button_double_click (button, cell_button);
374           return TRUE;
375         }
376
377       cell_button->click_time = 0;
378     }
379
380   if (event->type == GDK_2BUTTON_PRESS)
381     {
382       psppire_cell_renderer_button_double_click (button, cell_button);
383       return TRUE;
384     }
385
386   return FALSE;
387 }
388
389 static GtkCellEditable *
390 psppire_cell_renderer_button_start_editing (GtkCellRenderer      *cell,
391                                             GdkEvent             *event,
392                                             GtkWidget            *widget,
393                                             const gchar          *path,
394                                             GdkRectangle         *background_area,
395                                             GdkRectangle         *cell_area,
396                                             GtkCellRendererState  flags)
397 {
398   PsppireCellRendererButton *cell_button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
399   gfloat xalign, yalign;
400
401   gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
402   cell_button->button = g_object_new (PSPPIRE_TYPE_BUTTON_EDITABLE,
403                                       "label", cell_button->label,
404                                       "xalign", xalign,
405                                       "yalign", yalign,
406                                       "path", path,
407                                       NULL);
408
409   g_signal_connect (G_OBJECT (cell_button->button), "focus-out-event",
410                     G_CALLBACK (psppire_cell_renderer_button_focus_out_event),
411                     cell);
412   g_signal_connect (G_OBJECT (cell_button->button), "clicked",
413                     G_CALLBACK (psppire_cell_renderer_button_clicked),
414                     cell);
415   g_signal_connect (G_OBJECT (cell_button->button), "button-press-event",
416                     G_CALLBACK (psppire_cell_renderer_button_press_event),
417                     cell);
418
419   gtk_widget_show (cell_button->button);
420
421   if (event != NULL && event->any.type == GDK_BUTTON_RELEASE)
422     {
423       guint idle_id;
424
425       cell_button->click_time = event->button.time;
426       cell_button->click_x = event->button.x_root;
427       cell_button->click_y = event->button.y_root;
428       idle_id = g_idle_add (psppire_cell_renderer_button_initial_click,
429                             cell_button->button);
430       g_object_set_data (G_OBJECT (cell_button->button), IDLE_ID_STRING,
431                          GINT_TO_POINTER (idle_id));
432       g_signal_connect (G_OBJECT (cell_button->button), "destroy",
433                         G_CALLBACK (psppire_cell_renderer_button_on_destroy),
434                         NULL);
435     }
436   else
437     {
438       cell_button->click_time = 0;
439       cell_button->click_x = 0;
440       cell_button->click_y = 0;
441     }
442
443   return GTK_CELL_EDITABLE (cell_button->button);
444 }
445
446 static void
447 psppire_cell_renderer_button_class_init (PsppireCellRendererButtonClass *class)
448 {
449   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
450   GtkObjectClass *gtk_object_class = GTK_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
457   gtk_object_class->destroy = psppire_cell_renderer_button_destroy;
458
459   cell_class->get_size = psppire_cell_renderer_button_get_size;
460   cell_class->render = psppire_cell_renderer_button_render;
461   cell_class->start_editing = psppire_cell_renderer_button_start_editing;
462
463   g_signal_new ("clicked",
464                 G_TYPE_FROM_CLASS (gobject_class),
465                 G_SIGNAL_RUN_LAST,
466                 0,
467                 NULL, NULL,
468                 g_cclosure_marshal_VOID__STRING,
469                 G_TYPE_NONE,
470                 1, G_TYPE_STRING);
471
472   g_signal_new ("double-clicked",
473                 G_TYPE_FROM_CLASS (gobject_class),
474                 G_SIGNAL_RUN_LAST,
475                 0,
476                 NULL, NULL,
477                 g_cclosure_marshal_VOID__STRING,
478                 G_TYPE_NONE,
479                 1, G_TYPE_STRING);
480
481   g_object_class_install_property (gobject_class,
482                                    PROP_EDITABLE,
483                                    g_param_spec_boolean ("editable",
484                                                          "Editable",
485                                                          "Whether the button may be clicked.",
486                                                          FALSE,
487                                                          G_PARAM_READWRITE));
488
489   g_object_class_install_property (gobject_class,
490                                    PROP_LABEL,
491                                    g_param_spec_string ("label",
492                                                         "Label",
493                                                         "Text to appear in button.",
494                                                         "",
495                                                         G_PARAM_READWRITE));
496 }
497
498 static void
499 psppire_cell_renderer_button_init (PsppireCellRendererButton *obj)
500 {
501   obj->editable = FALSE;
502   obj->label = g_strdup ("");
503   obj->border_width = 0;
504   obj->xpad = 0;
505   obj->ypad = 0;
506
507   obj->button = NULL;
508
509   obj->button_style = NULL;
510   obj->label_style = NULL;
511   obj->base = NULL;
512   obj->style_set_handler = 0;
513   obj->destroy_handler = 0;
514 }
515
516 static void
517 psppire_cell_renderer_button_finalize (GObject *obj)
518 {
519   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
520
521   g_free (button->label);
522 }
523
524 static void
525 psppire_cell_renderer_button_destroy (GtkObject *obj)
526 {
527   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
528
529   update_style_cache (button, NULL);
530
531   GTK_OBJECT_CLASS (psppire_cell_renderer_button_parent_class)->destroy (obj);
532 }
533
534 GtkCellRenderer *
535 psppire_cell_renderer_button_new (void)
536 {
537   return GTK_CELL_RENDERER (g_object_new (PSPPIRE_TYPE_CELL_RENDERER_BUTTON, NULL));
538 }