1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2011, 2012 Free Software Foundation, Inc.
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.
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.
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/>. */
19 #include "ui/gui/psppire-cell-renderer-button.h"
24 #include "ui/gui/psppire-button-editable.h"
25 #include "ui/gui/pspp-widget-facade.h"
27 #include "gl/configmake.h"
28 #include "gl/relocatable.h"
31 #define _(msgid) gettext (msgid)
33 static void psppire_cell_renderer_button_dispose (GObject *);
34 static void psppire_cell_renderer_button_finalize (GObject *);
36 static void update_style_cache (PsppireCellRendererButton *button,
39 static void psppire_cell_renderer_button_load_gtkrc (void);
42 G_DEFINE_TYPE_EXTENDED (PsppireCellRendererButton,
43 psppire_cell_renderer_button,
44 GTK_TYPE_CELL_RENDERER,
46 psppire_cell_renderer_button_load_gtkrc ());
49 psppire_cell_renderer_button_load_gtkrc (void)
51 const char *gtkrc_file;
53 gtkrc_file = relocate (PKGDATADIR "/psppire.gtkrc");
54 gtk_rc_add_default_file (gtkrc_file);
55 gtk_rc_parse (gtkrc_file);
67 psppire_cell_renderer_button_set_property (GObject *object,
72 PsppireCellRendererButton *obj = PSPPIRE_CELL_RENDERER_BUTTON (object);
76 obj->editable = g_value_get_boolean (value);
78 g_object_set (obj, "mode", GTK_CELL_RENDERER_MODE_EDITABLE, NULL);
80 g_object_set (obj, "mode", GTK_CELL_RENDERER_MODE_INERT, NULL);
85 obj->label = g_value_dup_string (value);
89 psppire_cell_renderer_button_set_slash (obj,
90 g_value_get_boolean (value));
94 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
100 psppire_cell_renderer_button_get_property (GObject *object,
105 PsppireCellRendererButton *obj = PSPPIRE_CELL_RENDERER_BUTTON (object);
110 g_value_set_boolean (value, obj->editable);
114 g_value_set_string (value, obj->label);
118 g_value_set_boolean (value,
119 psppire_cell_renderer_button_get_slash (obj));
123 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
129 on_style_set (GtkWidget *base,
130 GtkStyle *previous_style,
131 PsppireCellRendererButton *button)
133 update_style_cache (button, NULL);
137 update_style_cache (PsppireCellRendererButton *button,
140 if (button->base == widget)
143 /* Clear old cache. */
144 if (button->button_style)
146 g_object_unref (button->button_style);
147 button->button_style = NULL;
149 if (button->label_style)
151 g_object_unref (button->label_style);
152 button->label_style = NULL;
154 if (button->base != NULL)
156 if (button->style_set_handler)
158 g_signal_handler_disconnect (button->base,
159 button->style_set_handler);
160 button->style_set_handler = 0;
162 g_object_unref (button->base);
166 /* Populate cache. */
169 button->button_style = facade_get_style (widget, GTK_TYPE_BUTTON, 0);
170 button->label_style = facade_get_style (widget, GTK_TYPE_BUTTON,
172 button->base = widget;
173 button->style_set_handler = g_signal_connect (widget, "style-set",
174 G_CALLBACK (on_style_set),
176 g_object_ref (widget);
177 g_object_ref (button->button_style);
178 g_object_ref (button->label_style);
183 psppire_cell_renderer_button_render (GtkCellRenderer *cell,
186 const GdkRectangle *background_area,
187 const GdkRectangle *cell_area,
188 GtkCellRendererState flags)
190 GtkStateType state_type;
191 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
192 gfloat xalign, yalign;
194 if (!button->editable || ! gtk_cell_renderer_get_sensitive (cell))
195 state_type = GTK_STATE_INSENSITIVE;
196 else if (flags & GTK_CELL_RENDERER_SELECTED)
198 if (gtk_widget_has_focus (widget))
199 state_type = GTK_STATE_SELECTED;
201 state_type = GTK_STATE_ACTIVE;
203 else if (flags & GTK_CELL_RENDERER_PRELIT)
204 state_type = GTK_STATE_PRELIGHT;
207 if (gtk_widget_get_state_flags (widget) == GTK_STATE_FLAG_INSENSITIVE)
208 state_type = GTK_STATE_INSENSITIVE;
210 state_type = GTK_STATE_NORMAL;
213 gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
216 update_style_cache (button, widget);
218 facade_button_render (widget, cr,
219 cell_area, button->border_width, button->button_style,
221 button->label_style, button->label, button->xpad,
222 button->ypad, xalign, yalign);
226 cairo_set_line_width (cr, 1.0);
227 cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
230 cell_area->y + cell_area->height);
233 cell_area->x + cell_area->width,
240 psppire_cell_renderer_button_get_size (GtkCellRenderer *cell,
242 const GdkRectangle *cell_area,
248 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
250 update_style_cache (button, widget);
251 if (cell_area != NULL)
253 /* The caller is really asking for the placement of the focus rectangle.
254 The focus rectangle should surround the whole label area, so calculate
258 facade_button_get_focus_inset (button->border_width, widget,
259 button->button_style, &inset);
262 *x_offset = inset.left;
264 *y_offset = inset.top;
266 *width = MAX (1, cell_area->width - inset.left - inset.right);
268 *height = MAX (1, cell_area->height - inset.top - inset.bottom);
272 /* The caller is asking for the preferred size of the cell. */
273 GtkRequisition label_req;
274 GtkRequisition request;
276 facade_label_get_size_request (button->xpad, button->ypad,
277 widget, button->label, &label_req);
278 facade_button_get_size_request (button->border_width, widget,
279 button->button_style, &label_req,
287 *width = request.width;
289 *height = request.height;
294 psppire_cell_renderer_button_clicked (GtkButton *button,
297 PsppireCellRendererButton *cell_button = data;
300 g_object_get (button, "path", &path, NULL);
301 g_signal_emit_by_name (cell_button, "clicked", path);
305 #define IDLE_ID_STRING "psppire-cell-renderer-button-idle-id"
308 psppire_cell_renderer_button_initial_click (gpointer data)
310 GtkButton *button = data;
312 g_object_steal_data (G_OBJECT (button), IDLE_ID_STRING);
313 gtk_button_clicked (button);
318 psppire_cell_renderer_button_on_destroy (GObject *object, gpointer user_data)
322 idle_id = GPOINTER_TO_INT (g_object_steal_data (object, IDLE_ID_STRING));
324 g_source_remove (idle_id);
328 psppire_cell_renderer_button_double_click (GtkButton *button,
329 PsppireCellRendererButton *cell_button)
333 if (g_object_get_data (G_OBJECT (button), IDLE_ID_STRING))
334 psppire_cell_renderer_button_initial_click (button);
336 g_object_get (button, "path", &path, NULL);
337 g_signal_emit_by_name (cell_button, "double-clicked", path);
342 psppire_cell_renderer_button_press_event (GtkButton *button,
343 GdkEventButton *event,
346 PsppireCellRendererButton *cell_button = data;
348 if (event->button == 3)
350 /* Allow right-click events to propagate upward in the widget hierarchy.
351 Otherwise right-click menus, that trigger on a button-press-event on
352 the containing PsppSheetView, will pop up if the button is rendered as
353 a facade but not if the button widget exists.
355 We have to translate the event's data by hand to be relative to the
356 parent window, because the normal GObject signal propagation mechanism
357 won't do it for us. (This might be a hint that we're doing this
361 gdk_window_get_position (event->window, &x, &y);
364 g_signal_stop_emission_by_name (button, "button-press-event");
368 if (cell_button->click_time != 0)
370 GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (button));
371 GtkSettings *settings = gtk_settings_get_for_screen (screen);
372 gint double_click_distance;
373 gint double_click_time;
375 g_object_get (settings,
376 "gtk-double-click-time", &double_click_time,
377 "gtk-double-click-distance", &double_click_distance,
380 if (event->type == GDK_BUTTON_PRESS
381 && event->button == 1
382 && event->time <= cell_button->click_time + double_click_time
383 && ABS (event->x_root - cell_button->click_x) <= double_click_distance
384 && ABS (event->y_root - cell_button->click_y) <= double_click_distance)
386 psppire_cell_renderer_button_double_click (button, cell_button);
390 cell_button->click_time = 0;
393 if (event->type == GDK_2BUTTON_PRESS)
395 psppire_cell_renderer_button_double_click (button, cell_button);
402 static GtkCellEditable *
403 psppire_cell_renderer_button_start_editing (GtkCellRenderer *cell,
407 const GdkRectangle *background_area,
408 const GdkRectangle *cell_area,
409 GtkCellRendererState flags)
411 PsppireCellRendererButton *cell_button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
412 gfloat xalign, yalign;
414 gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
415 cell_button->button = g_object_new (PSPPIRE_TYPE_BUTTON_EDITABLE,
416 "label", cell_button->label,
422 g_signal_connect (G_OBJECT (cell_button->button), "clicked",
423 G_CALLBACK (psppire_cell_renderer_button_clicked),
425 g_signal_connect (G_OBJECT (cell_button->button), "button-press-event",
426 G_CALLBACK (psppire_cell_renderer_button_press_event),
429 gtk_widget_show (cell_button->button);
431 if (event != NULL && event->any.type == GDK_BUTTON_RELEASE)
435 cell_button->click_time = event->button.time;
436 cell_button->click_x = event->button.x_root;
437 cell_button->click_y = event->button.y_root;
438 idle_id = g_idle_add (psppire_cell_renderer_button_initial_click,
439 cell_button->button);
440 g_object_set_data (G_OBJECT (cell_button->button), IDLE_ID_STRING,
441 GINT_TO_POINTER (idle_id));
442 g_signal_connect (G_OBJECT (cell_button->button), "destroy",
443 G_CALLBACK (psppire_cell_renderer_button_on_destroy),
448 cell_button->click_time = 0;
449 cell_button->click_x = 0;
450 cell_button->click_y = 0;
453 return GTK_CELL_EDITABLE (cell_button->button);
457 psppire_cell_renderer_button_class_init (PsppireCellRendererButtonClass *class)
459 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
460 GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
462 gobject_class->set_property = psppire_cell_renderer_button_set_property;
463 gobject_class->get_property = psppire_cell_renderer_button_get_property;
464 gobject_class->finalize = psppire_cell_renderer_button_finalize;
465 gobject_class->dispose = psppire_cell_renderer_button_dispose;
467 cell_class->get_size = psppire_cell_renderer_button_get_size;
468 cell_class->render = psppire_cell_renderer_button_render;
469 cell_class->start_editing = psppire_cell_renderer_button_start_editing;
471 g_signal_new ("clicked",
472 G_TYPE_FROM_CLASS (gobject_class),
476 g_cclosure_marshal_VOID__STRING,
480 g_signal_new ("double-clicked",
481 G_TYPE_FROM_CLASS (gobject_class),
485 g_cclosure_marshal_VOID__STRING,
489 g_object_class_install_property (gobject_class,
491 g_param_spec_boolean ("editable",
493 "Whether the button may be clicked.",
497 g_object_class_install_property (gobject_class,
499 g_param_spec_string ("label",
501 "Text to appear in button.",
505 g_object_class_install_property (gobject_class,
507 g_param_spec_boolean ("slash",
509 _("Whether to draw a diagonal slash across the button."),
516 psppire_cell_renderer_button_init (PsppireCellRendererButton *obj)
518 obj->editable = FALSE;
519 obj->label = g_strdup ("");
520 obj->border_width = 0;
528 obj->button_style = NULL;
529 obj->label_style = NULL;
531 obj->style_set_handler = 0;
532 obj->dispose_has_run = FALSE;
536 psppire_cell_renderer_button_finalize (GObject *obj)
538 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
540 g_free (button->label);
544 psppire_cell_renderer_button_dispose (GObject *obj)
546 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
548 if (button->dispose_has_run)
551 button->dispose_has_run = TRUE;
553 /* When called with NULL, as we are doing here, update_style_cache
554 does nothing more than to drop references */
555 update_style_cache (button, NULL);
557 G_OBJECT_CLASS (psppire_cell_renderer_button_parent_class)->dispose (obj);
561 psppire_cell_renderer_button_new (void)
563 return GTK_CELL_RENDERER (g_object_new (PSPPIRE_TYPE_CELL_RENDERER_BUTTON, NULL));
567 psppire_cell_renderer_button_set_slash (PsppireCellRendererButton *button,
570 g_return_if_fail (button != NULL);
571 button->slash = slash;
575 psppire_cell_renderer_button_get_slash (const PsppireCellRendererButton *button)
577 g_return_val_if_fail (button != NULL, FALSE);
578 return button->slash;