Remove some g_param_spec_ strings from the translation domain
[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 #define P_(msgid) (msgid)
31
32 static void psppire_cell_renderer_button_dispose (GObject *);
33 static void psppire_cell_renderer_button_finalize (GObject *);
34
35 static void update_style_cache (PsppireCellRendererButton *button,
36                                 GtkWidget                 *widget);
37
38 static void psppire_cell_renderer_button_load_gtkrc (void);
39
40
41 G_DEFINE_TYPE_EXTENDED (PsppireCellRendererButton,
42                         psppire_cell_renderer_button,
43                         GTK_TYPE_CELL_RENDERER,
44                         0,
45                         psppire_cell_renderer_button_load_gtkrc ());
46
47 static void
48 psppire_cell_renderer_button_load_gtkrc (void)
49 {
50   const char *gtkrc_file;
51
52   gtkrc_file = relocate (PKGDATADIR "/psppire.gtkrc");
53   gtk_rc_add_default_file (gtkrc_file);
54   gtk_rc_parse (gtkrc_file);
55 }
56
57 enum
58   {
59     PROP_0,
60     PROP_EDITABLE,
61     PROP_LABEL,
62     PROP_SLASH
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   switch (prop_id)
73     {
74     case PROP_EDITABLE:
75       obj->editable = g_value_get_boolean (value);
76       if (obj->editable)
77         g_object_set (obj, "mode", GTK_CELL_RENDERER_MODE_EDITABLE, NULL);
78       else
79         g_object_set (obj, "mode", GTK_CELL_RENDERER_MODE_INERT, NULL);
80       break;
81
82     case PROP_LABEL:
83       g_free (obj->label);
84       obj->label = g_value_dup_string (value);
85       break;
86
87     case PROP_SLASH:
88       psppire_cell_renderer_button_set_slash (obj,
89                                               g_value_get_boolean (value));
90       break;
91
92     default:
93       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
94       break;
95     }
96 }
97
98 static void
99 psppire_cell_renderer_button_get_property (GObject      *object,
100                                            guint         prop_id,
101                                            GValue       *value,
102                                            GParamSpec   *pspec)
103 {
104   PsppireCellRendererButton *obj = PSPPIRE_CELL_RENDERER_BUTTON (object);
105
106   switch (prop_id)
107     {
108     case PROP_EDITABLE:
109       g_value_set_boolean (value, obj->editable);
110       break;
111
112     case PROP_LABEL:
113       g_value_set_string (value, obj->label);
114       break;
115
116     case PROP_SLASH:
117       g_value_set_boolean (value,
118                            psppire_cell_renderer_button_get_slash (obj));
119       break;
120
121     default:
122       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
123       break;
124     }
125 }
126
127 static void
128 on_style_set (GtkWidget                 *base,
129               GtkStyle                  *previous_style,
130               PsppireCellRendererButton *button)
131 {
132   update_style_cache (button, NULL);
133 }
134
135 static void
136 update_style_cache (PsppireCellRendererButton *button,
137                     GtkWidget                 *widget)
138 {
139   if (button->base == widget)
140     return;
141
142   /* Clear old cache. */
143   if (button->button_style)
144     {
145       g_object_unref (button->button_style);
146       button->button_style = NULL;
147     }
148   if (button->label_style)
149     {
150       g_object_unref (button->label_style);
151       button->label_style = NULL;
152     }
153   if (button->base != NULL)
154     {
155       if (button->style_set_handler)
156         {
157           g_signal_handler_disconnect (button->base,
158                                        button->style_set_handler);
159           button->style_set_handler = 0;
160         }
161       g_object_unref (button->base);
162       button->base = NULL;
163     }
164
165   /* Populate cache. */
166   if (widget)
167     {
168       button->button_style = facade_get_style (widget, GTK_TYPE_BUTTON, 0);
169       button->label_style = facade_get_style (widget, GTK_TYPE_BUTTON,
170                                               GTK_TYPE_LABEL, 0);
171       button->base = widget;
172       button->style_set_handler = g_signal_connect (widget, "style-set",
173                                                     G_CALLBACK (on_style_set),
174                                                     button);
175       g_object_ref (widget);
176       g_object_ref (button->button_style);
177       g_object_ref (button->label_style);
178     }
179 }
180
181 static void
182 psppire_cell_renderer_button_render (GtkCellRenderer      *cell,
183                                      cairo_t              *cr,
184                                      GtkWidget            *widget,
185                                      const GdkRectangle         *background_area,
186                                      const GdkRectangle         *cell_area,
187                                      GtkCellRendererState  flags)
188 {
189   GtkStateType state_type;
190   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
191   gfloat xalign, yalign;
192   
193   if (!button->editable || ! gtk_cell_renderer_get_sensitive (cell))
194     state_type = GTK_STATE_INSENSITIVE;
195   else if (flags & GTK_CELL_RENDERER_SELECTED)
196     {
197       if (gtk_widget_has_focus (widget))
198         state_type = GTK_STATE_SELECTED;
199       else
200         state_type = GTK_STATE_ACTIVE;
201     }
202   else if (flags & GTK_CELL_RENDERER_PRELIT)
203     state_type = GTK_STATE_PRELIGHT;
204   else
205     {
206       if (gtk_widget_get_state_flags (widget) == GTK_STATE_FLAG_INSENSITIVE)
207         state_type = GTK_STATE_INSENSITIVE;
208       else
209         state_type = GTK_STATE_NORMAL;
210     }
211
212   gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
213
214
215   update_style_cache (button, widget);
216
217   facade_button_render (widget, cr,
218                         cell_area, button->border_width, button->button_style,
219                         state_type,
220                         button->label_style, button->label, button->xpad,
221                         button->ypad, xalign, yalign);
222
223   if (button->slash)
224     {
225       cairo_set_line_width (cr, 1.0);
226       cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
227       cairo_move_to (cr, 
228                      cell_area->x,
229                      cell_area->y + cell_area->height);
230
231       cairo_line_to (cr,
232                      cell_area->x + cell_area->width,
233                      cell_area->y);
234       cairo_stroke (cr);
235     }
236 }
237
238 static void
239 psppire_cell_renderer_button_get_size (GtkCellRenderer      *cell,
240                                        GtkWidget            *widget,
241                                        const GdkRectangle   *cell_area,
242                                        gint                 *x_offset,
243                                        gint                 *y_offset,
244                                        gint                 *width,
245                                        gint                 *height)
246 {
247   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
248
249   update_style_cache (button, widget);
250   if (cell_area != NULL)
251     {
252       /* The caller is really asking for the placement of the focus rectangle.
253          The focus rectangle should surround the whole label area, so calculate
254          that area. */
255       GtkBorder inset;
256
257       facade_button_get_focus_inset (button->border_width, widget,
258                                      button->button_style, &inset);
259
260       if (x_offset)
261         *x_offset = inset.left;
262       if (y_offset)
263         *y_offset = inset.top;
264       if (width)
265         *width = MAX (1, cell_area->width - inset.left - inset.right);
266       if (height)
267         *height = MAX (1, cell_area->height - inset.top - inset.bottom);
268     }
269   else
270     {
271       /* The caller is asking for the preferred size of the cell. */
272       GtkRequisition label_req;
273       GtkRequisition request;
274
275       facade_label_get_size_request (button->xpad, button->ypad,
276                                      widget, button->label, &label_req);
277       facade_button_get_size_request (button->border_width, widget,
278                                       button->button_style, &label_req,
279                                       &request);
280
281       if (x_offset)
282         *x_offset = 0;
283       if (y_offset)
284         *y_offset = 0;
285       if (width)
286         *width = request.width;
287       if (height)
288         *height = request.height;
289     }
290 }
291
292 static void
293 psppire_cell_renderer_button_clicked (GtkButton *button,
294                                       gpointer   data)
295 {
296   PsppireCellRendererButton *cell_button = data;
297   gchar *path;
298
299   g_object_get (button, "path", &path, NULL);
300   g_signal_emit_by_name (cell_button, "clicked", path);
301   g_free (path);
302 }
303
304 #define IDLE_ID_STRING "psppire-cell-renderer-button-idle-id"
305
306 static gboolean
307 psppire_cell_renderer_button_initial_click (gpointer data)
308 {
309   GtkButton *button = data;
310
311   g_object_steal_data (G_OBJECT (button), IDLE_ID_STRING);
312   gtk_button_clicked (button);
313   return FALSE;
314 }
315
316 static void
317 psppire_cell_renderer_button_on_destroy (GObject *object, gpointer user_data)
318 {
319   guint idle_id;
320
321   idle_id = GPOINTER_TO_INT (g_object_steal_data (object, IDLE_ID_STRING));
322   if (idle_id != 0)
323     g_source_remove (idle_id);
324 }
325
326 static void
327 psppire_cell_renderer_button_double_click (GtkButton *button,
328                                            PsppireCellRendererButton *cell_button)
329 {
330   gchar *path;
331
332   if (g_object_get_data (G_OBJECT (button), IDLE_ID_STRING))
333     psppire_cell_renderer_button_initial_click (button);
334
335   g_object_get (button, "path", &path, NULL);
336   g_signal_emit_by_name (cell_button, "double-clicked", path);
337   g_free (path);
338 }
339
340 static gboolean
341 psppire_cell_renderer_button_press_event (GtkButton      *button,
342                                           GdkEventButton *event,
343                                           gpointer        data)
344 {
345   PsppireCellRendererButton *cell_button = data;
346
347   if (event->button == 3)
348     {
349       /* Allow right-click events to propagate upward in the widget hierarchy.
350          Otherwise right-click menus, that trigger on a button-press-event on
351          the containing PsppSheetView, will pop up if the button is rendered as
352          a facade but not if the button widget exists.
353
354          We have to translate the event's data by hand to be relative to the
355          parent window, because the normal GObject signal propagation mechanism
356          won't do it for us.  (This might be a hint that we're doing this
357          wrong.) */
358       gint x, y;
359
360       gdk_window_get_position (event->window, &x, &y);
361       event->x += x;
362       event->y += y;
363       g_signal_stop_emission_by_name (button, "button-press-event");
364       return FALSE;
365     }
366
367   if (cell_button->click_time != 0)
368     {
369       GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (button));
370       GtkSettings *settings = gtk_settings_get_for_screen (screen);
371       gint double_click_distance;
372       gint double_click_time;
373
374       g_object_get (settings,
375                     "gtk-double-click-time", &double_click_time,
376                     "gtk-double-click-distance", &double_click_distance,
377                     NULL);
378
379       if (event->type == GDK_BUTTON_PRESS
380           && event->button == 1
381           && event->time <= cell_button->click_time + double_click_time
382           && ABS (event->x_root - cell_button->click_x) <= double_click_distance
383           && ABS (event->y_root - cell_button->click_y) <= double_click_distance)
384         {
385           psppire_cell_renderer_button_double_click (button, cell_button);
386           return TRUE;
387         }
388
389       cell_button->click_time = 0;
390     }
391
392   if (event->type == GDK_2BUTTON_PRESS)
393     {
394       psppire_cell_renderer_button_double_click (button, cell_button);
395       return TRUE;
396     }
397
398   return FALSE;
399 }
400
401 static GtkCellEditable *
402 psppire_cell_renderer_button_start_editing (GtkCellRenderer      *cell,
403                                             GdkEvent             *event,
404                                             GtkWidget            *widget,
405                                             const gchar          *path,
406                                             const GdkRectangle   *background_area,
407                                             const GdkRectangle   *cell_area,
408                                             GtkCellRendererState  flags)
409 {
410   PsppireCellRendererButton *cell_button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
411   gfloat xalign, yalign;
412
413   gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
414   cell_button->button = g_object_new (PSPPIRE_TYPE_BUTTON_EDITABLE,
415                                       "label", cell_button->label,
416                                       "xalign", xalign,
417                                       "yalign", yalign,
418                                       "path", path,
419                                       NULL);
420
421   g_signal_connect (G_OBJECT (cell_button->button), "clicked",
422                     G_CALLBACK (psppire_cell_renderer_button_clicked),
423                     cell);
424   g_signal_connect (G_OBJECT (cell_button->button), "button-press-event",
425                     G_CALLBACK (psppire_cell_renderer_button_press_event),
426                     cell);
427
428   gtk_widget_show (cell_button->button);
429
430   if (event != NULL && event->any.type == GDK_BUTTON_RELEASE)
431     {
432       guint idle_id;
433
434       cell_button->click_time = event->button.time;
435       cell_button->click_x = event->button.x_root;
436       cell_button->click_y = event->button.y_root;
437       idle_id = g_idle_add (psppire_cell_renderer_button_initial_click,
438                             cell_button->button);
439       g_object_set_data (G_OBJECT (cell_button->button), IDLE_ID_STRING,
440                          GINT_TO_POINTER (idle_id));
441       g_signal_connect (G_OBJECT (cell_button->button), "destroy",
442                         G_CALLBACK (psppire_cell_renderer_button_on_destroy),
443                         NULL);
444     }
445   else
446     {
447       cell_button->click_time = 0;
448       cell_button->click_x = 0;
449       cell_button->click_y = 0;
450     }
451
452   return GTK_CELL_EDITABLE (cell_button->button);
453 }
454
455 static void
456 psppire_cell_renderer_button_class_init (PsppireCellRendererButtonClass *class)
457 {
458   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
459   GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
460
461   gobject_class->set_property = psppire_cell_renderer_button_set_property;
462   gobject_class->get_property = psppire_cell_renderer_button_get_property;
463   gobject_class->finalize = psppire_cell_renderer_button_finalize;
464   gobject_class->dispose = psppire_cell_renderer_button_dispose;
465
466   cell_class->get_size = psppire_cell_renderer_button_get_size;
467   cell_class->render = psppire_cell_renderer_button_render;
468   cell_class->start_editing = psppire_cell_renderer_button_start_editing;
469
470   g_signal_new ("clicked",
471                 G_TYPE_FROM_CLASS (gobject_class),
472                 G_SIGNAL_RUN_LAST,
473                 0,
474                 NULL, NULL,
475                 g_cclosure_marshal_VOID__STRING,
476                 G_TYPE_NONE,
477                 1, G_TYPE_STRING);
478
479   g_signal_new ("double-clicked",
480                 G_TYPE_FROM_CLASS (gobject_class),
481                 G_SIGNAL_RUN_LAST,
482                 0,
483                 NULL, NULL,
484                 g_cclosure_marshal_VOID__STRING,
485                 G_TYPE_NONE,
486                 1, G_TYPE_STRING);
487
488   g_object_class_install_property (gobject_class,
489                                    PROP_EDITABLE,
490                                    g_param_spec_boolean ("editable",
491                                                          P_("Editable"),
492                                                          P_("Whether the button may be clicked."),
493                                                          FALSE,
494                                                          G_PARAM_READWRITE));
495
496   g_object_class_install_property (gobject_class,
497                                    PROP_LABEL,
498                                    g_param_spec_string ("label",
499                                                         P_("Label"),
500                                                         P_("Text to appear in button."),
501                                                         "",
502                                                         G_PARAM_READWRITE));
503
504   g_object_class_install_property (gobject_class,
505                                    PROP_SLASH,
506                                    g_param_spec_boolean ("slash",
507                                                          P_("Diagonal slash"),
508                                                          P_("Whether to draw a diagonal slash across the button."),
509                                                          FALSE,
510                                                          G_PARAM_READWRITE));
511
512 }
513
514 static void
515 psppire_cell_renderer_button_init (PsppireCellRendererButton *obj)
516 {
517   obj->editable = FALSE;
518   obj->label = g_strdup ("");
519   obj->border_width = 0;
520   obj->xpad = 0;
521   obj->ypad = 0;
522
523   obj->slash = FALSE;
524
525   obj->button = NULL;
526
527   obj->button_style = NULL;
528   obj->label_style = NULL;
529   obj->base = NULL;
530   obj->style_set_handler = 0;
531   obj->dispose_has_run = FALSE;
532 }
533
534 static void
535 psppire_cell_renderer_button_finalize (GObject *obj)
536 {
537   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
538
539   g_free (button->label);
540 }
541
542 static void
543 psppire_cell_renderer_button_dispose (GObject *obj)
544 {
545   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
546
547   if (button->dispose_has_run)
548     return;
549   
550   button->dispose_has_run = TRUE;
551
552   /* When called with NULL, as we are doing here, update_style_cache
553      does nothing more than to drop references */
554   update_style_cache (button, NULL);
555
556   G_OBJECT_CLASS (psppire_cell_renderer_button_parent_class)->dispose (obj);
557 }
558
559 GtkCellRenderer *
560 psppire_cell_renderer_button_new (void)
561 {
562   return GTK_CELL_RENDERER (g_object_new (PSPPIRE_TYPE_CELL_RENDERER_BUTTON, NULL));
563 }
564
565 void
566 psppire_cell_renderer_button_set_slash (PsppireCellRendererButton *button,
567                                         gboolean slash)
568 {
569   g_return_if_fail (button != NULL);
570   button->slash = slash;
571 }
572
573 gboolean
574 psppire_cell_renderer_button_get_slash (const PsppireCellRendererButton *button)
575 {
576   g_return_val_if_fail (button != NULL, FALSE);
577   return button->slash;
578 }