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);
296 psppire_cell_renderer_button_focus_out_event (GtkWidget *widget,
300 PsppireCellRendererButton *cell_button = data;
302 g_signal_handlers_disconnect_by_func (widget,
303 psppire_cell_renderer_button_focus_out_event,
305 g_signal_handlers_disconnect_by_func (widget,
306 psppire_cell_renderer_button_clicked,
309 gtk_cell_renderer_stop_editing (GTK_CELL_RENDERER (cell_button), FALSE);
314 #define IDLE_ID_STRING "psppire-cell-renderer-button-idle-id"
317 psppire_cell_renderer_button_initial_click (gpointer data)
319 GtkButton *button = data;
321 gtk_button_clicked (button);
322 g_object_steal_data (G_OBJECT (button), IDLE_ID_STRING);
327 psppire_cell_renderer_button_on_destroy (GObject *object, gpointer user_data)
331 idle_id = GPOINTER_TO_INT (g_object_steal_data (object, IDLE_ID_STRING));
333 g_source_remove (idle_id);
337 psppire_cell_renderer_button_double_click (GtkButton *button,
338 PsppireCellRendererButton *cell_button)
342 if (g_object_get_data (G_OBJECT (button), IDLE_ID_STRING))
343 psppire_cell_renderer_button_initial_click (button);
345 g_object_get (button, "path", &path, NULL);
346 g_signal_emit_by_name (cell_button, "double-clicked", path);
351 psppire_cell_renderer_button_press_event (GtkButton *button,
352 GdkEventButton *event,
355 PsppireCellRendererButton *cell_button = data;
357 if (event->button == 3)
359 /* Allow right-click events to propagate upward in the widget hierarchy.
360 Otherwise right-click menus, that trigger on a button-press-event on
361 the containing PsppSheetView, will pop up if the button is rendered as
362 a facade but not if the button widget exists. */
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 GdkRectangle *background_area,
407 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,
419 "slash", cell_button->slash,
422 g_signal_connect (G_OBJECT (cell_button->button), "focus-out-event",
423 G_CALLBACK (psppire_cell_renderer_button_focus_out_event),
425 g_signal_connect (G_OBJECT (cell_button->button), "clicked",
426 G_CALLBACK (psppire_cell_renderer_button_clicked),
428 g_signal_connect (G_OBJECT (cell_button->button), "button-press-event",
429 G_CALLBACK (psppire_cell_renderer_button_press_event),
432 gtk_widget_show (cell_button->button);
434 if (event != NULL && event->any.type == GDK_BUTTON_RELEASE)
438 cell_button->click_time = event->button.time;
439 cell_button->click_x = event->button.x_root;
440 cell_button->click_y = event->button.y_root;
441 idle_id = g_idle_add (psppire_cell_renderer_button_initial_click,
442 cell_button->button);
443 g_object_set_data (G_OBJECT (cell_button->button), IDLE_ID_STRING,
444 GINT_TO_POINTER (idle_id));
445 g_signal_connect (G_OBJECT (cell_button->button), "destroy",
446 G_CALLBACK (psppire_cell_renderer_button_on_destroy),
451 cell_button->click_time = 0;
452 cell_button->click_x = 0;
453 cell_button->click_y = 0;
456 return GTK_CELL_EDITABLE (cell_button->button);
460 psppire_cell_renderer_button_class_init (PsppireCellRendererButtonClass *class)
462 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
463 GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
465 gobject_class->set_property = psppire_cell_renderer_button_set_property;
466 gobject_class->get_property = psppire_cell_renderer_button_get_property;
467 gobject_class->finalize = psppire_cell_renderer_button_finalize;
468 gobject_class->dispose = psppire_cell_renderer_button_dispose;
470 cell_class->get_size = psppire_cell_renderer_button_get_size;
471 cell_class->render = psppire_cell_renderer_button_render;
472 cell_class->start_editing = psppire_cell_renderer_button_start_editing;
474 g_signal_new ("clicked",
475 G_TYPE_FROM_CLASS (gobject_class),
479 g_cclosure_marshal_VOID__STRING,
483 g_signal_new ("double-clicked",
484 G_TYPE_FROM_CLASS (gobject_class),
488 g_cclosure_marshal_VOID__STRING,
492 g_object_class_install_property (gobject_class,
494 g_param_spec_boolean ("editable",
496 "Whether the button may be clicked.",
500 g_object_class_install_property (gobject_class,
502 g_param_spec_string ("label",
504 "Text to appear in button.",
508 g_object_class_install_property (gobject_class,
510 g_param_spec_boolean ("slash",
512 _("Whether to draw a diagonal slash across the button."),
519 psppire_cell_renderer_button_init (PsppireCellRendererButton *obj)
521 obj->editable = FALSE;
522 obj->label = g_strdup ("");
523 obj->border_width = 0;
531 obj->button_style = NULL;
532 obj->label_style = NULL;
534 obj->style_set_handler = 0;
535 obj->dispose_has_run = FALSE;
539 psppire_cell_renderer_button_finalize (GObject *obj)
541 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
543 g_free (button->label);
547 psppire_cell_renderer_button_dispose (GObject *obj)
549 PsppireCellRendererButton *button = PSPPIRE_CELL_RENDERER_BUTTON (obj);
551 if (button->dispose_has_run)
554 button->dispose_has_run = TRUE;
556 /* When called with NULL, as we are doing here, update_style_cache
557 does nothing more than to drop references */
558 update_style_cache (button, NULL);
560 G_OBJECT_CLASS (psppire_cell_renderer_button_parent_class)->dispose (obj);
564 psppire_cell_renderer_button_new (void)
566 return GTK_CELL_RENDERER (g_object_new (PSPPIRE_TYPE_CELL_RENDERER_BUTTON, NULL));
570 psppire_cell_renderer_button_set_slash (PsppireCellRendererButton *button,
573 g_return_if_fail (button != NULL);
574 button->slash = slash;
578 psppire_cell_renderer_button_get_slash (const PsppireCellRendererButton *button)
580 g_return_val_if_fail (button != NULL, FALSE);
581 return button->slash;