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);
222 gdk_draw_line (window, button->button_style->black_gc,
224 cell_area->y + cell_area->height,
225 cell_area->x + cell_area->width,
230 psppire_cell_renderer_button_get_size (GtkCellRenderer *cell,
232 GdkRectangle *cell_area,
238 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
240 update_style_cache (button, widget);
241 if (cell_area != NULL)
243 /* The caller is really asking for the placement of the focus rectangle.
244 The focus rectangle should surround the whole label area, so calculate
248 facade_button_get_focus_inset (button->border_width, widget,
249 button->button_style, &inset);
252 *x_offset = inset.left;
254 *y_offset = inset.top;
256 *width = MAX (1, cell_area->width - inset.left - inset.right);
258 *height = MAX (1, cell_area->height - inset.top - inset.bottom);
262 /* The caller is asking for the preferred size of the cell. */
263 GtkRequisition label_req;
264 GtkRequisition request;
266 facade_label_get_size_request (button->xpad, button->ypad,
267 widget, button->label, &label_req);
268 facade_button_get_size_request (button->border_width, widget,
269 button->button_style, &label_req,
277 *width = request.width;
279 *height = request.height;
284 psppire_cell_renderer_button_clicked (GtkButton *button,
287 PsppireCellRendererButton *cell_button = data;
290 g_object_get (button, "path", &path, NULL);
291 g_signal_emit_by_name (cell_button, "clicked", path);
295 #define IDLE_ID_STRING "psppire-cell-renderer-button-idle-id"
298 psppire_cell_renderer_button_initial_click (gpointer data)
300 GtkButton *button = data;
302 g_object_steal_data (G_OBJECT (button), IDLE_ID_STRING);
303 gtk_button_clicked (button);
308 psppire_cell_renderer_button_on_destroy (GObject *object, gpointer user_data)
312 idle_id = GPOINTER_TO_INT (g_object_steal_data (object, IDLE_ID_STRING));
314 g_source_remove (idle_id);
318 psppire_cell_renderer_button_double_click (GtkButton *button,
319 PsppireCellRendererButton *cell_button)
323 if (g_object_get_data (G_OBJECT (button), IDLE_ID_STRING))
324 psppire_cell_renderer_button_initial_click (button);
326 g_object_get (button, "path", &path, NULL);
327 g_signal_emit_by_name (cell_button, "double-clicked", path);
332 psppire_cell_renderer_button_press_event (GtkButton *button,
333 GdkEventButton *event,
336 PsppireCellRendererButton *cell_button = data;
338 if (event->button == 3)
340 /* Allow right-click events to propagate upward in the widget hierarchy.
341 Otherwise right-click menus, that trigger on a button-press-event on
342 the containing PsppSheetView, will pop up if the button is rendered as
343 a facade but not if the button widget exists.
345 We have to translate the event's data by hand to be relative to the
346 parent window, because the normal GObject signal propagation mechanism
347 won't do it for us. (This might be a hint that we're doing this
351 gdk_window_get_position (event->window, &x, &y);
354 g_signal_stop_emission_by_name (button, "button-press-event");
358 if (cell_button->click_time != 0)
360 GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (button));
361 GtkSettings *settings = gtk_settings_get_for_screen (screen);
362 gint double_click_distance;
363 gint double_click_time;
365 g_object_get (settings,
366 "gtk-double-click-time", &double_click_time,
367 "gtk-double-click-distance", &double_click_distance,
370 if (event->type == GDK_BUTTON_PRESS
371 && event->button == 1
372 && event->time <= cell_button->click_time + double_click_time
373 && ABS (event->x_root - cell_button->click_x) <= double_click_distance
374 && ABS (event->y_root - cell_button->click_y) <= double_click_distance)
376 psppire_cell_renderer_button_double_click (button, cell_button);
380 cell_button->click_time = 0;
383 if (event->type == GDK_2BUTTON_PRESS)
385 psppire_cell_renderer_button_double_click (button, cell_button);
392 static GtkCellEditable *
393 psppire_cell_renderer_button_start_editing (GtkCellRenderer *cell,
397 GdkRectangle *background_area,
398 GdkRectangle *cell_area,
399 GtkCellRendererState flags)
401 PsppireCellRendererButton *cell_button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
402 gfloat xalign, yalign;
404 gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
405 cell_button->button = g_object_new (PSPPIRE_TYPE_BUTTON_EDITABLE,
406 "label", cell_button->label,
410 "slash", cell_button->slash,
413 g_signal_connect (G_OBJECT (cell_button->button), "clicked",
414 G_CALLBACK (psppire_cell_renderer_button_clicked),
416 g_signal_connect (G_OBJECT (cell_button->button), "button-press-event",
417 G_CALLBACK (psppire_cell_renderer_button_press_event),
420 gtk_widget_show (cell_button->button);
422 if (event != NULL && event->any.type == GDK_BUTTON_RELEASE)
426 cell_button->click_time = event->button.time;
427 cell_button->click_x = event->button.x_root;
428 cell_button->click_y = event->button.y_root;
429 idle_id = g_idle_add (psppire_cell_renderer_button_initial_click,
430 cell_button->button);
431 g_object_set_data (G_OBJECT (cell_button->button), IDLE_ID_STRING,
432 GINT_TO_POINTER (idle_id));
433 g_signal_connect (G_OBJECT (cell_button->button), "destroy",
434 G_CALLBACK (psppire_cell_renderer_button_on_destroy),
439 cell_button->click_time = 0;
440 cell_button->click_x = 0;
441 cell_button->click_y = 0;
444 return GTK_CELL_EDITABLE (cell_button->button);
448 psppire_cell_renderer_button_class_init (PsppireCellRendererButtonClass *class)
450 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
451 GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
453 gobject_class->set_property = psppire_cell_renderer_button_set_property;
454 gobject_class->get_property = psppire_cell_renderer_button_get_property;
455 gobject_class->finalize = psppire_cell_renderer_button_finalize;
456 gobject_class->dispose = psppire_cell_renderer_button_dispose;
458 cell_class->get_size = psppire_cell_renderer_button_get_size;
459 cell_class->render = psppire_cell_renderer_button_render;
460 cell_class->start_editing = psppire_cell_renderer_button_start_editing;
462 g_signal_new ("clicked",
463 G_TYPE_FROM_CLASS (gobject_class),
467 g_cclosure_marshal_VOID__STRING,
471 g_signal_new ("double-clicked",
472 G_TYPE_FROM_CLASS (gobject_class),
476 g_cclosure_marshal_VOID__STRING,
480 g_object_class_install_property (gobject_class,
482 g_param_spec_boolean ("editable",
484 "Whether the button may be clicked.",
488 g_object_class_install_property (gobject_class,
490 g_param_spec_string ("label",
492 "Text to appear in button.",
496 g_object_class_install_property (gobject_class,
498 g_param_spec_boolean ("slash",
500 _("Whether to draw a diagonal slash across the button."),
507 psppire_cell_renderer_button_init (PsppireCellRendererButton *obj)
509 obj->editable = FALSE;
510 obj->label = g_strdup ("");
511 obj->border_width = 0;
519 obj->button_style = NULL;
520 obj->label_style = NULL;
522 obj->style_set_handler = 0;
523 obj->dispose_has_run = FALSE;
527 psppire_cell_renderer_button_finalize (GObject *obj)
529 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
531 g_free (button->label);
535 psppire_cell_renderer_button_dispose (GObject *obj)
537 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
539 if (button->dispose_has_run)
542 button->dispose_has_run = TRUE;
544 /* When called with NULL, as we are doing here, update_style_cache
545 does nothing more than to drop references */
546 update_style_cache (button, NULL);
548 G_OBJECT_CLASS (psppire_cell_renderer_button_parent_class)->dispose (obj);
552 psppire_cell_renderer_button_new (void)
554 return GTK_CELL_RENDERER (g_object_new (PSPPIRE_TYPE_CELL_RENDERER_BUTTON, NULL));
558 psppire_cell_renderer_button_set_slash (PsppireCellRendererButton *button,
561 g_return_if_fail (button != NULL);
562 button->slash = slash;
566 psppire_cell_renderer_button_get_slash (const PsppireCellRendererButton *button)
568 g_return_val_if_fail (button != NULL, FALSE);
569 return button->slash;