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;
99 psppire_dialog_get_property (GObject *object,
104 PsppireDialog *dialog = PSPPIRE_DIALOG (object);
108 case PROP_ORIENTATION:
110 if ( GTK_IS_VBOX (dialog->box) || GTK_VPANED (dialog->box))
111 g_value_set_enum (value, PSPPIRE_VERTICAL);
112 else if ( GTK_IS_HBOX (dialog->box) || GTK_HPANED (dialog->box))
113 g_value_set_enum (value, PSPPIRE_HORIZONTAL);
114 else if ( GTK_IS_TABLE (dialog->box))
115 g_value_set_enum (value, PSPPIRE_TABULAR);
119 g_value_set_boolean (value, dialog->slidable);
122 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
129 dialog_set_container (PsppireDialog *dialog)
131 if ( dialog->box != NULL)
133 gtk_container_remove (GTK_CONTAINER (dialog), dialog->box);
136 switch (dialog->orientation)
138 case PSPPIRE_HORIZONTAL:
139 if ( dialog->slidable)
140 dialog->box = gtk_hpaned_new();
142 dialog->box = gtk_hbox_new (FALSE, 5);
144 case PSPPIRE_VERTICAL:
145 if ( dialog->slidable)
146 dialog->box = gtk_vpaned_new();
148 dialog->box = gtk_vbox_new (FALSE, 5);
150 case PSPPIRE_TABULAR:
151 dialog->box = gtk_table_new (2, 3, FALSE);
152 g_object_set (dialog->box,
159 gtk_widget_show_all (dialog->box);
160 gtk_container_add (GTK_CONTAINER (dialog), dialog->box);
165 psppire_dialog_set_property (GObject *object,
171 PsppireDialog *dialog = PSPPIRE_DIALOG (object);
176 dialog->slidable = g_value_get_boolean (value);
178 case PROP_ORIENTATION:
179 dialog->orientation = g_value_get_enum (value);
182 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
186 dialog_set_container (dialog);
190 static GParamSpec *orientation_spec ;
193 psppire_dialog_class_init (PsppireDialogClass *class)
195 GObjectClass *object_class = (GObjectClass *) class;
197 GParamSpec *sliding_spec ;
200 g_param_spec_enum ("orientation",
202 "Which way widgets are packed",
203 PSPPIRE_TYPE_ORIENTATION,
204 PSPPIRE_HORIZONTAL /* default value */,
205 G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
208 g_param_spec_boolean ("slidable",
210 "Can the container be sized by the user",
212 G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
214 object_class->set_property = psppire_dialog_set_property;
215 object_class->get_property = psppire_dialog_get_property;
217 g_object_class_install_property (object_class,
222 g_object_class_install_property (object_class,
226 signals [DIALOG_REFRESH] =
227 g_signal_new ("refresh",
228 G_TYPE_FROM_CLASS (class),
232 g_cclosure_marshal_VOID__VOID,
238 g_signal_new ("response",
239 G_TYPE_FROM_CLASS (class),
243 g_cclosure_marshal_VOID__INT,
249 signals [VALIDITY_CHANGED] =
250 g_signal_new ("validity-changed",
251 G_TYPE_FROM_CLASS (class),
255 g_cclosure_marshal_VOID__BOOLEAN,
261 signals [DIALOG_HELP] =
262 g_signal_new ("help",
263 G_TYPE_FROM_CLASS (class),
267 g_cclosure_marshal_VOID__STRING,
273 parent_class = g_type_class_peek_parent (class);
280 close_dialog (GtkWidget *w, gpointer data)
282 PsppireDialog *dialog = data;
284 psppire_dialog_close (dialog);
288 psppire_dialog_close (PsppireDialog *dialog)
290 g_main_loop_quit (dialog->loop);
291 gtk_widget_hide (GTK_WIDGET (dialog));
296 delete_event_callback (GtkWidget *w, GdkEvent *e, gpointer data)
298 close_dialog (w, data);
303 psppire_dialog_init (PsppireDialog *dialog)
307 dialog->contents_are_valid = NULL;
308 dialog->validity_data = NULL;
309 dialog->contents_are_acceptable = NULL;
310 dialog->acceptable_data = NULL;
311 dialog->slidable = FALSE;
313 g_value_init (&value, orientation_spec->value_type);
314 g_param_value_set_default (orientation_spec, &value);
316 gtk_window_set_type_hint (GTK_WINDOW (dialog),
317 GDK_WINDOW_TYPE_HINT_DIALOG);
319 g_value_unset (&value);
321 g_signal_connect (dialog, "delete-event",
322 G_CALLBACK (delete_event_callback),
325 gtk_window_set_type_hint (GTK_WINDOW (dialog),
326 GDK_WINDOW_TYPE_HINT_DIALOG);
328 g_object_set (dialog, "icon-name", "pspp", NULL);
333 psppire_dialog_new (void)
335 PsppireDialog *dialog ;
337 dialog = g_object_new (psppire_dialog_get_type (),
340 return GTK_WIDGET (dialog) ;
345 psppire_dialog_notify_change (PsppireDialog *dialog)
347 if ( dialog->contents_are_valid )
349 gboolean valid = dialog->contents_are_valid (dialog->validity_data);
351 g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, valid);
357 remove_notify_handlers (PsppireDialog *dialog, GObject *sel)
359 g_signal_handlers_disconnect_by_data (sel, dialog);
363 /* Descend the widget tree, connecting appropriate signals to the
364 psppire_dialog_notify_change callback */
366 connect_notify_signal (GtkWidget *w, gpointer data)
368 PsppireDialog *dialog = data;
370 if ( PSPPIRE_IS_BUTTONBOX (w))
373 if ( GTK_IS_CONTAINER (w))
375 gtk_container_foreach (GTK_CONTAINER (w),
376 connect_notify_signal,
381 /* It's unfortunate that GTK+ doesn't have a generic
382 "user-modified-state-changed" signal. Instead, we have to try and
383 predict what widgets and signals are likely to exist in our dialogs. */
385 if ( GTK_IS_TOGGLE_BUTTON (w))
387 g_signal_connect_swapped (w, "toggled",
388 G_CALLBACK (psppire_dialog_notify_change),
392 if ( PSPPIRE_IS_SELECTOR (w))
394 g_signal_connect_swapped (w, "selected",
395 G_CALLBACK (psppire_dialog_notify_change),
398 g_signal_connect_swapped (w, "de-selected",
399 G_CALLBACK (psppire_dialog_notify_change),
403 if ( GTK_IS_EDITABLE (w))
405 g_signal_connect_swapped (w, "changed",
406 G_CALLBACK (psppire_dialog_notify_change),
410 if ( GTK_IS_CELL_EDITABLE (w))
412 g_signal_connect_swapped (w, "editing-done",
413 G_CALLBACK (psppire_dialog_notify_change),
417 if ( GTK_IS_TEXT_VIEW (w))
419 GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
421 g_signal_connect_swapped (buffer, "changed",
422 G_CALLBACK (psppire_dialog_notify_change),
426 if ( GTK_IS_TREE_VIEW (w))
429 GtkTreeView *tv = GTK_TREE_VIEW (w);
430 GtkTreeSelection *selection =
431 gtk_tree_view_get_selection (tv);
432 GtkTreeViewColumn *col;
433 GtkTreeModel *model = gtk_tree_view_get_model (tv);
437 g_signal_connect_swapped (model, "row-changed",
438 G_CALLBACK (psppire_dialog_notify_change),
441 g_signal_connect_swapped (model, "row-deleted",
442 G_CALLBACK (psppire_dialog_notify_change),
445 g_signal_connect_swapped (model, "row-inserted",
446 G_CALLBACK (psppire_dialog_notify_change),
449 g_signal_connect (dialog, "destroy", G_CALLBACK (remove_notify_handlers),
453 g_signal_connect_swapped (selection, "changed",
454 G_CALLBACK (psppire_dialog_notify_change),
457 while ((col = gtk_tree_view_get_column (tv, i++)))
459 GList *renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (col));
460 GList *start = renderers;
463 if ( GTK_IS_CELL_RENDERER_TOGGLE (renderers->data))
464 g_signal_connect_swapped (renderers->data, "toggled",
465 G_CALLBACK (psppire_dialog_notify_change), dialog);
466 renderers = renderers->next;
475 psppire_dialog_run (PsppireDialog *dialog)
478 g_object_get (dialog, "title", &title, NULL);
481 g_warning ("PsppireDialog %s has no title", gtk_widget_get_name (GTK_WIDGET (dialog)));
483 if ( dialog->contents_are_valid != NULL )
484 gtk_container_foreach (GTK_CONTAINER (dialog->box),
485 connect_notify_signal,
488 dialog->loop = g_main_loop_new (NULL, FALSE);
490 gtk_widget_show (GTK_WIDGET (dialog));
492 if ( dialog->contents_are_valid != NULL)
493 g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, FALSE);
495 g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
497 gdk_threads_leave ();
498 g_main_loop_run (dialog->loop);
499 gdk_threads_enter ();
501 g_main_loop_unref (dialog->loop);
503 g_signal_emit (dialog, signals [RESPONSE], 0, dialog->response);
505 return dialog->response;
510 psppire_dialog_reload (PsppireDialog *dialog)
512 g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
517 psppire_dialog_help (PsppireDialog *dialog)
520 g_object_get (dialog, "name", &name, NULL);
524 g_signal_emit (dialog, signals [DIALOG_HELP], 0, name);
529 psppire_orientation_get_type (void)
531 static GType etype = 0;
534 static const GEnumValue values[] =
536 { PSPPIRE_HORIZONTAL, "PSPPIRE_HORIZONTAL", "Horizontal" },
537 { PSPPIRE_VERTICAL, "PSPPIRE_VERTICAL", "Vertical" },
538 { PSPPIRE_TABULAR, "PSPPIRE_TABULAR", "Tabular" },
542 etype = g_enum_register_static
543 (g_intern_static_string ("PsppireOrientation"), values);
550 /* Sets a predicate function that is checked after each change that the user
551 makes to the dialog's state. If the predicate function returns false, then
552 "OK" and other buttons that accept the dialog's settings will be
555 psppire_dialog_set_valid_predicate (PsppireDialog *dialog,
556 ContentsAreValid contents_are_valid,
559 dialog->contents_are_valid = contents_are_valid;
560 dialog->validity_data = data;
563 /* Sets a predicate function that is called after "OK" or another button that
564 accepts the dialog's settings is pushed. If the predicate function returns
565 false, then the button push is ignored. (If the predicate function returns
566 false, then it should take some action to notify the user why the contents
567 are unacceptable, e.g. pop up a dialog box.)
569 An accept predicate is preferred over a validity predicate when the reason
570 why the dialog settings are unacceptable may not be obvious to the user, so
571 that the user needs a helpful message to explain. */
573 psppire_dialog_set_accept_predicate (PsppireDialog *dialog,
574 ContentsAreValid contents_are_acceptable,
577 dialog->contents_are_acceptable = contents_are_acceptable;
578 dialog->acceptable_data = data;
582 psppire_dialog_is_acceptable (const PsppireDialog *dialog)
584 return (dialog->contents_are_acceptable == NULL
585 || dialog->contents_are_acceptable (dialog->acceptable_data));
592 get_internal_child (GtkBuildable *buildable,
594 const gchar *childname)
596 PsppireDialog *dialog = PSPPIRE_DIALOG (buildable);
598 if ( 0 == strcmp (childname, "hbox"))
599 return G_OBJECT (dialog->box);
607 psppire_dialog_buildable_init (GtkBuildableIface *iface)
609 iface->get_internal_child = get_internal_child;