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 G_DEFINE_TYPE (PsppireDialog, psppire_dialog, PSPPIRE_TYPE_WINDOW_BASE);
69 psppire_dialog_get_property (GObject *object,
74 PsppireDialog *dialog = PSPPIRE_DIALOG (object);
79 g_value_set_boolean (value, dialog->slidable);
82 g_value_set_string (value, dialog->help_page);
85 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
91 psppire_dialog_set_property (GObject *object,
97 PsppireDialog *dialog = PSPPIRE_DIALOG (object);
102 dialog->slidable = g_value_get_boolean (value);
105 dialog->help_page = g_value_dup_string (value);
108 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
114 psppire_dialog_class_init (PsppireDialogClass *class)
116 GObjectClass *object_class = G_OBJECT_CLASS (class);
118 object_class->finalize = psppire_dialog_finalize;
120 GParamSpec *sliding_spec ;
121 GParamSpec *help_page_spec ;
124 g_param_spec_string ("help-page",
126 "The section of the manual to load when the Help button is clicked",
131 g_param_spec_boolean ("slidable",
133 "Can the container be sized by the user",
135 G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
137 object_class->set_property = psppire_dialog_set_property;
138 object_class->get_property = psppire_dialog_get_property;
140 g_object_class_install_property (object_class,
144 g_object_class_install_property (object_class,
149 signals [DIALOG_REFRESH] =
150 g_signal_new ("refresh",
151 G_TYPE_FROM_CLASS (class),
155 g_cclosure_marshal_VOID__VOID,
161 g_signal_new ("response",
162 G_TYPE_FROM_CLASS (class),
166 g_cclosure_marshal_VOID__INT,
172 signals [VALIDITY_CHANGED] =
173 g_signal_new ("validity-changed",
174 G_TYPE_FROM_CLASS (class),
178 g_cclosure_marshal_VOID__BOOLEAN,
184 signals [DIALOG_HELP] =
185 g_signal_new ("help",
186 G_TYPE_FROM_CLASS (class),
190 g_cclosure_marshal_VOID__STRING,
195 parent_class = g_type_class_peek_parent (class);
199 close_dialog (GtkWidget *w, gpointer data)
201 PsppireDialog *dialog = data;
203 psppire_dialog_close (dialog);
207 psppire_dialog_close (PsppireDialog *dialog)
209 g_main_loop_quit (dialog->loop);
210 gtk_widget_hide (GTK_WIDGET (dialog));
214 delete_event_callback (GtkWidget *w, GdkEvent *e, gpointer data)
216 close_dialog (w, data);
221 psppire_dialog_init (PsppireDialog *dialog)
223 dialog->contents_are_valid = NULL;
224 dialog->validity_data = NULL;
225 dialog->contents_are_acceptable = NULL;
226 dialog->acceptable_data = NULL;
227 dialog->slidable = FALSE;
228 dialog->help_page = NULL;
230 gtk_window_set_type_hint (GTK_WINDOW (dialog),
231 GDK_WINDOW_TYPE_HINT_DIALOG);
233 g_signal_connect (dialog, "delete-event",
234 G_CALLBACK (delete_event_callback),
237 gtk_window_set_type_hint (GTK_WINDOW (dialog),
238 GDK_WINDOW_TYPE_HINT_DIALOG);
240 g_object_set (dialog, "icon-name", "org.gnu.pspp", NULL);
244 psppire_dialog_new (void)
246 PsppireDialog *dialog ;
248 dialog = g_object_new (psppire_dialog_get_type (),
251 return GTK_WIDGET (dialog) ;
256 psppire_dialog_notify_change (PsppireDialog *dialog)
258 if (dialog->contents_are_valid)
260 gboolean valid = dialog->contents_are_valid (dialog->validity_data);
262 g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, valid);
267 /* Descend the widget tree, connecting appropriate signals to the
268 psppire_dialog_notify_change callback */
270 connect_notify_signal (GtkWidget *w, gpointer data)
272 PsppireDialog *dialog = data;
274 if (PSPPIRE_IS_BUTTON_BOX (w))
277 if (GTK_IS_CONTAINER (w))
279 gtk_container_foreach (GTK_CONTAINER (w),
280 connect_notify_signal,
285 /* It's unfortunate that GTK+ doesn't have a generic
286 "user-modified-state-changed" signal. Instead, we have to try and
287 predict what widgets and signals are likely to exist in our dialogs. */
289 if (GTK_IS_TOGGLE_BUTTON (w))
291 g_signal_connect_swapped (w, "toggled",
292 G_CALLBACK (psppire_dialog_notify_change),
296 if (PSPPIRE_IS_SELECTOR (w))
298 g_signal_connect_swapped (w, "selected",
299 G_CALLBACK (psppire_dialog_notify_change),
302 g_signal_connect_swapped (w, "de-selected",
303 G_CALLBACK (psppire_dialog_notify_change),
306 psppire_selector_update_subjects (PSPPIRE_SELECTOR (w));
309 if (GTK_IS_EDITABLE (w))
311 g_signal_connect_swapped (w, "changed",
312 G_CALLBACK (psppire_dialog_notify_change),
316 if (GTK_IS_CELL_EDITABLE (w))
318 g_signal_connect_swapped (w, "editing-done",
319 G_CALLBACK (psppire_dialog_notify_change),
323 if (GTK_IS_TEXT_VIEW (w))
325 GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
327 g_signal_connect_swapped (buffer, "changed",
328 G_CALLBACK (psppire_dialog_notify_change),
332 if (GTK_IS_TREE_VIEW (w))
335 GtkTreeView *tv = GTK_TREE_VIEW (w);
336 GtkTreeSelection *selection =
337 gtk_tree_view_get_selection (tv);
338 GtkTreeViewColumn *col;
339 GtkTreeModel *model = gtk_tree_view_get_model (tv);
343 g_signal_connect_swapped (model, "row-changed",
344 G_CALLBACK (psppire_dialog_notify_change),
347 g_signal_connect_swapped (model, "row-deleted",
348 G_CALLBACK (psppire_dialog_notify_change),
351 g_signal_connect_swapped (model, "row-inserted",
352 G_CALLBACK (psppire_dialog_notify_change),
357 g_signal_connect_swapped (selection, "changed",
358 G_CALLBACK (psppire_dialog_notify_change),
361 while ((col = gtk_tree_view_get_column (tv, i++)))
363 GList *renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (col));
364 GList *start = renderers;
367 if (GTK_IS_CELL_RENDERER_TOGGLE (renderers->data))
368 g_signal_connect_swapped (renderers->data, "toggled",
369 G_CALLBACK (psppire_dialog_notify_change), dialog);
370 renderers = renderers->next;
379 psppire_dialog_run (PsppireDialog *dialog)
382 g_object_get (dialog, "title", &title, NULL);
385 g_warning ("PsppireDialog %s has no title", gtk_widget_get_name (GTK_WIDGET (dialog)));
387 if (dialog->contents_are_valid != NULL)
388 gtk_container_foreach (GTK_CONTAINER (gtk_bin_get_child(GTK_BIN(dialog))),
389 connect_notify_signal,
392 dialog->loop = g_main_loop_new (NULL, FALSE);
394 gtk_widget_show (GTK_WIDGET (dialog));
395 psppire_dialog_notify_change (dialog);
397 g_main_loop_run (dialog->loop);
399 g_main_loop_unref (dialog->loop);
401 g_signal_emit (dialog, signals [RESPONSE], 0, dialog->response);
405 return dialog->response;
410 psppire_dialog_reload (PsppireDialog *dialog)
412 g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
417 psppire_dialog_help (PsppireDialog *dialog)
419 const char *page = NULL;
421 g_object_get (dialog, "help-page", &page, NULL);
425 g_signal_emit (dialog, signals [DIALOG_HELP], 0, page);
428 /* Sets a predicate function that is checked after each change that the user
429 makes to the dialog's state. If the predicate function returns false, then
430 "OK" and other buttons that accept the dialog's settings will be
433 psppire_dialog_set_valid_predicate (PsppireDialog *dialog,
434 ContentsAreValid contents_are_valid,
437 dialog->contents_are_valid = contents_are_valid;
438 dialog->validity_data = data;
441 /* Sets a predicate function that is called after "OK" or another button that
442 accepts the dialog's settings is pushed. If the predicate function returns
443 false, then the button push is ignored. (If the predicate function returns
444 false, then it should take some action to notify the user why the contents
445 are unacceptable, e.g. pop up a dialog box.)
447 An accept predicate is preferred over a validity predicate when the reason
448 why the dialog settings are unacceptable may not be obvious to the user, so
449 that the user needs a helpful message to explain. */
451 psppire_dialog_set_accept_predicate (PsppireDialog *dialog,
452 ContentsAreValid contents_are_acceptable,
455 dialog->contents_are_acceptable = contents_are_acceptable;
456 dialog->acceptable_data = data;
460 psppire_dialog_is_acceptable (const PsppireDialog *dialog)
462 return (dialog->contents_are_acceptable == NULL
463 || dialog->contents_are_acceptable (dialog->acceptable_data));