1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2007, 2010, 2011, 2012 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];
43 static void psppire_dialog_buildable_init (GtkBuildableIface *iface);
47 psppire_dialog_get_type (void)
49 static GType dialog_type = 0;
53 static const GTypeInfo dialog_info =
55 sizeof (PsppireDialogClass),
57 NULL, /* base_finalize */
58 (GClassInitFunc) psppire_dialog_class_init,
59 NULL, /* class_finalize */
60 NULL, /* class_data */
61 sizeof (PsppireDialog),
63 (GInstanceInitFunc) psppire_dialog_init,
66 static const GInterfaceInfo buildable_info =
68 (GInterfaceInitFunc) psppire_dialog_buildable_init,
73 dialog_type = g_type_register_static (PSPPIRE_TYPE_WINDOW_BASE,
74 "PsppireDialog", &dialog_info, 0);
76 g_type_add_interface_static (dialog_type,
86 static GObjectClass *parent_class = NULL;
90 psppire_dialog_finalize (GObject *object)
92 PsppireDialog *dialog ;
94 g_return_if_fail (object != NULL);
95 g_return_if_fail (PSPPIRE_IS_DIALOG (object));
97 dialog = PSPPIRE_DIALOG (object);
99 if (G_OBJECT_CLASS (parent_class)->finalize)
100 G_OBJECT_CLASS (parent_class)->finalize (object);
115 psppire_dialog_get_property (GObject *object,
120 PsppireDialog *dialog = PSPPIRE_DIALOG (object);
124 case PROP_ORIENTATION:
126 if ( GTK_IS_VBOX (dialog->box) || GTK_VPANED (dialog->box))
127 g_value_set_enum (value, PSPPIRE_VERTICAL);
128 else if ( GTK_IS_HBOX (dialog->box) || GTK_HPANED (dialog->box))
129 g_value_set_enum (value, PSPPIRE_HORIZONTAL);
130 else if ( GTK_IS_TABLE (dialog->box))
131 g_value_set_enum (value, PSPPIRE_TABULAR);
135 g_value_set_boolean (value, dialog->slidable);
138 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
145 dialog_set_container (PsppireDialog *dialog)
147 if ( dialog->box != NULL)
149 gtk_container_remove (GTK_CONTAINER (dialog), dialog->box);
152 switch (dialog->orientation)
154 case PSPPIRE_HORIZONTAL:
155 if ( dialog->slidable)
156 dialog->box = gtk_hpaned_new();
158 dialog->box = gtk_hbox_new (FALSE, 5);
160 case PSPPIRE_VERTICAL:
161 if ( dialog->slidable)
162 dialog->box = gtk_vpaned_new();
164 dialog->box = gtk_vbox_new (FALSE, 5);
166 case PSPPIRE_TABULAR:
167 dialog->box = gtk_table_new (2, 3, FALSE);
168 g_object_set (dialog->box,
175 gtk_widget_show_all (dialog->box);
176 gtk_container_add (GTK_CONTAINER (dialog), dialog->box);
181 psppire_dialog_set_property (GObject *object,
187 PsppireDialog *dialog = PSPPIRE_DIALOG (object);
192 dialog->slidable = g_value_get_boolean (value);
194 case PROP_ORIENTATION:
195 dialog->orientation = g_value_get_enum (value);
198 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
202 dialog_set_container (dialog);
206 static GParamSpec *orientation_spec ;
209 psppire_dialog_class_init (PsppireDialogClass *class)
211 GObjectClass *object_class = (GObjectClass *) class;
213 GParamSpec *sliding_spec ;
216 g_param_spec_enum ("orientation",
218 "Which way widgets are packed",
219 PSPPIRE_TYPE_ORIENTATION,
220 PSPPIRE_HORIZONTAL /* default value */,
221 G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
224 g_param_spec_boolean ("slidable",
226 "Can the container be sized by the user",
228 G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
230 object_class->set_property = psppire_dialog_set_property;
231 object_class->get_property = psppire_dialog_get_property;
233 g_object_class_install_property (object_class,
238 g_object_class_install_property (object_class,
242 signals [DIALOG_REFRESH] =
243 g_signal_new ("refresh",
244 G_TYPE_FROM_CLASS (class),
248 g_cclosure_marshal_VOID__VOID,
254 g_signal_new ("response",
255 G_TYPE_FROM_CLASS (class),
259 g_cclosure_marshal_VOID__INT,
265 signals [VALIDITY_CHANGED] =
266 g_signal_new ("validity-changed",
267 G_TYPE_FROM_CLASS (class),
271 g_cclosure_marshal_VOID__BOOLEAN,
277 signals [DIALOG_HELP] =
278 g_signal_new ("help",
279 G_TYPE_FROM_CLASS (class),
283 g_cclosure_marshal_VOID__STRING,
289 object_class->finalize = psppire_dialog_finalize;
291 parent_class = g_type_class_peek_parent (class);
298 close_dialog (GtkWidget *w, gpointer data)
300 PsppireDialog *dialog = data;
302 psppire_dialog_close (dialog);
306 psppire_dialog_close (PsppireDialog *dialog)
308 g_main_loop_quit (dialog->loop);
309 gtk_widget_hide (GTK_WIDGET (dialog));
314 delete_event_callback (GtkWidget *w, GdkEvent *e, gpointer data)
316 close_dialog (w, data);
321 psppire_dialog_init (PsppireDialog *dialog)
325 dialog->contents_are_valid = NULL;
326 dialog->validity_data = NULL;
327 dialog->contents_are_acceptable = NULL;
328 dialog->acceptable_data = NULL;
329 dialog->slidable = FALSE;
331 g_value_init (&value, orientation_spec->value_type);
332 g_param_value_set_default (orientation_spec, &value);
334 gtk_window_set_type_hint (GTK_WINDOW (dialog),
335 GDK_WINDOW_TYPE_HINT_DIALOG);
337 g_value_unset (&value);
339 g_signal_connect (dialog, "delete-event",
340 G_CALLBACK (delete_event_callback),
343 gtk_window_set_type_hint (GTK_WINDOW (dialog),
344 GDK_WINDOW_TYPE_HINT_DIALOG);
346 g_object_set (dialog, "icon-name", "pspp", NULL);
351 psppire_dialog_new (void)
353 PsppireDialog *dialog ;
355 dialog = g_object_new (psppire_dialog_get_type (),
358 return GTK_WIDGET (dialog) ;
363 psppire_dialog_notify_change (PsppireDialog *dialog)
365 if ( dialog->contents_are_valid )
367 gboolean valid = dialog->contents_are_valid (dialog->validity_data);
369 g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, valid);
374 /* Descend the widget tree, connecting appropriate signals to the
375 psppire_dialog_notify_change callback */
377 connect_notify_signal (GtkWidget *w, gpointer data)
379 PsppireDialog *dialog = data;
381 if ( PSPPIRE_IS_BUTTONBOX (w))
384 if ( GTK_IS_CONTAINER (w))
386 gtk_container_foreach (GTK_CONTAINER (w),
387 connect_notify_signal,
392 /* It's unfortunate that GTK+ doesn't have a generic
393 "user-modified-state-changed" signal. Instead, we have to try and
394 predict what widgets and signals are likely to exist in our dialogs. */
396 if ( GTK_IS_TOGGLE_BUTTON (w))
398 g_signal_connect_swapped (w, "toggled",
399 G_CALLBACK (psppire_dialog_notify_change),
403 if ( PSPPIRE_IS_SELECTOR (w))
405 g_signal_connect_swapped (w, "selected",
406 G_CALLBACK (psppire_dialog_notify_change),
409 g_signal_connect_swapped (w, "de-selected",
410 G_CALLBACK (psppire_dialog_notify_change),
414 if ( GTK_IS_EDITABLE (w))
416 g_signal_connect_swapped (w, "changed",
417 G_CALLBACK (psppire_dialog_notify_change),
421 if ( GTK_IS_CELL_EDITABLE (w))
423 g_signal_connect_swapped (w, "editing-done",
424 G_CALLBACK (psppire_dialog_notify_change),
428 if ( GTK_IS_TEXT_VIEW (w))
430 GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
432 g_signal_connect_swapped (buffer, "changed",
433 G_CALLBACK (psppire_dialog_notify_change),
437 if ( GTK_IS_TREE_VIEW (w))
440 GtkTreeView *tv = GTK_TREE_VIEW (w);
441 GtkTreeSelection *selection =
442 gtk_tree_view_get_selection (tv);
443 GtkTreeViewColumn *col;
444 GtkTreeModel *model = gtk_tree_view_get_model (tv);
448 g_signal_connect_swapped (model, "row-changed",
449 G_CALLBACK (psppire_dialog_notify_change),
452 g_signal_connect_swapped (model, "row-deleted",
453 G_CALLBACK (psppire_dialog_notify_change),
456 g_signal_connect_swapped (model, "row-inserted",
457 G_CALLBACK (psppire_dialog_notify_change),
461 g_signal_connect_swapped (selection, "changed",
462 G_CALLBACK (psppire_dialog_notify_change),
465 while ((col = gtk_tree_view_get_column (tv, i++)))
467 GList *renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (col));
468 GList *start = renderers;
471 if ( GTK_IS_CELL_RENDERER_TOGGLE (renderers->data))
472 g_signal_connect_swapped (renderers->data, "toggled",
473 G_CALLBACK (psppire_dialog_notify_change), dialog);
474 renderers = renderers->next;
483 psppire_dialog_run (PsppireDialog *dialog)
485 if ( dialog->contents_are_valid != NULL )
486 gtk_container_foreach (GTK_CONTAINER (dialog->box),
487 connect_notify_signal,
490 dialog->loop = g_main_loop_new (NULL, FALSE);
492 gtk_widget_show (GTK_WIDGET (dialog));
494 if ( dialog->contents_are_valid != NULL)
495 g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, FALSE);
497 g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
499 gdk_threads_leave ();
500 g_main_loop_run (dialog->loop);
501 gdk_threads_enter ();
503 g_main_loop_unref (dialog->loop);
505 g_signal_emit (dialog, signals [RESPONSE], 0, dialog->response);
507 return dialog->response;
512 psppire_dialog_reload (PsppireDialog *dialog)
514 g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
519 psppire_dialog_help (PsppireDialog *dialog)
522 g_object_get (dialog, "name", &name, NULL);
526 g_signal_emit (dialog, signals [DIALOG_HELP], 0, name);
531 psppire_orientation_get_type (void)
533 static GType etype = 0;
536 static const GEnumValue values[] =
538 { PSPPIRE_HORIZONTAL, "PSPPIRE_HORIZONTAL", "Horizontal" },
539 { PSPPIRE_VERTICAL, "PSPPIRE_VERTICAL", "Vertical" },
540 { PSPPIRE_TABULAR, "PSPPIRE_TABULAR", "Tabular" },
544 etype = g_enum_register_static
545 (g_intern_static_string ("PsppireOrientation"), values);
552 /* Sets a predicate function that is checked after each change that the user
553 makes to the dialog's state. If the predicate function returns false, then
554 "OK" and other buttons that accept the dialog's settings will be
557 psppire_dialog_set_valid_predicate (PsppireDialog *dialog,
558 ContentsAreValid contents_are_valid,
561 dialog->contents_are_valid = contents_are_valid;
562 dialog->validity_data = data;
565 /* Sets a predicate function that is called after "OK" or another button that
566 accepts the dialog's settings is pushed. If the predicate function returns
567 false, then the button push is ignored. (If the predicate function returns
568 false, then it should take some action to notify the user why the contents
569 are unacceptable, e.g. pop up a dialog box.)
571 An accept predicate is preferred over a validity predicate when the reason
572 why the dialog settings are unacceptable may not be obvious to the user, so
573 that the user needs a helpful message to explain. */
575 psppire_dialog_set_accept_predicate (PsppireDialog *dialog,
576 ContentsAreValid contents_are_acceptable,
579 dialog->contents_are_acceptable = contents_are_acceptable;
580 dialog->acceptable_data = data;
584 psppire_dialog_is_acceptable (const PsppireDialog *dialog)
586 return (dialog->contents_are_acceptable == NULL
587 || dialog->contents_are_acceptable (dialog->acceptable_data));
594 get_internal_child (GtkBuildable *buildable,
596 const gchar *childname)
598 PsppireDialog *dialog = PSPPIRE_DIALOG (buildable);
600 if ( 0 == strcmp (childname, "hbox"))
601 return G_OBJECT (dialog->box);
609 psppire_dialog_buildable_init (GtkBuildableIface *iface)
611 iface->get_internal_child = get_internal_child;