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 psppire_dialog_finalize (GObject *object)
47 PsppireDialog *dialog = PSPPIRE_DIALOG (object);
49 g_free (dialog->help_page);
51 if (G_OBJECT_CLASS (parent_class)->finalize)
52 G_OBJECT_CLASS (parent_class)->finalize (object);
56 psppire_dialog_base_init (PsppireDialogClass *class)
58 GObjectClass *object_class = G_OBJECT_CLASS (class);
60 object_class->finalize = psppire_dialog_finalize;
64 psppire_dialog_get_type (void)
66 static GType dialog_type = 0;
70 static const GTypeInfo dialog_info =
72 sizeof (PsppireDialogClass),
73 (GBaseInitFunc) (void (*)(void)) psppire_dialog_base_init,
74 NULL, /* base_finalize */
75 (GClassInitFunc) (void (*)(void)) psppire_dialog_class_init,
76 NULL, /* class_finalize */
77 NULL, /* class_data */
78 sizeof (PsppireDialog),
80 (GInstanceInitFunc) (void (*)(void)) psppire_dialog_init,
81 NULL /* value_table */
84 dialog_type = g_type_register_static (PSPPIRE_TYPE_WINDOW_BASE,
85 "PsppireDialog", &dialog_info, 0);
104 psppire_dialog_get_property (GObject *object,
109 PsppireDialog *dialog = PSPPIRE_DIALOG (object);
114 g_value_set_boolean (value, dialog->slidable);
117 g_value_set_string (value, dialog->help_page);
120 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
126 psppire_dialog_set_property (GObject *object,
132 PsppireDialog *dialog = PSPPIRE_DIALOG (object);
137 dialog->slidable = g_value_get_boolean (value);
140 dialog->help_page = g_value_dup_string (value);
143 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
149 psppire_dialog_class_init (PsppireDialogClass *class)
151 GObjectClass *object_class = (GObjectClass *) class;
153 GParamSpec *sliding_spec ;
154 GParamSpec *help_page_spec ;
157 g_param_spec_string ("help-page",
159 "The section of the manual to load when the Help button is clicked",
164 g_param_spec_boolean ("slidable",
166 "Can the container be sized by the user",
168 G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
170 object_class->set_property = psppire_dialog_set_property;
171 object_class->get_property = psppire_dialog_get_property;
173 g_object_class_install_property (object_class,
177 g_object_class_install_property (object_class,
182 signals [DIALOG_REFRESH] =
183 g_signal_new ("refresh",
184 G_TYPE_FROM_CLASS (class),
188 g_cclosure_marshal_VOID__VOID,
194 g_signal_new ("response",
195 G_TYPE_FROM_CLASS (class),
199 g_cclosure_marshal_VOID__INT,
205 signals [VALIDITY_CHANGED] =
206 g_signal_new ("validity-changed",
207 G_TYPE_FROM_CLASS (class),
211 g_cclosure_marshal_VOID__BOOLEAN,
217 signals [DIALOG_HELP] =
218 g_signal_new ("help",
219 G_TYPE_FROM_CLASS (class),
223 g_cclosure_marshal_VOID__STRING,
228 parent_class = g_type_class_peek_parent (class);
232 close_dialog (GtkWidget *w, gpointer data)
234 PsppireDialog *dialog = data;
236 psppire_dialog_close (dialog);
240 psppire_dialog_close (PsppireDialog *dialog)
242 g_main_loop_quit (dialog->loop);
243 gtk_widget_hide (GTK_WIDGET (dialog));
247 delete_event_callback (GtkWidget *w, GdkEvent *e, gpointer data)
249 close_dialog (w, data);
254 psppire_dialog_init (PsppireDialog *dialog)
256 dialog->contents_are_valid = NULL;
257 dialog->validity_data = NULL;
258 dialog->contents_are_acceptable = NULL;
259 dialog->acceptable_data = NULL;
260 dialog->slidable = FALSE;
261 dialog->help_page = NULL;
263 gtk_window_set_type_hint (GTK_WINDOW (dialog),
264 GDK_WINDOW_TYPE_HINT_DIALOG);
266 g_signal_connect (dialog, "delete-event",
267 G_CALLBACK (delete_event_callback),
270 gtk_window_set_type_hint (GTK_WINDOW (dialog),
271 GDK_WINDOW_TYPE_HINT_DIALOG);
273 g_object_set (dialog, "icon-name", "pspp", NULL);
277 psppire_dialog_new (void)
279 PsppireDialog *dialog ;
281 dialog = g_object_new (psppire_dialog_get_type (),
284 return GTK_WIDGET (dialog) ;
289 psppire_dialog_notify_change (PsppireDialog *dialog)
291 if (dialog->contents_are_valid)
293 gboolean valid = dialog->contents_are_valid (dialog->validity_data);
295 g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, valid);
300 /* Descend the widget tree, connecting appropriate signals to the
301 psppire_dialog_notify_change callback */
303 connect_notify_signal (GtkWidget *w, gpointer data)
305 PsppireDialog *dialog = data;
307 if (PSPPIRE_IS_BUTTON_BOX (w))
310 if (GTK_IS_CONTAINER (w))
312 gtk_container_foreach (GTK_CONTAINER (w),
313 connect_notify_signal,
318 /* It's unfortunate that GTK+ doesn't have a generic
319 "user-modified-state-changed" signal. Instead, we have to try and
320 predict what widgets and signals are likely to exist in our dialogs. */
322 if (GTK_IS_TOGGLE_BUTTON (w))
324 g_signal_connect_swapped (w, "toggled",
325 G_CALLBACK (psppire_dialog_notify_change),
329 if (PSPPIRE_IS_SELECTOR (w))
331 g_signal_connect_swapped (w, "selected",
332 G_CALLBACK (psppire_dialog_notify_change),
335 g_signal_connect_swapped (w, "de-selected",
336 G_CALLBACK (psppire_dialog_notify_change),
339 psppire_selector_update_subjects (PSPPIRE_SELECTOR (w));
342 if (GTK_IS_EDITABLE (w))
344 g_signal_connect_swapped (w, "changed",
345 G_CALLBACK (psppire_dialog_notify_change),
349 if (GTK_IS_CELL_EDITABLE (w))
351 g_signal_connect_swapped (w, "editing-done",
352 G_CALLBACK (psppire_dialog_notify_change),
356 if (GTK_IS_TEXT_VIEW (w))
358 GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
360 g_signal_connect_swapped (buffer, "changed",
361 G_CALLBACK (psppire_dialog_notify_change),
365 if (GTK_IS_TREE_VIEW (w))
368 GtkTreeView *tv = GTK_TREE_VIEW (w);
369 GtkTreeSelection *selection =
370 gtk_tree_view_get_selection (tv);
371 GtkTreeViewColumn *col;
372 GtkTreeModel *model = gtk_tree_view_get_model (tv);
376 g_signal_connect_swapped (model, "row-changed",
377 G_CALLBACK (psppire_dialog_notify_change),
380 g_signal_connect_swapped (model, "row-deleted",
381 G_CALLBACK (psppire_dialog_notify_change),
384 g_signal_connect_swapped (model, "row-inserted",
385 G_CALLBACK (psppire_dialog_notify_change),
390 g_signal_connect_swapped (selection, "changed",
391 G_CALLBACK (psppire_dialog_notify_change),
394 while ((col = gtk_tree_view_get_column (tv, i++)))
396 GList *renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (col));
397 GList *start = renderers;
400 if (GTK_IS_CELL_RENDERER_TOGGLE (renderers->data))
401 g_signal_connect_swapped (renderers->data, "toggled",
402 G_CALLBACK (psppire_dialog_notify_change), dialog);
403 renderers = renderers->next;
412 psppire_dialog_run (PsppireDialog *dialog)
415 g_object_get (dialog, "title", &title, NULL);
418 g_warning ("PsppireDialog %s has no title", gtk_widget_get_name (GTK_WIDGET (dialog)));
420 if (dialog->contents_are_valid != NULL)
421 gtk_container_foreach (GTK_CONTAINER (gtk_bin_get_child(GTK_BIN(dialog))),
422 connect_notify_signal,
425 dialog->loop = g_main_loop_new (NULL, FALSE);
427 gtk_widget_show (GTK_WIDGET (dialog));
428 psppire_dialog_notify_change (dialog);
430 g_main_loop_run (dialog->loop);
432 g_main_loop_unref (dialog->loop);
434 g_signal_emit (dialog, signals [RESPONSE], 0, dialog->response);
438 return dialog->response;
443 psppire_dialog_reload (PsppireDialog *dialog)
445 g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
450 psppire_dialog_help (PsppireDialog *dialog)
452 const char *page = NULL;
454 g_object_get (dialog, "help-page", &page, NULL);
458 g_signal_emit (dialog, signals [DIALOG_HELP], 0, page);
461 /* Sets a predicate function that is checked after each change that the user
462 makes to the dialog's state. If the predicate function returns false, then
463 "OK" and other buttons that accept the dialog's settings will be
466 psppire_dialog_set_valid_predicate (PsppireDialog *dialog,
467 ContentsAreValid contents_are_valid,
470 dialog->contents_are_valid = contents_are_valid;
471 dialog->validity_data = data;
474 /* Sets a predicate function that is called after "OK" or another button that
475 accepts the dialog's settings is pushed. If the predicate function returns
476 false, then the button push is ignored. (If the predicate function returns
477 false, then it should take some action to notify the user why the contents
478 are unacceptable, e.g. pop up a dialog box.)
480 An accept predicate is preferred over a validity predicate when the reason
481 why the dialog settings are unacceptable may not be obvious to the user, so
482 that the user needs a helpful message to explain. */
484 psppire_dialog_set_accept_predicate (PsppireDialog *dialog,
485 ContentsAreValid contents_are_acceptable,
488 dialog->contents_are_acceptable = contents_are_acceptable;
489 dialog->acceptable_data = data;
493 psppire_dialog_is_acceptable (const PsppireDialog *dialog)
495 return (dialog->contents_are_acceptable == NULL
496 || dialog->contents_are_acceptable (dialog->acceptable_data));