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"
30 #define P_(msgid) (msgid)
32 static void psppire_cell_renderer_button_dispose (GObject *);
33 static void psppire_cell_renderer_button_finalize (GObject *);
35 static void update_style_cache (PsppireCellRendererButton *button,
38 static void psppire_cell_renderer_button_load_gtkrc (void);
41 G_DEFINE_TYPE_EXTENDED (PsppireCellRendererButton,
42 psppire_cell_renderer_button,
43 GTK_TYPE_CELL_RENDERER,
45 psppire_cell_renderer_button_load_gtkrc ());
48 psppire_cell_renderer_button_load_gtkrc (void)
50 const char *gtkrc_file;
52 gtkrc_file = relocate (PKGDATADIR "/psppire.gtkrc");
53 gtk_rc_add_default_file (gtkrc_file);
54 gtk_rc_parse (gtkrc_file);
66 psppire_cell_renderer_button_set_property (GObject *object,
71 PsppireCellRendererButton *obj = PSPPIRE_CELL_RENDERER_BUTTON (object);
75 obj->editable = g_value_get_boolean (value);
77 g_object_set (obj, "mode", GTK_CELL_RENDERER_MODE_EDITABLE, NULL);
79 g_object_set (obj, "mode", GTK_CELL_RENDERER_MODE_INERT, NULL);
84 obj->label = g_value_dup_string (value);
88 psppire_cell_renderer_button_set_slash (obj,
89 g_value_get_boolean (value));
93 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
99 psppire_cell_renderer_button_get_property (GObject *object,
104 PsppireCellRendererButton *obj = PSPPIRE_CELL_RENDERER_BUTTON (object);
109 g_value_set_boolean (value, obj->editable);
113 g_value_set_string (value, obj->label);
117 g_value_set_boolean (value,
118 psppire_cell_renderer_button_get_slash (obj));
122 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
128 on_style_set (GtkWidget *base,
129 GtkStyle *previous_style,
130 PsppireCellRendererButton *button)
132 update_style_cache (button, NULL);
136 update_style_cache (PsppireCellRendererButton *button,
139 if (button->base == widget)
142 /* Clear old cache. */
143 if (button->button_style)
145 g_object_unref (button->button_style);
146 button->button_style = NULL;
148 if (button->label_style)
150 g_object_unref (button->label_style);
151 button->label_style = NULL;
153 if (button->base != NULL)
155 if (button->style_set_handler)
157 g_signal_handler_disconnect (button->base,
158 button->style_set_handler);
159 button->style_set_handler = 0;
161 g_object_unref (button->base);
165 /* Populate cache. */
168 button->button_style = facade_get_style (widget, GTK_TYPE_BUTTON, 0);
169 button->label_style = facade_get_style (widget, GTK_TYPE_BUTTON,
171 button->base = widget;
172 button->style_set_handler = g_signal_connect (widget, "style-set",
173 G_CALLBACK (on_style_set),
175 g_object_ref (widget);
176 g_object_ref (button->button_style);
177 g_object_ref (button->label_style);
182 psppire_cell_renderer_button_render (GtkCellRenderer *cell,
185 const GdkRectangle *background_area,
186 const GdkRectangle *cell_area,
187 GtkCellRendererState flags)
189 GtkStateType state_type;
190 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
191 gfloat xalign, yalign;
193 if (!button->editable || ! gtk_cell_renderer_get_sensitive (cell))
194 state_type = GTK_STATE_INSENSITIVE;
195 else if (flags & GTK_CELL_RENDERER_SELECTED)
197 if (gtk_widget_has_focus (widget))
198 state_type = GTK_STATE_SELECTED;
200 state_type = GTK_STATE_ACTIVE;
202 else if (flags & GTK_CELL_RENDERER_PRELIT)
203 state_type = GTK_STATE_PRELIGHT;
206 if (gtk_widget_get_state_flags (widget) == GTK_STATE_FLAG_INSENSITIVE)
207 state_type = GTK_STATE_INSENSITIVE;
209 state_type = GTK_STATE_NORMAL;
212 gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
215 update_style_cache (button, widget);
217 facade_button_render (widget, cr,
218 cell_area, button->border_width, button->button_style,
220 button->label_style, button->label, button->xpad,
221 button->ypad, xalign, yalign);
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 const 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 const GdkRectangle *background_area,
407 const 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 P_("Whether the button may be clicked."),
496 g_object_class_install_property (gobject_class,
498 g_param_spec_string ("label",
500 P_("Text to appear in button."),
504 g_object_class_install_property (gobject_class,
506 g_param_spec_boolean ("slash",
507 P_("Diagonal slash"),
508 P_("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;