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,
412 g_signal_connect (G_OBJECT (cell_button->button), "clicked",
413 G_CALLBACK (psppire_cell_renderer_button_clicked),
415 g_signal_connect (G_OBJECT (cell_button->button), "button-press-event",
416 G_CALLBACK (psppire_cell_renderer_button_press_event),
419 gtk_widget_show (cell_button->button);
421 if (event != NULL && event->any.type == GDK_BUTTON_RELEASE)
425 cell_button->click_time = event->button.time;
426 cell_button->click_x = event->button.x_root;
427 cell_button->click_y = event->button.y_root;
428 idle_id = g_idle_add (psppire_cell_renderer_button_initial_click,
429 cell_button->button);
430 g_object_set_data (G_OBJECT (cell_button->button), IDLE_ID_STRING,
431 GINT_TO_POINTER (idle_id));
432 g_signal_connect (G_OBJECT (cell_button->button), "destroy",
433 G_CALLBACK (psppire_cell_renderer_button_on_destroy),
438 cell_button->click_time = 0;
439 cell_button->click_x = 0;
440 cell_button->click_y = 0;
443 return GTK_CELL_EDITABLE (cell_button->button);
447 psppire_cell_renderer_button_class_init (PsppireCellRendererButtonClass *class)
449 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
450 GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
452 gobject_class->set_property = psppire_cell_renderer_button_set_property;
453 gobject_class->get_property = psppire_cell_renderer_button_get_property;
454 gobject_class->finalize = psppire_cell_renderer_button_finalize;
455 gobject_class->dispose = psppire_cell_renderer_button_dispose;
457 cell_class->get_size = psppire_cell_renderer_button_get_size;
458 cell_class->render = psppire_cell_renderer_button_render;
459 cell_class->start_editing = psppire_cell_renderer_button_start_editing;
461 g_signal_new ("clicked",
462 G_TYPE_FROM_CLASS (gobject_class),
466 g_cclosure_marshal_VOID__STRING,
470 g_signal_new ("double-clicked",
471 G_TYPE_FROM_CLASS (gobject_class),
475 g_cclosure_marshal_VOID__STRING,
479 g_object_class_install_property (gobject_class,
481 g_param_spec_boolean ("editable",
483 "Whether the button may be clicked.",
487 g_object_class_install_property (gobject_class,
489 g_param_spec_string ("label",
491 "Text to appear in button.",
495 g_object_class_install_property (gobject_class,
497 g_param_spec_boolean ("slash",
499 _("Whether to draw a diagonal slash across the button."),
506 psppire_cell_renderer_button_init (PsppireCellRendererButton *obj)
508 obj->editable = FALSE;
509 obj->label = g_strdup ("");
510 obj->border_width = 0;
518 obj->button_style = NULL;
519 obj->label_style = NULL;
521 obj->style_set_handler = 0;
522 obj->dispose_has_run = FALSE;
526 psppire_cell_renderer_button_finalize (GObject *obj)
528 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
530 g_free (button->label);
534 psppire_cell_renderer_button_dispose (GObject *obj)
536 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
538 if (button->dispose_has_run)
541 button->dispose_has_run = TRUE;
543 /* When called with NULL, as we are doing here, update_style_cache
544 does nothing more than to drop references */
545 update_style_cache (button, NULL);
547 G_OBJECT_CLASS (psppire_cell_renderer_button_parent_class)->dispose (obj);
551 psppire_cell_renderer_button_new (void)
553 return GTK_CELL_RENDERER (g_object_new (PSPPIRE_TYPE_CELL_RENDERER_BUTTON, NULL));
557 psppire_cell_renderer_button_set_slash (PsppireCellRendererButton *button,
560 g_return_if_fail (button != NULL);
561 button->slash = slash;
565 psppire_cell_renderer_button_get_slash (const PsppireCellRendererButton *button)
567 g_return_val_if_fail (button != NULL, FALSE);
568 return button->slash;