1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2007, 2010, 2011, 2012, 2015 Free Software Foundation
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/>. */
21 #include "psppire-dialog.h"
22 #include "psppire-buttonbox.h"
23 #include "psppire-selector.h"
25 #include "builder-wrapper.h"
26 #include "help-menu.h"
28 #include "psppire-window-base.h"
30 static void psppire_dialog_class_init (PsppireDialogClass *);
31 static void psppire_dialog_init (PsppireDialog *);
40 static guint signals [n_SIGNALS];
42 static GObjectClass *parent_class = NULL;
45 static void psppire_dialog_buildable_init (GtkBuildableIface *iface);
48 psppire_dialog_finalize (GObject *object)
50 PsppireDialog *dialog = PSPPIRE_DIALOG (object);
52 g_free (dialog->help_page);
54 if (G_OBJECT_CLASS (parent_class)->finalize)
55 G_OBJECT_CLASS (parent_class)->finalize (object);
59 psppire_dialog_base_init (PsppireDialogClass *class)
61 GObjectClass *object_class = G_OBJECT_CLASS (class);
63 object_class->finalize = psppire_dialog_finalize;
67 psppire_dialog_get_type (void)
69 static GType dialog_type = 0;
73 static const GTypeInfo dialog_info =
75 sizeof (PsppireDialogClass),
76 (GBaseInitFunc) psppire_dialog_base_init,
77 NULL, /* base_finalize */
78 (GClassInitFunc) psppire_dialog_class_init,
79 NULL, /* class_finalize */
80 NULL, /* class_data */
81 sizeof (PsppireDialog),
83 (GInstanceInitFunc) psppire_dialog_init,
86 static const GInterfaceInfo buildable_info =
88 (GInterfaceInitFunc) psppire_dialog_buildable_init,
93 dialog_type = g_type_register_static (PSPPIRE_TYPE_WINDOW_BASE,
94 "PsppireDialog", &dialog_info, 0);
96 g_type_add_interface_static (dialog_type,
117 psppire_dialog_get_property (GObject *object,
122 PsppireDialog *dialog = PSPPIRE_DIALOG (object);
126 case PROP_ORIENTATION:
128 if ( GTK_IS_VBOX (dialog->box) || GTK_VPANED (dialog->box))
129 g_value_set_enum (value, PSPPIRE_VERTICAL);
130 else if ( GTK_IS_HBOX (dialog->box) || GTK_HPANED (dialog->box))
131 g_value_set_enum (value, PSPPIRE_HORIZONTAL);
132 else if ( GTK_IS_TABLE (dialog->box))
133 g_value_set_enum (value, PSPPIRE_TABULAR);
137 g_value_set_boolean (value, dialog->slidable);
140 g_value_set_string (value, dialog->help_page);
143 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
150 dialog_set_container (PsppireDialog *dialog)
152 if ( dialog->box != NULL)
154 gtk_container_remove (GTK_CONTAINER (dialog), dialog->box);
157 switch (dialog->orientation)
159 case PSPPIRE_HORIZONTAL:
160 if ( dialog->slidable)
161 dialog->box = gtk_hpaned_new();
163 dialog->box = gtk_hbox_new (FALSE, 5);
165 case PSPPIRE_VERTICAL:
166 if ( dialog->slidable)
167 dialog->box = gtk_vpaned_new();
169 dialog->box = gtk_vbox_new (FALSE, 5);
171 case PSPPIRE_TABULAR:
172 dialog->box = gtk_table_new (2, 3, FALSE);
173 g_object_set (dialog->box,
180 gtk_widget_show_all (dialog->box);
181 gtk_container_add (GTK_CONTAINER (dialog), dialog->box);
186 psppire_dialog_set_property (GObject *object,
192 PsppireDialog *dialog = PSPPIRE_DIALOG (object);
197 dialog->slidable = g_value_get_boolean (value);
199 case PROP_ORIENTATION:
200 dialog->orientation = g_value_get_enum (value);
203 dialog->help_page = g_value_dup_string (value);
206 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
210 dialog_set_container (dialog);
214 static GParamSpec *orientation_spec ;
217 psppire_dialog_class_init (PsppireDialogClass *class)
219 GObjectClass *object_class = (GObjectClass *) class;
221 GParamSpec *sliding_spec ;
222 GParamSpec *help_page_spec ;
225 g_param_spec_string ("help-page",
227 "The section of the manual to load when the Help button is clicked",
232 g_param_spec_enum ("orientation",
234 "Which way widgets are packed",
235 PSPPIRE_TYPE_ORIENTATION,
236 PSPPIRE_HORIZONTAL /* default value */,
237 G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
240 g_param_spec_boolean ("slidable",
242 "Can the container be sized by the user",
244 G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
246 object_class->set_property = psppire_dialog_set_property;
247 object_class->get_property = psppire_dialog_get_property;
249 g_object_class_install_property (object_class,
254 g_object_class_install_property (object_class,
258 g_object_class_install_property (object_class,
263 signals [DIALOG_REFRESH] =
264 g_signal_new ("refresh",
265 G_TYPE_FROM_CLASS (class),
269 g_cclosure_marshal_VOID__VOID,
275 g_signal_new ("response",
276 G_TYPE_FROM_CLASS (class),
280 g_cclosure_marshal_VOID__INT,
286 signals [VALIDITY_CHANGED] =
287 g_signal_new ("validity-changed",
288 G_TYPE_FROM_CLASS (class),
292 g_cclosure_marshal_VOID__BOOLEAN,
298 signals [DIALOG_HELP] =
299 g_signal_new ("help",
300 G_TYPE_FROM_CLASS (class),
304 g_cclosure_marshal_VOID__STRING,
310 parent_class = g_type_class_peek_parent (class);
317 close_dialog (GtkWidget *w, gpointer data)
319 PsppireDialog *dialog = data;
321 psppire_dialog_close (dialog);
325 psppire_dialog_close (PsppireDialog *dialog)
327 g_main_loop_quit (dialog->loop);
328 gtk_widget_hide (GTK_WIDGET (dialog));
333 delete_event_callback (GtkWidget *w, GdkEvent *e, gpointer data)
335 close_dialog (w, data);
340 psppire_dialog_init (PsppireDialog *dialog)
344 dialog->contents_are_valid = NULL;
345 dialog->validity_data = NULL;
346 dialog->contents_are_acceptable = NULL;
347 dialog->acceptable_data = NULL;
348 dialog->slidable = FALSE;
349 dialog->help_page = NULL;
351 g_value_init (&value, orientation_spec->value_type);
352 g_param_value_set_default (orientation_spec, &value);
354 gtk_window_set_type_hint (GTK_WINDOW (dialog),
355 GDK_WINDOW_TYPE_HINT_DIALOG);
357 g_value_unset (&value);
359 g_signal_connect (dialog, "delete-event",
360 G_CALLBACK (delete_event_callback),
363 gtk_window_set_type_hint (GTK_WINDOW (dialog),
364 GDK_WINDOW_TYPE_HINT_DIALOG);
366 g_object_set (dialog, "icon-name", "pspp", NULL);
371 psppire_dialog_new (void)
373 PsppireDialog *dialog ;
375 dialog = g_object_new (psppire_dialog_get_type (),
378 return GTK_WIDGET (dialog) ;
383 psppire_dialog_notify_change (PsppireDialog *dialog)
385 if ( dialog->contents_are_valid )
387 gboolean valid = dialog->contents_are_valid (dialog->validity_data);
389 g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, valid);
395 remove_notify_handlers (PsppireDialog *dialog, GObject *sel)
397 g_signal_handlers_disconnect_by_data (sel, dialog);
401 /* Descend the widget tree, connecting appropriate signals to the
402 psppire_dialog_notify_change callback */
404 connect_notify_signal (GtkWidget *w, gpointer data)
406 PsppireDialog *dialog = data;
408 if ( PSPPIRE_IS_BUTTONBOX (w))
411 if ( GTK_IS_CONTAINER (w))
413 gtk_container_foreach (GTK_CONTAINER (w),
414 connect_notify_signal,
419 /* It's unfortunate that GTK+ doesn't have a generic
420 "user-modified-state-changed" signal. Instead, we have to try and
421 predict what widgets and signals are likely to exist in our dialogs. */
423 if ( GTK_IS_TOGGLE_BUTTON (w))
425 g_signal_connect_swapped (w, "toggled",
426 G_CALLBACK (psppire_dialog_notify_change),
430 if ( PSPPIRE_IS_SELECTOR (w))
432 g_signal_connect_swapped (w, "selected",
433 G_CALLBACK (psppire_dialog_notify_change),
436 g_signal_connect_swapped (w, "de-selected",
437 G_CALLBACK (psppire_dialog_notify_change),
441 if ( GTK_IS_EDITABLE (w))
443 g_signal_connect_swapped (w, "changed",
444 G_CALLBACK (psppire_dialog_notify_change),
448 if ( GTK_IS_CELL_EDITABLE (w))
450 g_signal_connect_swapped (w, "editing-done",
451 G_CALLBACK (psppire_dialog_notify_change),
455 if ( GTK_IS_TEXT_VIEW (w))
457 GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
459 g_signal_connect_swapped (buffer, "changed",
460 G_CALLBACK (psppire_dialog_notify_change),
464 if ( GTK_IS_TREE_VIEW (w))
467 GtkTreeView *tv = GTK_TREE_VIEW (w);
468 GtkTreeSelection *selection =
469 gtk_tree_view_get_selection (tv);
470 GtkTreeViewColumn *col;
471 GtkTreeModel *model = gtk_tree_view_get_model (tv);
475 g_signal_connect_swapped (model, "row-changed",
476 G_CALLBACK (psppire_dialog_notify_change),
479 g_signal_connect_swapped (model, "row-deleted",
480 G_CALLBACK (psppire_dialog_notify_change),
483 g_signal_connect_swapped (model, "row-inserted",
484 G_CALLBACK (psppire_dialog_notify_change),
487 g_signal_connect (dialog, "destroy", G_CALLBACK (remove_notify_handlers),
491 g_signal_connect_swapped (selection, "changed",
492 G_CALLBACK (psppire_dialog_notify_change),
495 while ((col = gtk_tree_view_get_column (tv, i++)))
497 GList *renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (col));
498 GList *start = renderers;
501 if ( GTK_IS_CELL_RENDERER_TOGGLE (renderers->data))
502 g_signal_connect_swapped (renderers->data, "toggled",
503 G_CALLBACK (psppire_dialog_notify_change), dialog);
504 renderers = renderers->next;
513 psppire_dialog_run (PsppireDialog *dialog)
516 g_object_get (dialog, "title", &title, NULL);
519 g_warning ("PsppireDialog %s has no title", gtk_widget_get_name (GTK_WIDGET (dialog)));
521 if ( dialog->contents_are_valid != NULL )
522 gtk_container_foreach (GTK_CONTAINER (dialog->box),
523 connect_notify_signal,
526 dialog->loop = g_main_loop_new (NULL, FALSE);
528 gtk_widget_show (GTK_WIDGET (dialog));
530 if ( dialog->contents_are_valid != NULL)
531 g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, FALSE);
533 g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
535 gdk_threads_leave ();
536 g_main_loop_run (dialog->loop);
537 gdk_threads_enter ();
539 g_main_loop_unref (dialog->loop);
541 g_signal_emit (dialog, signals [RESPONSE], 0, dialog->response);
543 return dialog->response;
548 psppire_dialog_reload (PsppireDialog *dialog)
550 g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
555 psppire_dialog_help (PsppireDialog *dialog)
557 const char *page = NULL;
559 g_object_get (dialog, "help-page", &page, NULL);
563 g_signal_emit (dialog, signals [DIALOG_HELP], 0, page);
568 psppire_orientation_get_type (void)
570 static GType etype = 0;
573 static const GEnumValue values[] =
575 { PSPPIRE_HORIZONTAL, "PSPPIRE_HORIZONTAL", "Horizontal" },
576 { PSPPIRE_VERTICAL, "PSPPIRE_VERTICAL", "Vertical" },
577 { PSPPIRE_TABULAR, "PSPPIRE_TABULAR", "Tabular" },
581 etype = g_enum_register_static
582 (g_intern_static_string ("PsppireOrientation"), values);
589 /* Sets a predicate function that is checked after each change that the user
590 makes to the dialog's state. If the predicate function returns false, then
591 "OK" and other buttons that accept the dialog's settings will be
594 psppire_dialog_set_valid_predicate (PsppireDialog *dialog,
595 ContentsAreValid contents_are_valid,
598 dialog->contents_are_valid = contents_are_valid;
599 dialog->validity_data = data;
602 /* Sets a predicate function that is called after "OK" or another button that
603 accepts the dialog's settings is pushed. If the predicate function returns
604 false, then the button push is ignored. (If the predicate function returns
605 false, then it should take some action to notify the user why the contents
606 are unacceptable, e.g. pop up a dialog box.)
608 An accept predicate is preferred over a validity predicate when the reason
609 why the dialog settings are unacceptable may not be obvious to the user, so
610 that the user needs a helpful message to explain. */
612 psppire_dialog_set_accept_predicate (PsppireDialog *dialog,
613 ContentsAreValid contents_are_acceptable,
616 dialog->contents_are_acceptable = contents_are_acceptable;
617 dialog->acceptable_data = data;
621 psppire_dialog_is_acceptable (const PsppireDialog *dialog)
623 return (dialog->contents_are_acceptable == NULL
624 || dialog->contents_are_acceptable (dialog->acceptable_data));
631 get_internal_child (GtkBuildable *buildable,
633 const gchar *childname)
635 PsppireDialog *dialog = PSPPIRE_DIALOG (buildable);
637 if ( 0 == strcmp (childname, "hbox"))
638 return G_OBJECT (dialog->box);
646 psppire_dialog_buildable_init (GtkBuildableIface *iface)
648 iface->get_internal_child = get_internal_child;