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);
77 obj->editable = g_value_get_boolean (value);
79 GTK_CELL_RENDERER (obj)->mode = GTK_CELL_RENDERER_MODE_EDITABLE;
81 GTK_CELL_RENDERER (obj)->mode = GTK_CELL_RENDERER_MODE_INERT;
86 obj->label = g_value_dup_string (value);
90 psppire_cell_renderer_button_set_slash (obj,
91 g_value_get_boolean (value));
95 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
101 psppire_cell_renderer_button_get_property (GObject *object,
106 PsppireCellRendererButton *obj = PSPPIRE_CELL_RENDERER_BUTTON (object);
111 g_value_set_boolean (value, obj->editable);
115 g_value_set_string (value, obj->label);
119 g_value_set_boolean (value,
120 psppire_cell_renderer_button_get_slash (obj));
124 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
130 on_style_set (GtkWidget *base,
131 GtkStyle *previous_style,
132 PsppireCellRendererButton *button)
134 update_style_cache (button, NULL);
138 update_style_cache (PsppireCellRendererButton *button,
141 if (button->base == widget)
144 /* Clear old cache. */
145 if (button->button_style)
147 g_object_unref (button->button_style);
148 button->button_style = NULL;
150 if (button->label_style)
152 g_object_unref (button->label_style);
153 button->label_style = NULL;
155 if (button->base != NULL)
157 if (button->style_set_handler)
159 g_signal_handler_disconnect (button->base,
160 button->style_set_handler);
161 button->style_set_handler = 0;
163 g_object_unref (button->base);
167 /* Populate cache. */
170 button->button_style = facade_get_style (widget, GTK_TYPE_BUTTON, 0);
171 button->label_style = facade_get_style (widget, GTK_TYPE_BUTTON,
173 button->base = widget;
174 button->style_set_handler = g_signal_connect (widget, "style-set",
175 G_CALLBACK (on_style_set),
177 g_object_ref (widget);
178 g_object_ref (button->button_style);
179 g_object_ref (button->label_style);
184 psppire_cell_renderer_button_render (GtkCellRenderer *cell,
187 GdkRectangle *background_area,
188 GdkRectangle *cell_area,
189 GdkRectangle *expose_area,
190 GtkCellRendererState flags)
192 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
193 GtkStateType state_type;
195 if (!button->editable || !cell->sensitive)
196 state_type = GTK_STATE_INSENSITIVE;
197 else if (flags & GTK_CELL_RENDERER_SELECTED)
199 if (gtk_widget_has_focus (widget))
200 state_type = GTK_STATE_SELECTED;
202 state_type = GTK_STATE_ACTIVE;
204 else if (flags & GTK_CELL_RENDERER_PRELIT)
205 state_type = GTK_STATE_PRELIGHT;
208 if (gtk_widget_get_state (widget) == GTK_STATE_INSENSITIVE)
209 state_type = GTK_STATE_INSENSITIVE;
211 state_type = GTK_STATE_NORMAL;
214 update_style_cache (button, widget);
215 facade_button_render (widget, window, expose_area,
216 cell_area, button->border_width, button->button_style,
218 button->label_style, button->label, button->xpad,
219 button->ypad, cell->xalign, cell->yalign);
223 cairo_t *cr = gdk_cairo_create (window);
225 cairo_set_line_width (cr, 1.0);
226 cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
229 cell_area->y + cell_area->height);
232 cell_area->x + cell_area->width,
239 psppire_cell_renderer_button_get_size (GtkCellRenderer *cell,
241 GdkRectangle *cell_area,
247 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
249 update_style_cache (button, widget);
250 if (cell_area != NULL)
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
257 facade_button_get_focus_inset (button->border_width, widget,
258 button->button_style, &inset);
261 *x_offset = inset.left;
263 *y_offset = inset.top;
265 *width = MAX (1, cell_area->width - inset.left - inset.right);
267 *height = MAX (1, cell_area->height - inset.top - inset.bottom);
271 /* The caller is asking for the preferred size of the cell. */
272 GtkRequisition label_req;
273 GtkRequisition request;
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,
286 *width = request.width;
288 *height = request.height;
293 psppire_cell_renderer_button_clicked (GtkButton *button,
296 PsppireCellRendererButton *cell_button = data;
299 g_object_get (button, "path", &path, NULL);
300 g_signal_emit_by_name (cell_button, "clicked", path);
304 #define IDLE_ID_STRING "psppire-cell-renderer-button-idle-id"
307 psppire_cell_renderer_button_initial_click (gpointer data)
309 GtkButton *button = data;
311 g_object_steal_data (G_OBJECT (button), IDLE_ID_STRING);
312 gtk_button_clicked (button);
317 psppire_cell_renderer_button_on_destroy (GObject *object, gpointer user_data)
321 idle_id = GPOINTER_TO_INT (g_object_steal_data (object, IDLE_ID_STRING));
323 g_source_remove (idle_id);
327 psppire_cell_renderer_button_double_click (GtkButton *button,
328 PsppireCellRendererButton *cell_button)
332 if (g_object_get_data (G_OBJECT (button), IDLE_ID_STRING))
333 psppire_cell_renderer_button_initial_click (button);
335 g_object_get (button, "path", &path, NULL);
336 g_signal_emit_by_name (cell_button, "double-clicked", path);
341 psppire_cell_renderer_button_press_event (GtkButton *button,
342 GdkEventButton *event,
345 PsppireCellRendererButton *cell_button = data;
347 if (event->button == 3)
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.
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
360 gdk_window_get_position (event->window, &x, &y);
363 g_signal_stop_emission_by_name (button, "button-press-event");
367 if (cell_button->click_time != 0)
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;
374 g_object_get (settings,
375 "gtk-double-click-time", &double_click_time,
376 "gtk-double-click-distance", &double_click_distance,
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)
385 psppire_cell_renderer_button_double_click (button, cell_button);
389 cell_button->click_time = 0;
392 if (event->type == GDK_2BUTTON_PRESS)
394 psppire_cell_renderer_button_double_click (button, cell_button);
401 static GtkCellEditable *
402 psppire_cell_renderer_button_start_editing (GtkCellRenderer *cell,
406 GdkRectangle *background_area,
407 GdkRectangle *cell_area,
408 GtkCellRendererState flags)
410 PsppireCellRendererButton *cell_button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
411 gfloat xalign, yalign;
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,
421 g_signal_connect (G_OBJECT (cell_button->button), "clicked",
422 G_CALLBACK (psppire_cell_renderer_button_clicked),
424 g_signal_connect (G_OBJECT (cell_button->button), "button-press-event",
425 G_CALLBACK (psppire_cell_renderer_button_press_event),
428 gtk_widget_show (cell_button->button);
430 if (event != NULL && event->any.type == GDK_BUTTON_RELEASE)
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),
447 cell_button->click_time = 0;
448 cell_button->click_x = 0;
449 cell_button->click_y = 0;
452 return GTK_CELL_EDITABLE (cell_button->button);
456 psppire_cell_renderer_button_class_init (PsppireCellRendererButtonClass *class)
458 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
459 GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
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;
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;
470 g_signal_new ("clicked",
471 G_TYPE_FROM_CLASS (gobject_class),
475 g_cclosure_marshal_VOID__STRING,
479 g_signal_new ("double-clicked",
480 G_TYPE_FROM_CLASS (gobject_class),
484 g_cclosure_marshal_VOID__STRING,
488 g_object_class_install_property (gobject_class,
490 g_param_spec_boolean ("editable",
492 "Whether the button may be clicked.",
496 g_object_class_install_property (gobject_class,
498 g_param_spec_string ("label",
500 "Text to appear in button.",
504 g_object_class_install_property (gobject_class,
506 g_param_spec_boolean ("slash",
508 _("Whether to draw a diagonal slash across the button."),
515 psppire_cell_renderer_button_init (PsppireCellRendererButton *obj)
517 obj->editable = FALSE;
518 obj->label = g_strdup ("");
519 obj->border_width = 0;
527 obj->button_style = NULL;
528 obj->label_style = NULL;
530 obj->style_set_handler = 0;
531 obj->dispose_has_run = FALSE;
535 psppire_cell_renderer_button_finalize (GObject *obj)
537 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
539 g_free (button->label);
543 psppire_cell_renderer_button_dispose (GObject *obj)
545 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
547 if (button->dispose_has_run)
550 button->dispose_has_run = TRUE;
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);
556 G_OBJECT_CLASS (psppire_cell_renderer_button_parent_class)->dispose (obj);
560 psppire_cell_renderer_button_new (void)
562 return GTK_CELL_RENDERER (g_object_new (PSPPIRE_TYPE_CELL_RENDERER_BUTTON, NULL));
566 psppire_cell_renderer_button_set_slash (PsppireCellRendererButton *button,
569 g_return_if_fail (button != NULL);
570 button->slash = slash;
574 psppire_cell_renderer_button_get_slash (const PsppireCellRendererButton *button)
576 g_return_val_if_fail (button != NULL, FALSE);
577 return button->slash;