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 g_return_if_fail (object != NULL);
93 g_return_if_fail (PSPPIRE_IS_DIALOG (object));
95 if (G_OBJECT_CLASS (parent_class)->finalize)
96 G_OBJECT_CLASS (parent_class)->finalize (object);
111 psppire_dialog_get_property (GObject *object,
116 PsppireDialog *dialog = PSPPIRE_DIALOG (object);
120 case PROP_ORIENTATION:
122 if ( GTK_IS_VBOX (dialog->box) || GTK_VPANED (dialog->box))
123 g_value_set_enum (value, PSPPIRE_VERTICAL);
124 else if ( GTK_IS_HBOX (dialog->box) || GTK_HPANED (dialog->box))
125 g_value_set_enum (value, PSPPIRE_HORIZONTAL);
126 else if ( GTK_IS_TABLE (dialog->box))
127 g_value_set_enum (value, PSPPIRE_TABULAR);
131 g_value_set_boolean (value, dialog->slidable);
134 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
141 dialog_set_container (PsppireDialog *dialog)
143 if ( dialog->box != NULL)
145 gtk_container_remove (GTK_CONTAINER (dialog), dialog->box);
148 switch (dialog->orientation)
150 case PSPPIRE_HORIZONTAL:
151 if ( dialog->slidable)
152 dialog->box = gtk_hpaned_new();
154 dialog->box = gtk_hbox_new (FALSE, 5);
156 case PSPPIRE_VERTICAL:
157 if ( dialog->slidable)
158 dialog->box = gtk_vpaned_new();
160 dialog->box = gtk_vbox_new (FALSE, 5);
162 case PSPPIRE_TABULAR:
163 dialog->box = gtk_table_new (2, 3, FALSE);
164 g_object_set (dialog->box,
171 gtk_widget_show_all (dialog->box);
172 gtk_container_add (GTK_CONTAINER (dialog), dialog->box);
177 psppire_dialog_set_property (GObject *object,
183 PsppireDialog *dialog = PSPPIRE_DIALOG (object);
188 dialog->slidable = g_value_get_boolean (value);
190 case PROP_ORIENTATION:
191 dialog->orientation = g_value_get_enum (value);
194 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
198 dialog_set_container (dialog);
202 static GParamSpec *orientation_spec ;
205 psppire_dialog_class_init (PsppireDialogClass *class)
207 GObjectClass *object_class = (GObjectClass *) class;
209 GParamSpec *sliding_spec ;
212 g_param_spec_enum ("orientation",
214 "Which way widgets are packed",
215 PSPPIRE_TYPE_ORIENTATION,
216 PSPPIRE_HORIZONTAL /* default value */,
217 G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
220 g_param_spec_boolean ("slidable",
222 "Can the container be sized by the user",
224 G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
226 object_class->set_property = psppire_dialog_set_property;
227 object_class->get_property = psppire_dialog_get_property;
229 g_object_class_install_property (object_class,
234 g_object_class_install_property (object_class,
238 signals [DIALOG_REFRESH] =
239 g_signal_new ("refresh",
240 G_TYPE_FROM_CLASS (class),
244 g_cclosure_marshal_VOID__VOID,
250 g_signal_new ("response",
251 G_TYPE_FROM_CLASS (class),
255 g_cclosure_marshal_VOID__INT,
261 signals [VALIDITY_CHANGED] =
262 g_signal_new ("validity-changed",
263 G_TYPE_FROM_CLASS (class),
267 g_cclosure_marshal_VOID__BOOLEAN,
273 signals [DIALOG_HELP] =
274 g_signal_new ("help",
275 G_TYPE_FROM_CLASS (class),
279 g_cclosure_marshal_VOID__STRING,
285 object_class->finalize = psppire_dialog_finalize;
287 parent_class = g_type_class_peek_parent (class);
294 close_dialog (GtkWidget *w, gpointer data)
296 PsppireDialog *dialog = data;
298 psppire_dialog_close (dialog);
302 psppire_dialog_close (PsppireDialog *dialog)
304 g_main_loop_quit (dialog->loop);
305 gtk_widget_hide (GTK_WIDGET (dialog));
310 delete_event_callback (GtkWidget *w, GdkEvent *e, gpointer data)
312 close_dialog (w, data);
317 psppire_dialog_init (PsppireDialog *dialog)
321 dialog->contents_are_valid = NULL;
322 dialog->validity_data = NULL;
323 dialog->contents_are_acceptable = NULL;
324 dialog->acceptable_data = NULL;
325 dialog->slidable = FALSE;
327 g_value_init (&value, orientation_spec->value_type);
328 g_param_value_set_default (orientation_spec, &value);
330 gtk_window_set_type_hint (GTK_WINDOW (dialog),
331 GDK_WINDOW_TYPE_HINT_DIALOG);
333 g_value_unset (&value);
335 g_signal_connect (dialog, "delete-event",
336 G_CALLBACK (delete_event_callback),
339 gtk_window_set_type_hint (GTK_WINDOW (dialog),
340 GDK_WINDOW_TYPE_HINT_DIALOG);
342 g_object_set (dialog, "icon-name", "pspp", NULL);
347 psppire_dialog_new (void)
349 PsppireDialog *dialog ;
351 dialog = g_object_new (psppire_dialog_get_type (),
354 return GTK_WIDGET (dialog) ;
359 psppire_dialog_notify_change (PsppireDialog *dialog)
361 if ( dialog->contents_are_valid )
363 gboolean valid = dialog->contents_are_valid (dialog->validity_data);
365 g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, valid);
370 /* Descend the widget tree, connecting appropriate signals to the
371 psppire_dialog_notify_change callback */
373 connect_notify_signal (GtkWidget *w, gpointer data)
375 PsppireDialog *dialog = data;
377 if ( PSPPIRE_IS_BUTTONBOX (w))
380 if ( GTK_IS_CONTAINER (w))
382 gtk_container_foreach (GTK_CONTAINER (w),
383 connect_notify_signal,
388 /* It's unfortunate that GTK+ doesn't have a generic
389 "user-modified-state-changed" signal. Instead, we have to try and
390 predict what widgets and signals are likely to exist in our dialogs. */
392 if ( GTK_IS_TOGGLE_BUTTON (w))
394 g_signal_connect_swapped (w, "toggled",
395 G_CALLBACK (psppire_dialog_notify_change),
399 if ( PSPPIRE_IS_SELECTOR (w))
401 g_signal_connect_swapped (w, "selected",
402 G_CALLBACK (psppire_dialog_notify_change),
405 g_signal_connect_swapped (w, "de-selected",
406 G_CALLBACK (psppire_dialog_notify_change),
410 if ( GTK_IS_EDITABLE (w))
412 g_signal_connect_swapped (w, "changed",
413 G_CALLBACK (psppire_dialog_notify_change),
417 if ( GTK_IS_CELL_EDITABLE (w))
419 g_signal_connect_swapped (w, "editing-done",
420 G_CALLBACK (psppire_dialog_notify_change),
424 if ( GTK_IS_TEXT_VIEW (w))
426 GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
428 g_signal_connect_swapped (buffer, "changed",
429 G_CALLBACK (psppire_dialog_notify_change),
433 if ( GTK_IS_TREE_VIEW (w))
436 GtkTreeView *tv = GTK_TREE_VIEW (w);
437 GtkTreeSelection *selection =
438 gtk_tree_view_get_selection (tv);
439 GtkTreeViewColumn *col;
440 GtkTreeModel *model = gtk_tree_view_get_model (tv);
444 g_signal_connect_swapped (model, "row-changed",
445 G_CALLBACK (psppire_dialog_notify_change),
448 g_signal_connect_swapped (model, "row-deleted",
449 G_CALLBACK (psppire_dialog_notify_change),
452 g_signal_connect_swapped (model, "row-inserted",
453 G_CALLBACK (psppire_dialog_notify_change),
457 g_signal_connect_swapped (selection, "changed",
458 G_CALLBACK (psppire_dialog_notify_change),
461 while ((col = gtk_tree_view_get_column (tv, i++)))
463 GList *renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (col));
464 GList *start = renderers;
467 if ( GTK_IS_CELL_RENDERER_TOGGLE (renderers->data))
468 g_signal_connect_swapped (renderers->data, "toggled",
469 G_CALLBACK (psppire_dialog_notify_change), dialog);
470 renderers = renderers->next;
479 psppire_dialog_run (PsppireDialog *dialog)
481 if ( dialog->contents_are_valid != NULL )
482 gtk_container_foreach (GTK_CONTAINER (dialog->box),
483 connect_notify_signal,
486 dialog->loop = g_main_loop_new (NULL, FALSE);
488 gtk_widget_show (GTK_WIDGET (dialog));
490 if ( dialog->contents_are_valid != NULL)
491 g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, FALSE);
493 g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
495 gdk_threads_leave ();
496 g_main_loop_run (dialog->loop);
497 gdk_threads_enter ();
499 g_main_loop_unref (dialog->loop);
501 g_signal_emit (dialog, signals [RESPONSE], 0, dialog->response);
503 return dialog->response;
508 psppire_dialog_reload (PsppireDialog *dialog)
510 g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
515 psppire_dialog_help (PsppireDialog *dialog)
518 g_object_get (dialog, "name", &name, NULL);
522 g_signal_emit (dialog, signals [DIALOG_HELP], 0, name);
527 psppire_orientation_get_type (void)
529 static GType etype = 0;
532 static const GEnumValue values[] =
534 { PSPPIRE_HORIZONTAL, "PSPPIRE_HORIZONTAL", "Horizontal" },
535 { PSPPIRE_VERTICAL, "PSPPIRE_VERTICAL", "Vertical" },
536 { PSPPIRE_TABULAR, "PSPPIRE_TABULAR", "Tabular" },
540 etype = g_enum_register_static
541 (g_intern_static_string ("PsppireOrientation"), values);
548 /* Sets a predicate function that is checked after each change that the user
549 makes to the dialog's state. If the predicate function returns false, then
550 "OK" and other buttons that accept the dialog's settings will be
553 psppire_dialog_set_valid_predicate (PsppireDialog *dialog,
554 ContentsAreValid contents_are_valid,
557 dialog->contents_are_valid = contents_are_valid;
558 dialog->validity_data = data;
561 /* Sets a predicate function that is called after "OK" or another button that
562 accepts the dialog's settings is pushed. If the predicate function returns
563 false, then the button push is ignored. (If the predicate function returns
564 false, then it should take some action to notify the user why the contents
565 are unacceptable, e.g. pop up a dialog box.)
567 An accept predicate is preferred over a validity predicate when the reason
568 why the dialog settings are unacceptable may not be obvious to the user, so
569 that the user needs a helpful message to explain. */
571 psppire_dialog_set_accept_predicate (PsppireDialog *dialog,
572 ContentsAreValid contents_are_acceptable,
575 dialog->contents_are_acceptable = contents_are_acceptable;
576 dialog->acceptable_data = data;
580 psppire_dialog_is_acceptable (const PsppireDialog *dialog)
582 return (dialog->contents_are_acceptable == NULL
583 || dialog->contents_are_acceptable (dialog->acceptable_data));
590 get_internal_child (GtkBuildable *buildable,
592 const gchar *childname)
594 PsppireDialog *dialog = PSPPIRE_DIALOG (buildable);
596 if ( 0 == strcmp (childname, "hbox"))
597 return G_OBJECT (dialog->box);
605 psppire_dialog_buildable_init (GtkBuildableIface *iface)
607 iface->get_internal_child = get_internal_child;