gui: Redo var sheet, data sheet, text import with PsppSheetView.
[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 (event->button == 3)
374     {
375       /* Allow right-click events to propagate upward in the widget hierarchy.
376          Otherwise right-click menus, that trigger on a button-press-event on
377          the containing PsppSheetView, will pop up if the button is rendered as
378          a facade but not if the button widget exists.  */
379       g_signal_stop_emission_by_name (button, "button-press-event");
380       return FALSE;
381     }
382
383   if (cell_button->click_time != 0)
384     {
385       GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (button));
386       GtkSettings *settings = gtk_settings_get_for_screen (screen);
387       gint double_click_distance;
388       gint double_click_time;
389
390       g_object_get (settings,
391                     "gtk-double-click-time", &double_click_time,
392                     "gtk-double-click-distance", &double_click_distance,
393                     NULL);
394
395       if (event->type == GDK_BUTTON_PRESS
396           && event->button == 1
397           && event->time <= cell_button->click_time + double_click_time
398           && ABS (event->x_root - cell_button->click_x) <= double_click_distance
399           && ABS (event->y_root - cell_button->click_y) <= double_click_distance)
400         {
401           psppire_cell_renderer_button_double_click (button, cell_button);
402           return TRUE;
403         }
404
405       cell_button->click_time = 0;
406     }
407
408   if (event->type == GDK_2BUTTON_PRESS)
409     {
410       psppire_cell_renderer_button_double_click (button, cell_button);
411       return TRUE;
412     }
413
414   return FALSE;
415 }
416
417 static GtkCellEditable *
418 psppire_cell_renderer_button_start_editing (GtkCellRenderer      *cell,
419                                             GdkEvent             *event,
420                                             GtkWidget            *widget,
421                                             const gchar          *path,
422                                             GdkRectangle         *background_area,
423                                             GdkRectangle         *cell_area,
424                                             GtkCellRendererState  flags)
425 {
426   PsppireCellRendererButton *cell_button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
427   gfloat xalign, yalign;
428
429   gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
430   cell_button->button = g_object_new (PSPPIRE_TYPE_BUTTON_EDITABLE,
431                                       "label", cell_button->label,
432                                       "xalign", xalign,
433                                       "yalign", yalign,
434                                       "path", path,
435                                       "slash", cell_button->slash,
436                                       NULL);
437
438   g_signal_connect (G_OBJECT (cell_button->button), "focus-out-event",
439                     G_CALLBACK (psppire_cell_renderer_button_focus_out_event),
440                     cell);
441   g_signal_connect (G_OBJECT (cell_button->button), "clicked",
442                     G_CALLBACK (psppire_cell_renderer_button_clicked),
443                     cell);
444   g_signal_connect (G_OBJECT (cell_button->button), "button-press-event",
445                     G_CALLBACK (psppire_cell_renderer_button_press_event),
446                     cell);
447
448   gtk_widget_show (cell_button->button);
449
450   if (event != NULL && event->any.type == GDK_BUTTON_RELEASE)
451     {
452       guint idle_id;
453
454       cell_button->click_time = event->button.time;
455       cell_button->click_x = event->button.x_root;
456       cell_button->click_y = event->button.y_root;
457       idle_id = g_idle_add (psppire_cell_renderer_button_initial_click,
458                             cell_button->button);
459       g_object_set_data (G_OBJECT (cell_button->button), IDLE_ID_STRING,
460                          GINT_TO_POINTER (idle_id));
461       g_signal_connect (G_OBJECT (cell_button->button), "destroy",
462                         G_CALLBACK (psppire_cell_renderer_button_on_destroy),
463                         NULL);
464     }
465   else
466     {
467       cell_button->click_time = 0;
468       cell_button->click_x = 0;
469       cell_button->click_y = 0;
470     }
471
472   return GTK_CELL_EDITABLE (cell_button->button);
473 }
474
475 static void
476 psppire_cell_renderer_button_class_init (PsppireCellRendererButtonClass *class)
477 {
478   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
479   GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS (class);
480   GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
481
482   gobject_class->set_property = psppire_cell_renderer_button_set_property;
483   gobject_class->get_property = psppire_cell_renderer_button_get_property;
484   gobject_class->finalize = psppire_cell_renderer_button_finalize;
485
486   gtk_object_class->destroy = psppire_cell_renderer_button_destroy;
487
488   cell_class->get_size = psppire_cell_renderer_button_get_size;
489   cell_class->render = psppire_cell_renderer_button_render;
490   cell_class->start_editing = psppire_cell_renderer_button_start_editing;
491
492   g_signal_new ("clicked",
493                 G_TYPE_FROM_CLASS (gobject_class),
494                 G_SIGNAL_RUN_LAST,
495                 0,
496                 NULL, NULL,
497                 g_cclosure_marshal_VOID__STRING,
498                 G_TYPE_NONE,
499                 1, G_TYPE_STRING);
500
501   g_signal_new ("double-clicked",
502                 G_TYPE_FROM_CLASS (gobject_class),
503                 G_SIGNAL_RUN_LAST,
504                 0,
505                 NULL, NULL,
506                 g_cclosure_marshal_VOID__STRING,
507                 G_TYPE_NONE,
508                 1, G_TYPE_STRING);
509
510   g_object_class_install_property (gobject_class,
511                                    PROP_EDITABLE,
512                                    g_param_spec_boolean ("editable",
513                                                          "Editable",
514                                                          "Whether the button may be clicked.",
515                                                          FALSE,
516                                                          G_PARAM_READWRITE));
517
518   g_object_class_install_property (gobject_class,
519                                    PROP_LABEL,
520                                    g_param_spec_string ("label",
521                                                         "Label",
522                                                         "Text to appear in button.",
523                                                         "",
524                                                         G_PARAM_READWRITE));
525
526   g_object_class_install_property (gobject_class,
527                                    PROP_SLASH,
528                                    g_param_spec_boolean ("slash",
529                                                          _("Diagonal slash"),
530                                                          _("Whether to draw a diagonal slash across the button."),
531                                                          FALSE,
532                                                          G_PARAM_READWRITE));
533
534 }
535
536 static void
537 psppire_cell_renderer_button_init (PsppireCellRendererButton *obj)
538 {
539   obj->editable = FALSE;
540   obj->label = g_strdup ("");
541   obj->border_width = 0;
542   obj->xpad = 0;
543   obj->ypad = 0;
544
545   obj->slash = FALSE;
546
547   obj->button = NULL;
548
549   obj->button_style = NULL;
550   obj->label_style = NULL;
551   obj->base = NULL;
552   obj->style_set_handler = 0;
553   obj->destroy_handler = 0;
554 }
555
556 static void
557 psppire_cell_renderer_button_finalize (GObject *obj)
558 {
559   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
560
561   g_free (button->label);
562 }
563
564 static void
565 psppire_cell_renderer_button_destroy (GtkObject *obj)
566 {
567   PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
568
569   update_style_cache (button, NULL);
570
571   GTK_OBJECT_CLASS (psppire_cell_renderer_button_parent_class)->destroy (obj);
572 }
573
574 GtkCellRenderer *
575 psppire_cell_renderer_button_new (void)
576 {
577   return GTK_CELL_RENDERER (g_object_new (PSPPIRE_TYPE_CELL_RENDERER_BUTTON, NULL));
578 }
579
580 void
581 psppire_cell_renderer_button_set_slash (PsppireCellRendererButton *button,
582                                         gboolean slash)
583 {
584   g_return_if_fail (button != NULL);
585   button->slash = slash;
586 }
587
588 gboolean
589 psppire_cell_renderer_button_get_slash (const PsppireCellRendererButton *button)
590 {
591   g_return_val_if_fail (button != NULL, FALSE);
592   return button->slash;
593 }