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;
196 if (!button->editable || ! gtk_cell_renderer_get_sensitive (cell))
197 state_type = GTK_STATE_INSENSITIVE;
198 else if (flags & GTK_CELL_RENDERER_SELECTED)
200 if (gtk_widget_has_focus (widget))
201 state_type = GTK_STATE_SELECTED;
203 state_type = GTK_STATE_ACTIVE;
205 else if (flags & GTK_CELL_RENDERER_PRELIT)
206 state_type = GTK_STATE_PRELIGHT;
209 if (gtk_widget_get_state (widget) == GTK_STATE_INSENSITIVE)
210 state_type = GTK_STATE_INSENSITIVE;
212 state_type = GTK_STATE_NORMAL;
215 gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
218 update_style_cache (button, widget);
220 cr = gdk_cairo_create (window);
221 facade_button_render (widget, cr, expose_area,
222 cell_area, button->border_width, button->button_style,
224 button->label_style, button->label, button->xpad,
225 button->ypad, xalign, yalign);
229 cairo_set_line_width (cr, 1.0);
230 cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
233 cell_area->y + cell_area->height);
236 cell_area->x + cell_area->width,
245 psppire_cell_renderer_button_get_size (GtkCellRenderer *cell,
247 GdkRectangle *cell_area,
253 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
255 update_style_cache (button, widget);
256 if (cell_area != NULL)
258 /* The caller is really asking for the placement of the focus rectangle.
259 The focus rectangle should surround the whole label area, so calculate
263 facade_button_get_focus_inset (button->border_width, widget,
264 button->button_style, &inset);
267 *x_offset = inset.left;
269 *y_offset = inset.top;
271 *width = MAX (1, cell_area->width - inset.left - inset.right);
273 *height = MAX (1, cell_area->height - inset.top - inset.bottom);
277 /* The caller is asking for the preferred size of the cell. */
278 GtkRequisition label_req;
279 GtkRequisition request;
281 facade_label_get_size_request (button->xpad, button->ypad,
282 widget, button->label, &label_req);
283 facade_button_get_size_request (button->border_width, widget,
284 button->button_style, &label_req,
292 *width = request.width;
294 *height = request.height;
299 psppire_cell_renderer_button_clicked (GtkButton *button,
302 PsppireCellRendererButton *cell_button = data;
305 g_object_get (button, "path", &path, NULL);
306 g_signal_emit_by_name (cell_button, "clicked", path);
310 #define IDLE_ID_STRING "psppire-cell-renderer-button-idle-id"
313 psppire_cell_renderer_button_initial_click (gpointer data)
315 GtkButton *button = data;
317 g_object_steal_data (G_OBJECT (button), IDLE_ID_STRING);
318 gtk_button_clicked (button);
323 psppire_cell_renderer_button_on_destroy (GObject *object, gpointer user_data)
327 idle_id = GPOINTER_TO_INT (g_object_steal_data (object, IDLE_ID_STRING));
329 g_source_remove (idle_id);
333 psppire_cell_renderer_button_double_click (GtkButton *button,
334 PsppireCellRendererButton *cell_button)
338 if (g_object_get_data (G_OBJECT (button), IDLE_ID_STRING))
339 psppire_cell_renderer_button_initial_click (button);
341 g_object_get (button, "path", &path, NULL);
342 g_signal_emit_by_name (cell_button, "double-clicked", path);
347 psppire_cell_renderer_button_press_event (GtkButton *button,
348 GdkEventButton *event,
351 PsppireCellRendererButton *cell_button = data;
353 if (event->button == 3)
355 /* Allow right-click events to propagate upward in the widget hierarchy.
356 Otherwise right-click menus, that trigger on a button-press-event on
357 the containing PsppSheetView, will pop up if the button is rendered as
358 a facade but not if the button widget exists.
360 We have to translate the event's data by hand to be relative to the
361 parent window, because the normal GObject signal propagation mechanism
362 won't do it for us. (This might be a hint that we're doing this
366 gdk_window_get_position (event->window, &x, &y);
369 g_signal_stop_emission_by_name (button, "button-press-event");
373 if (cell_button->click_time != 0)
375 GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (button));
376 GtkSettings *settings = gtk_settings_get_for_screen (screen);
377 gint double_click_distance;
378 gint double_click_time;
380 g_object_get (settings,
381 "gtk-double-click-time", &double_click_time,
382 "gtk-double-click-distance", &double_click_distance,
385 if (event->type == GDK_BUTTON_PRESS
386 && event->button == 1
387 && event->time <= cell_button->click_time + double_click_time
388 && ABS (event->x_root - cell_button->click_x) <= double_click_distance
389 && ABS (event->y_root - cell_button->click_y) <= double_click_distance)
391 psppire_cell_renderer_button_double_click (button, cell_button);
395 cell_button->click_time = 0;
398 if (event->type == GDK_2BUTTON_PRESS)
400 psppire_cell_renderer_button_double_click (button, cell_button);
407 static GtkCellEditable *
408 psppire_cell_renderer_button_start_editing (GtkCellRenderer *cell,
412 GdkRectangle *background_area,
413 GdkRectangle *cell_area,
414 GtkCellRendererState flags)
416 PsppireCellRendererButton *cell_button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
417 gfloat xalign, yalign;
419 gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
420 cell_button->button = g_object_new (PSPPIRE_TYPE_BUTTON_EDITABLE,
421 "label", cell_button->label,
427 g_signal_connect (G_OBJECT (cell_button->button), "clicked",
428 G_CALLBACK (psppire_cell_renderer_button_clicked),
430 g_signal_connect (G_OBJECT (cell_button->button), "button-press-event",
431 G_CALLBACK (psppire_cell_renderer_button_press_event),
434 gtk_widget_show (cell_button->button);
436 if (event != NULL && event->any.type == GDK_BUTTON_RELEASE)
440 cell_button->click_time = event->button.time;
441 cell_button->click_x = event->button.x_root;
442 cell_button->click_y = event->button.y_root;
443 idle_id = g_idle_add (psppire_cell_renderer_button_initial_click,
444 cell_button->button);
445 g_object_set_data (G_OBJECT (cell_button->button), IDLE_ID_STRING,
446 GINT_TO_POINTER (idle_id));
447 g_signal_connect (G_OBJECT (cell_button->button), "destroy",
448 G_CALLBACK (psppire_cell_renderer_button_on_destroy),
453 cell_button->click_time = 0;
454 cell_button->click_x = 0;
455 cell_button->click_y = 0;
458 return GTK_CELL_EDITABLE (cell_button->button);
462 psppire_cell_renderer_button_class_init (PsppireCellRendererButtonClass *class)
464 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
465 GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
467 gobject_class->set_property = psppire_cell_renderer_button_set_property;
468 gobject_class->get_property = psppire_cell_renderer_button_get_property;
469 gobject_class->finalize = psppire_cell_renderer_button_finalize;
470 gobject_class->dispose = psppire_cell_renderer_button_dispose;
472 cell_class->get_size = psppire_cell_renderer_button_get_size;
473 cell_class->render = psppire_cell_renderer_button_render;
474 cell_class->start_editing = psppire_cell_renderer_button_start_editing;
476 g_signal_new ("clicked",
477 G_TYPE_FROM_CLASS (gobject_class),
481 g_cclosure_marshal_VOID__STRING,
485 g_signal_new ("double-clicked",
486 G_TYPE_FROM_CLASS (gobject_class),
490 g_cclosure_marshal_VOID__STRING,
494 g_object_class_install_property (gobject_class,
496 g_param_spec_boolean ("editable",
498 "Whether the button may be clicked.",
502 g_object_class_install_property (gobject_class,
504 g_param_spec_string ("label",
506 "Text to appear in button.",
510 g_object_class_install_property (gobject_class,
512 g_param_spec_boolean ("slash",
514 _("Whether to draw a diagonal slash across the button."),
521 psppire_cell_renderer_button_init (PsppireCellRendererButton *obj)
523 obj->editable = FALSE;
524 obj->label = g_strdup ("");
525 obj->border_width = 0;
533 obj->button_style = NULL;
534 obj->label_style = NULL;
536 obj->style_set_handler = 0;
537 obj->dispose_has_run = FALSE;
541 psppire_cell_renderer_button_finalize (GObject *obj)
543 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
545 g_free (button->label);
549 psppire_cell_renderer_button_dispose (GObject *obj)
551 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
553 if (button->dispose_has_run)
556 button->dispose_has_run = TRUE;
558 /* When called with NULL, as we are doing here, update_style_cache
559 does nothing more than to drop references */
560 update_style_cache (button, NULL);
562 G_OBJECT_CLASS (psppire_cell_renderer_button_parent_class)->dispose (obj);
566 psppire_cell_renderer_button_new (void)
568 return GTK_CELL_RENDERER (g_object_new (PSPPIRE_TYPE_CELL_RENDERER_BUTTON, NULL));
572 psppire_cell_renderer_button_set_slash (PsppireCellRendererButton *button,
575 g_return_if_fail (button != NULL);
576 button->slash = slash;
580 psppire_cell_renderer_button_get_slash (const PsppireCellRendererButton *button)
582 g_return_val_if_fail (button != NULL, FALSE);
583 return button->slash;