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 GdkRectangle *background_area,
187 GdkRectangle *cell_area,
188 GdkRectangle *expose_area,
189 GtkCellRendererState flags)
191 GtkStateType state_type;
192 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
193 gfloat xalign, yalign;
195 if (!button->editable || ! gtk_cell_renderer_get_sensitive (cell))
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 gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
216 update_style_cache (button, widget);
217 facade_button_render (widget, window, expose_area,
218 cell_area, button->border_width, button->button_style,
220 button->label_style, button->label, button->xpad,
221 button->ypad, xalign, yalign);
225 cairo_t *cr = gdk_cairo_create (window);
227 cairo_set_line_width (cr, 1.0);
228 cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
231 cell_area->y + cell_area->height);
234 cell_area->x + cell_area->width,
241 psppire_cell_renderer_button_get_size (GtkCellRenderer *cell,
243 GdkRectangle *cell_area,
249 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
251 update_style_cache (button, widget);
252 if (cell_area != NULL)
254 /* The caller is really asking for the placement of the focus rectangle.
255 The focus rectangle should surround the whole label area, so calculate
259 facade_button_get_focus_inset (button->border_width, widget,
260 button->button_style, &inset);
263 *x_offset = inset.left;
265 *y_offset = inset.top;
267 *width = MAX (1, cell_area->width - inset.left - inset.right);
269 *height = MAX (1, cell_area->height - inset.top - inset.bottom);
273 /* The caller is asking for the preferred size of the cell. */
274 GtkRequisition label_req;
275 GtkRequisition request;
277 facade_label_get_size_request (button->xpad, button->ypad,
278 widget, button->label, &label_req);
279 facade_button_get_size_request (button->border_width, widget,
280 button->button_style, &label_req,
288 *width = request.width;
290 *height = request.height;
295 psppire_cell_renderer_button_clicked (GtkButton *button,
298 PsppireCellRendererButton *cell_button = data;
301 g_object_get (button, "path", &path, NULL);
302 g_signal_emit_by_name (cell_button, "clicked", path);
306 #define IDLE_ID_STRING "psppire-cell-renderer-button-idle-id"
309 psppire_cell_renderer_button_initial_click (gpointer data)
311 GtkButton *button = data;
313 g_object_steal_data (G_OBJECT (button), IDLE_ID_STRING);
314 gtk_button_clicked (button);
319 psppire_cell_renderer_button_on_destroy (GObject *object, gpointer user_data)
323 idle_id = GPOINTER_TO_INT (g_object_steal_data (object, IDLE_ID_STRING));
325 g_source_remove (idle_id);
329 psppire_cell_renderer_button_double_click (GtkButton *button,
330 PsppireCellRendererButton *cell_button)
334 if (g_object_get_data (G_OBJECT (button), IDLE_ID_STRING))
335 psppire_cell_renderer_button_initial_click (button);
337 g_object_get (button, "path", &path, NULL);
338 g_signal_emit_by_name (cell_button, "double-clicked", path);
343 psppire_cell_renderer_button_press_event (GtkButton *button,
344 GdkEventButton *event,
347 PsppireCellRendererButton *cell_button = data;
349 if (event->button == 3)
351 /* Allow right-click events to propagate upward in the widget hierarchy.
352 Otherwise right-click menus, that trigger on a button-press-event on
353 the containing PsppSheetView, will pop up if the button is rendered as
354 a facade but not if the button widget exists.
356 We have to translate the event's data by hand to be relative to the
357 parent window, because the normal GObject signal propagation mechanism
358 won't do it for us. (This might be a hint that we're doing this
362 gdk_window_get_position (event->window, &x, &y);
365 g_signal_stop_emission_by_name (button, "button-press-event");
369 if (cell_button->click_time != 0)
371 GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (button));
372 GtkSettings *settings = gtk_settings_get_for_screen (screen);
373 gint double_click_distance;
374 gint double_click_time;
376 g_object_get (settings,
377 "gtk-double-click-time", &double_click_time,
378 "gtk-double-click-distance", &double_click_distance,
381 if (event->type == GDK_BUTTON_PRESS
382 && event->button == 1
383 && event->time <= cell_button->click_time + double_click_time
384 && ABS (event->x_root - cell_button->click_x) <= double_click_distance
385 && ABS (event->y_root - cell_button->click_y) <= double_click_distance)
387 psppire_cell_renderer_button_double_click (button, cell_button);
391 cell_button->click_time = 0;
394 if (event->type == GDK_2BUTTON_PRESS)
396 psppire_cell_renderer_button_double_click (button, cell_button);
403 static GtkCellEditable *
404 psppire_cell_renderer_button_start_editing (GtkCellRenderer *cell,
408 GdkRectangle *background_area,
409 GdkRectangle *cell_area,
410 GtkCellRendererState flags)
412 PsppireCellRendererButton *cell_button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
413 gfloat xalign, yalign;
415 gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
416 cell_button->button = g_object_new (PSPPIRE_TYPE_BUTTON_EDITABLE,
417 "label", cell_button->label,
423 g_signal_connect (G_OBJECT (cell_button->button), "clicked",
424 G_CALLBACK (psppire_cell_renderer_button_clicked),
426 g_signal_connect (G_OBJECT (cell_button->button), "button-press-event",
427 G_CALLBACK (psppire_cell_renderer_button_press_event),
430 gtk_widget_show (cell_button->button);
432 if (event != NULL && event->any.type == GDK_BUTTON_RELEASE)
436 cell_button->click_time = event->button.time;
437 cell_button->click_x = event->button.x_root;
438 cell_button->click_y = event->button.y_root;
439 idle_id = g_idle_add (psppire_cell_renderer_button_initial_click,
440 cell_button->button);
441 g_object_set_data (G_OBJECT (cell_button->button), IDLE_ID_STRING,
442 GINT_TO_POINTER (idle_id));
443 g_signal_connect (G_OBJECT (cell_button->button), "destroy",
444 G_CALLBACK (psppire_cell_renderer_button_on_destroy),
449 cell_button->click_time = 0;
450 cell_button->click_x = 0;
451 cell_button->click_y = 0;
454 return GTK_CELL_EDITABLE (cell_button->button);
458 psppire_cell_renderer_button_class_init (PsppireCellRendererButtonClass *class)
460 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
461 GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
463 gobject_class->set_property = psppire_cell_renderer_button_set_property;
464 gobject_class->get_property = psppire_cell_renderer_button_get_property;
465 gobject_class->finalize = psppire_cell_renderer_button_finalize;
466 gobject_class->dispose = psppire_cell_renderer_button_dispose;
468 cell_class->get_size = psppire_cell_renderer_button_get_size;
469 cell_class->render = psppire_cell_renderer_button_render;
470 cell_class->start_editing = psppire_cell_renderer_button_start_editing;
472 g_signal_new ("clicked",
473 G_TYPE_FROM_CLASS (gobject_class),
477 g_cclosure_marshal_VOID__STRING,
481 g_signal_new ("double-clicked",
482 G_TYPE_FROM_CLASS (gobject_class),
486 g_cclosure_marshal_VOID__STRING,
490 g_object_class_install_property (gobject_class,
492 g_param_spec_boolean ("editable",
494 "Whether the button may be clicked.",
498 g_object_class_install_property (gobject_class,
500 g_param_spec_string ("label",
502 "Text to appear in button.",
506 g_object_class_install_property (gobject_class,
508 g_param_spec_boolean ("slash",
510 _("Whether to draw a diagonal slash across the button."),
517 psppire_cell_renderer_button_init (PsppireCellRendererButton *obj)
519 obj->editable = FALSE;
520 obj->label = g_strdup ("");
521 obj->border_width = 0;
529 obj->button_style = NULL;
530 obj->label_style = NULL;
532 obj->style_set_handler = 0;
533 obj->dispose_has_run = FALSE;
537 psppire_cell_renderer_button_finalize (GObject *obj)
539 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
541 g_free (button->label);
545 psppire_cell_renderer_button_dispose (GObject *obj)
547 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
549 if (button->dispose_has_run)
552 button->dispose_has_run = TRUE;
554 /* When called with NULL, as we are doing here, update_style_cache
555 does nothing more than to drop references */
556 update_style_cache (button, NULL);
558 G_OBJECT_CLASS (psppire_cell_renderer_button_parent_class)->dispose (obj);
562 psppire_cell_renderer_button_new (void)
564 return GTK_CELL_RENDERER (g_object_new (PSPPIRE_TYPE_CELL_RENDERER_BUTTON, NULL));
568 psppire_cell_renderer_button_set_slash (PsppireCellRendererButton *button,
571 g_return_if_fail (button != NULL);
572 button->slash = slash;
576 psppire_cell_renderer_button_get_slash (const PsppireCellRendererButton *button)
578 g_return_val_if_fail (button != NULL, FALSE);
579 return button->slash;