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) psppire_dialog_base_init,
74 NULL, /* base_finalize */
75 (GClassInitFunc) psppire_dialog_class_init,
76 NULL, /* class_finalize */
77 NULL, /* class_data */
78 sizeof (PsppireDialog),
80 (GInstanceInitFunc) psppire_dialog_init,
83 dialog_type = g_type_register_static (PSPPIRE_TYPE_WINDOW_BASE,
84 "PsppireDialog", &dialog_info, 0);
103 psppire_dialog_get_property (GObject *object,
108 PsppireDialog *dialog = PSPPIRE_DIALOG (object);
113 g_value_set_boolean (value, dialog->slidable);
116 g_value_set_string (value, dialog->help_page);
119 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
125 psppire_dialog_set_property (GObject *object,
131 PsppireDialog *dialog = PSPPIRE_DIALOG (object);
136 dialog->slidable = g_value_get_boolean (value);
139 dialog->help_page = g_value_dup_string (value);
142 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
148 psppire_dialog_class_init (PsppireDialogClass *class)
150 GObjectClass *object_class = (GObjectClass *) class;
152 GParamSpec *sliding_spec ;
153 GParamSpec *help_page_spec ;
156 g_param_spec_string ("help-page",
158 "The section of the manual to load when the Help button is clicked",
163 g_param_spec_boolean ("slidable",
165 "Can the container be sized by the user",
167 G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
169 object_class->set_property = psppire_dialog_set_property;
170 object_class->get_property = psppire_dialog_get_property;
172 g_object_class_install_property (object_class,
176 g_object_class_install_property (object_class,
181 signals [DIALOG_REFRESH] =
182 g_signal_new ("refresh",
183 G_TYPE_FROM_CLASS (class),
187 g_cclosure_marshal_VOID__VOID,
193 g_signal_new ("response",
194 G_TYPE_FROM_CLASS (class),
198 g_cclosure_marshal_VOID__INT,
204 signals [VALIDITY_CHANGED] =
205 g_signal_new ("validity-changed",
206 G_TYPE_FROM_CLASS (class),
210 g_cclosure_marshal_VOID__BOOLEAN,
216 signals [DIALOG_HELP] =
217 g_signal_new ("help",
218 G_TYPE_FROM_CLASS (class),
222 g_cclosure_marshal_VOID__STRING,
227 parent_class = g_type_class_peek_parent (class);
231 close_dialog (GtkWidget *w, gpointer data)
233 PsppireDialog *dialog = data;
235 psppire_dialog_close (dialog);
239 psppire_dialog_close (PsppireDialog *dialog)
241 g_main_loop_quit (dialog->loop);
242 gtk_widget_hide (GTK_WIDGET (dialog));
246 delete_event_callback (GtkWidget *w, GdkEvent *e, gpointer data)
248 close_dialog (w, data);
253 psppire_dialog_init (PsppireDialog *dialog)
255 dialog->contents_are_valid = NULL;
256 dialog->validity_data = NULL;
257 dialog->contents_are_acceptable = NULL;
258 dialog->acceptable_data = NULL;
259 dialog->slidable = FALSE;
260 dialog->help_page = NULL;
262 gtk_window_set_type_hint (GTK_WINDOW (dialog),
263 GDK_WINDOW_TYPE_HINT_DIALOG);
265 g_signal_connect (dialog, "delete-event",
266 G_CALLBACK (delete_event_callback),
269 gtk_window_set_type_hint (GTK_WINDOW (dialog),
270 GDK_WINDOW_TYPE_HINT_DIALOG);
272 g_object_set (dialog, "icon-name", "pspp", NULL);
276 psppire_dialog_new (void)
278 PsppireDialog *dialog ;
280 dialog = g_object_new (psppire_dialog_get_type (),
283 return GTK_WIDGET (dialog) ;
288 psppire_dialog_notify_change (PsppireDialog *dialog)
290 if ( dialog->contents_are_valid )
292 gboolean valid = dialog->contents_are_valid (dialog->validity_data);
294 g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, valid);
300 remove_notify_handlers (PsppireDialog *dialog, GObject *sel)
302 g_signal_handlers_disconnect_by_data (sel, dialog);
306 /* Descend the widget tree, connecting appropriate signals to the
307 psppire_dialog_notify_change callback */
309 connect_notify_signal (GtkWidget *w, gpointer data)
311 PsppireDialog *dialog = data;
313 if ( PSPPIRE_IS_BUTTONBOX (w))
316 if ( GTK_IS_CONTAINER (w))
318 gtk_container_foreach (GTK_CONTAINER (w),
319 connect_notify_signal,
324 /* It's unfortunate that GTK+ doesn't have a generic
325 "user-modified-state-changed" signal. Instead, we have to try and
326 predict what widgets and signals are likely to exist in our dialogs. */
328 if ( GTK_IS_TOGGLE_BUTTON (w))
330 g_signal_connect_swapped (w, "toggled",
331 G_CALLBACK (psppire_dialog_notify_change),
335 if ( PSPPIRE_IS_SELECTOR (w))
337 g_signal_connect_swapped (w, "selected",
338 G_CALLBACK (psppire_dialog_notify_change),
341 g_signal_connect_swapped (w, "de-selected",
342 G_CALLBACK (psppire_dialog_notify_change),
346 if ( GTK_IS_EDITABLE (w))
348 g_signal_connect_swapped (w, "changed",
349 G_CALLBACK (psppire_dialog_notify_change),
353 if ( GTK_IS_CELL_EDITABLE (w))
355 g_signal_connect_swapped (w, "editing-done",
356 G_CALLBACK (psppire_dialog_notify_change),
360 if ( GTK_IS_TEXT_VIEW (w))
362 GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
364 g_signal_connect_swapped (buffer, "changed",
365 G_CALLBACK (psppire_dialog_notify_change),
369 if ( GTK_IS_TREE_VIEW (w))
372 GtkTreeView *tv = GTK_TREE_VIEW (w);
373 GtkTreeSelection *selection =
374 gtk_tree_view_get_selection (tv);
375 GtkTreeViewColumn *col;
376 GtkTreeModel *model = gtk_tree_view_get_model (tv);
380 g_signal_connect_swapped (model, "row-changed",
381 G_CALLBACK (psppire_dialog_notify_change),
384 g_signal_connect_swapped (model, "row-deleted",
385 G_CALLBACK (psppire_dialog_notify_change),
388 g_signal_connect_swapped (model, "row-inserted",
389 G_CALLBACK (psppire_dialog_notify_change),
392 g_signal_connect (dialog, "destroy", G_CALLBACK (remove_notify_handlers),
396 g_signal_connect_swapped (selection, "changed",
397 G_CALLBACK (psppire_dialog_notify_change),
400 while ((col = gtk_tree_view_get_column (tv, i++)))
402 GList *renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (col));
403 GList *start = renderers;
406 if ( GTK_IS_CELL_RENDERER_TOGGLE (renderers->data))
407 g_signal_connect_swapped (renderers->data, "toggled",
408 G_CALLBACK (psppire_dialog_notify_change), dialog);
409 renderers = renderers->next;
418 psppire_dialog_run (PsppireDialog *dialog)
421 g_object_get (dialog, "title", &title, NULL);
424 g_warning ("PsppireDialog %s has no title", gtk_widget_get_name (GTK_WIDGET (dialog)));
426 if ( dialog->contents_are_valid != NULL )
427 gtk_container_foreach (GTK_CONTAINER (gtk_bin_get_child(GTK_BIN(dialog))),
428 connect_notify_signal,
431 dialog->loop = g_main_loop_new (NULL, FALSE);
433 gtk_widget_show (GTK_WIDGET (dialog));
435 if ( dialog->contents_are_valid != NULL)
436 g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, FALSE);
438 g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
440 g_main_loop_run (dialog->loop);
442 g_main_loop_unref (dialog->loop);
444 g_signal_emit (dialog, signals [RESPONSE], 0, dialog->response);
446 return dialog->response;
451 psppire_dialog_reload (PsppireDialog *dialog)
453 g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
458 psppire_dialog_help (PsppireDialog *dialog)
460 const char *page = NULL;
462 g_object_get (dialog, "help-page", &page, NULL);
466 g_signal_emit (dialog, signals [DIALOG_HELP], 0, page);
469 /* Sets a predicate function that is checked after each change that the user
470 makes to the dialog's state. If the predicate function returns false, then
471 "OK" and other buttons that accept the dialog's settings will be
474 psppire_dialog_set_valid_predicate (PsppireDialog *dialog,
475 ContentsAreValid contents_are_valid,
478 dialog->contents_are_valid = contents_are_valid;
479 dialog->validity_data = data;
482 /* Sets a predicate function that is called after "OK" or another button that
483 accepts the dialog's settings is pushed. If the predicate function returns
484 false, then the button push is ignored. (If the predicate function returns
485 false, then it should take some action to notify the user why the contents
486 are unacceptable, e.g. pop up a dialog box.)
488 An accept predicate is preferred over a validity predicate when the reason
489 why the dialog settings are unacceptable may not be obvious to the user, so
490 that the user needs a helpful message to explain. */
492 psppire_dialog_set_accept_predicate (PsppireDialog *dialog,
493 ContentsAreValid contents_are_acceptable,
496 dialog->contents_are_acceptable = contents_are_acceptable;
497 dialog->acceptable_data = data;
501 psppire_dialog_is_acceptable (const PsppireDialog *dialog)
503 return (dialog->contents_are_acceptable == NULL
504 || dialog->contents_are_acceptable (dialog->acceptable_data));