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
349 gdk_window_coords_to_parent (event->window,
351 &event->x, &event->y);
352 g_signal_stop_emission_by_name (button, "button-press-event");
356 if (cell_button->click_time != 0)
358 GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (button));
359 GtkSettings *settings = gtk_settings_get_for_screen (screen);
360 gint double_click_distance;
361 gint double_click_time;
363 g_object_get (settings,
364 "gtk-double-click-time", &double_click_time,
365 "gtk-double-click-distance", &double_click_distance,
368 if (event->type == GDK_BUTTON_PRESS
369 && event->button == 1
370 && event->time <= cell_button->click_time + double_click_time
371 && ABS (event->x_root - cell_button->click_x) <= double_click_distance
372 && ABS (event->y_root - cell_button->click_y) <= double_click_distance)
374 psppire_cell_renderer_button_double_click (button, cell_button);
378 cell_button->click_time = 0;
381 if (event->type == GDK_2BUTTON_PRESS)
383 psppire_cell_renderer_button_double_click (button, cell_button);
390 static GtkCellEditable *
391 psppire_cell_renderer_button_start_editing (GtkCellRenderer *cell,
395 GdkRectangle *background_area,
396 GdkRectangle *cell_area,
397 GtkCellRendererState flags)
399 PsppireCellRendererButton *cell_button = PSPPIRE_CELL_RENDERER_BUTTON (cell);
400 gfloat xalign, yalign;
402 gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
403 cell_button->button = g_object_new (PSPPIRE_TYPE_BUTTON_EDITABLE,
404 "label", cell_button->label,
408 "slash", cell_button->slash,
411 g_signal_connect (G_OBJECT (cell_button->button), "clicked",
412 G_CALLBACK (psppire_cell_renderer_button_clicked),
414 g_signal_connect (G_OBJECT (cell_button->button), "button-press-event",
415 G_CALLBACK (psppire_cell_renderer_button_press_event),
418 gtk_widget_show (cell_button->button);
420 if (event != NULL && event->any.type == GDK_BUTTON_RELEASE)
424 cell_button->click_time = event->button.time;
425 cell_button->click_x = event->button.x_root;
426 cell_button->click_y = event->button.y_root;
427 idle_id = g_idle_add (psppire_cell_renderer_button_initial_click,
428 cell_button->button);
429 g_object_set_data (G_OBJECT (cell_button->button), IDLE_ID_STRING,
430 GINT_TO_POINTER (idle_id));
431 g_signal_connect (G_OBJECT (cell_button->button), "destroy",
432 G_CALLBACK (psppire_cell_renderer_button_on_destroy),
437 cell_button->click_time = 0;
438 cell_button->click_x = 0;
439 cell_button->click_y = 0;
442 return GTK_CELL_EDITABLE (cell_button->button);
446 psppire_cell_renderer_button_class_init (PsppireCellRendererButtonClass *class)
448 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
449 GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
451 gobject_class->set_property = psppire_cell_renderer_button_set_property;
452 gobject_class->get_property = psppire_cell_renderer_button_get_property;
453 gobject_class->finalize = psppire_cell_renderer_button_finalize;
454 gobject_class->dispose = psppire_cell_renderer_button_dispose;
456 cell_class->get_size = psppire_cell_renderer_button_get_size;
457 cell_class->render = psppire_cell_renderer_button_render;
458 cell_class->start_editing = psppire_cell_renderer_button_start_editing;
460 g_signal_new ("clicked",
461 G_TYPE_FROM_CLASS (gobject_class),
465 g_cclosure_marshal_VOID__STRING,
469 g_signal_new ("double-clicked",
470 G_TYPE_FROM_CLASS (gobject_class),
474 g_cclosure_marshal_VOID__STRING,
478 g_object_class_install_property (gobject_class,
480 g_param_spec_boolean ("editable",
482 "Whether the button may be clicked.",
486 g_object_class_install_property (gobject_class,
488 g_param_spec_string ("label",
490 "Text to appear in button.",
494 g_object_class_install_property (gobject_class,
496 g_param_spec_boolean ("slash",
498 _("Whether to draw a diagonal slash across the button."),
505 psppire_cell_renderer_button_init (PsppireCellRendererButton *obj)
507 obj->editable = FALSE;
508 obj->label = g_strdup ("");
509 obj->border_width = 0;
517 obj->button_style = NULL;
518 obj->label_style = NULL;
520 obj->style_set_handler = 0;
521 obj->dispose_has_run = FALSE;
525 psppire_cell_renderer_button_finalize (GObject *obj)
527 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
529 g_free (button->label);
533 psppire_cell_renderer_button_dispose (GObject *obj)
535 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
537 if (button->dispose_has_run)
540 button->dispose_has_run = TRUE;
542 /* When called with NULL, as we are doing here, update_style_cache
543 does nothing more than to drop references */
544 update_style_cache (button, NULL);
546 G_OBJECT_CLASS (psppire_cell_renderer_button_parent_class)->dispose (obj);
550 psppire_cell_renderer_button_new (void)
552 return GTK_CELL_RENDERER (g_object_new (PSPPIRE_TYPE_CELL_RENDERER_BUTTON, NULL));
556 psppire_cell_renderer_button_set_slash (PsppireCellRendererButton *button,
559 g_return_if_fail (button != NULL);
560 button->slash = slash;
564 psppire_cell_renderer_button_get_slash (const PsppireCellRendererButton *button)
566 g_return_val_if_fail (button != NULL, FALSE);
567 return button->slash;