Warnings: missing initializer for value_tables and function type cast (GObject)
[pspp] / src / ui / gui / psppire-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 2010, 2011, 2012, 2015  Free Software Foundation
3
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.
8
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.
13
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/>. */
16
17
18 #include <config.h>
19
20 #include <gtk/gtk.h>
21 #include "psppire-dialog.h"
22 #include "psppire-buttonbox.h"
23 #include "psppire-selector.h"
24 #include <string.h>
25 #include "builder-wrapper.h"
26 #include "help-menu.h"
27
28 #include "psppire-window-base.h"
29
30 static void psppire_dialog_class_init          (PsppireDialogClass *);
31 static void psppire_dialog_init                (PsppireDialog      *);
32
33
34 enum  {DIALOG_REFRESH,
35        RESPONSE,
36        VALIDITY_CHANGED,
37        DIALOG_HELP,
38        n_SIGNALS};
39
40 static guint signals [n_SIGNALS];
41
42 static GObjectClass     *parent_class = NULL;
43
44 static void
45 psppire_dialog_finalize (GObject *object)
46 {
47   PsppireDialog *dialog = PSPPIRE_DIALOG (object);
48
49   g_free (dialog->help_page);
50
51   if (G_OBJECT_CLASS (parent_class)->finalize)
52     G_OBJECT_CLASS (parent_class)->finalize (object);
53 }
54
55 static void
56 psppire_dialog_base_init (PsppireDialogClass *class)
57 {
58   GObjectClass *object_class = G_OBJECT_CLASS (class);
59
60   object_class->finalize = psppire_dialog_finalize;
61 }
62
63 GType
64 psppire_dialog_get_type (void)
65 {
66   static GType dialog_type = 0;
67
68   if (!dialog_type)
69     {
70       static const GTypeInfo dialog_info =
71       {
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),
79         0,
80         (GInstanceInitFunc) (void (*)(void)) psppire_dialog_init,
81         NULL /* value_table */
82       };
83
84       dialog_type = g_type_register_static (PSPPIRE_TYPE_WINDOW_BASE,
85                                             "PsppireDialog", &dialog_info, 0);
86     }
87
88   return dialog_type;
89 }
90
91
92
93 /* Properties */
94 enum
95 {
96   PROP_0,
97   PROP_ORIENTATION,
98   PROP_SLIDING,
99   PROP_HELP_PAGE,
100 };
101
102
103 static void
104 psppire_dialog_get_property (GObject         *object,
105                              guint            prop_id,
106                              GValue          *value,
107                              GParamSpec      *pspec)
108 {
109   PsppireDialog *dialog = PSPPIRE_DIALOG (object);
110
111   switch (prop_id)
112     {
113     case PROP_SLIDING:
114       g_value_set_boolean (value, dialog->slidable);
115       break;
116     case PROP_HELP_PAGE:
117       g_value_set_string (value, dialog->help_page);
118       break;
119     default:
120       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
121       break;
122     };
123 }
124
125 static void
126 psppire_dialog_set_property (GObject         *object,
127                              guint            prop_id,
128                              const GValue    *value,
129                              GParamSpec      *pspec)
130
131 {
132   PsppireDialog *dialog = PSPPIRE_DIALOG (object);
133
134   switch (prop_id)
135     {
136     case PROP_SLIDING:
137       dialog->slidable = g_value_get_boolean (value);
138       break;
139     case PROP_HELP_PAGE:
140       dialog->help_page = g_value_dup_string (value);
141       break;
142     default:
143       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
144       break;
145     };
146 }
147
148 static void
149 psppire_dialog_class_init (PsppireDialogClass *class)
150 {
151   GObjectClass *object_class = (GObjectClass *) class;
152
153   GParamSpec *sliding_spec ;
154   GParamSpec *help_page_spec ;
155
156   help_page_spec =
157     g_param_spec_string ("help-page",
158                          "Help Page",
159                          "The section of the manual to load when the Help button is clicked",
160                          NULL,
161                          G_PARAM_READWRITE);
162
163   sliding_spec =
164     g_param_spec_boolean ("slidable",
165                           "Slidable",
166                           "Can the container be sized by the user",
167                           FALSE,
168                           G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
169
170   object_class->set_property = psppire_dialog_set_property;
171   object_class->get_property = psppire_dialog_get_property;
172
173   g_object_class_install_property (object_class,
174                                    PROP_SLIDING,
175                                    sliding_spec);
176
177   g_object_class_install_property (object_class,
178                                    PROP_HELP_PAGE,
179                                    help_page_spec);
180
181
182   signals [DIALOG_REFRESH] =
183     g_signal_new ("refresh",
184                   G_TYPE_FROM_CLASS (class),
185                   G_SIGNAL_RUN_FIRST,
186                   0,
187                   NULL, NULL,
188                   g_cclosure_marshal_VOID__VOID,
189                   G_TYPE_NONE,
190                   0);
191
192
193   signals [RESPONSE] =
194     g_signal_new ("response",
195                   G_TYPE_FROM_CLASS (class),
196                   G_SIGNAL_RUN_FIRST,
197                   0,
198                   NULL, NULL,
199                   g_cclosure_marshal_VOID__INT,
200                   G_TYPE_NONE,
201                   1,
202                   G_TYPE_INT);
203
204
205   signals [VALIDITY_CHANGED] =
206     g_signal_new ("validity-changed",
207                   G_TYPE_FROM_CLASS (class),
208                   G_SIGNAL_RUN_FIRST,
209                   0,
210                   NULL, NULL,
211                   g_cclosure_marshal_VOID__BOOLEAN,
212                   G_TYPE_NONE,
213                   1,
214                   G_TYPE_BOOLEAN);
215
216
217   signals [DIALOG_HELP] =
218     g_signal_new ("help",
219                   G_TYPE_FROM_CLASS (class),
220                   G_SIGNAL_RUN_FIRST,
221                   0,
222                   NULL, NULL,
223                   g_cclosure_marshal_VOID__STRING,
224                   G_TYPE_NONE,
225                   1,
226                   G_TYPE_STRING);
227
228   parent_class = g_type_class_peek_parent (class);
229 }
230
231 static void
232 close_dialog (GtkWidget *w, gpointer data)
233 {
234   PsppireDialog *dialog = data;
235
236   psppire_dialog_close (dialog);
237 }
238
239 void
240 psppire_dialog_close (PsppireDialog *dialog)
241 {
242   g_main_loop_quit (dialog->loop);
243   gtk_widget_hide (GTK_WIDGET (dialog));
244 }
245
246 static void
247 delete_event_callback (GtkWidget *w, GdkEvent *e, gpointer data)
248 {
249   close_dialog (w, data);
250 }
251
252
253 static void
254 psppire_dialog_init (PsppireDialog *dialog)
255 {
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;
262
263   gtk_window_set_type_hint (GTK_WINDOW (dialog),
264         GDK_WINDOW_TYPE_HINT_DIALOG);
265
266   g_signal_connect (dialog, "delete-event",
267                     G_CALLBACK (delete_event_callback),
268                     dialog);
269
270   gtk_window_set_type_hint (GTK_WINDOW (dialog),
271         GDK_WINDOW_TYPE_HINT_DIALOG);
272
273   g_object_set (dialog, "icon-name", "pspp", NULL);
274 }
275
276 GtkWidget*
277 psppire_dialog_new (void)
278 {
279   PsppireDialog *dialog ;
280
281   dialog = g_object_new (psppire_dialog_get_type (),
282                          NULL);
283
284   return GTK_WIDGET (dialog) ;
285 }
286
287
288 void
289 psppire_dialog_notify_change (PsppireDialog *dialog)
290 {
291   if (dialog->contents_are_valid)
292     {
293       gboolean valid = dialog->contents_are_valid (dialog->validity_data);
294
295       g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, valid);
296     }
297 }
298
299
300 /* Descend the widget tree, connecting appropriate signals to the
301    psppire_dialog_notify_change callback */
302 static void
303 connect_notify_signal (GtkWidget *w, gpointer data)
304 {
305   PsppireDialog *dialog = data;
306
307   if (PSPPIRE_IS_BUTTON_BOX (w))
308     return;
309
310   if (GTK_IS_CONTAINER (w))
311     {
312       gtk_container_foreach (GTK_CONTAINER (w),
313                              connect_notify_signal,
314                              dialog);
315     }
316
317
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. */
321
322   if (GTK_IS_TOGGLE_BUTTON (w))
323     {
324       g_signal_connect_swapped (w, "toggled",
325                                 G_CALLBACK (psppire_dialog_notify_change),
326                                 dialog);
327     }
328
329   if (PSPPIRE_IS_SELECTOR (w))
330     {
331       g_signal_connect_swapped (w, "selected",
332                                 G_CALLBACK (psppire_dialog_notify_change),
333                                 dialog);
334
335       g_signal_connect_swapped (w, "de-selected",
336                                 G_CALLBACK (psppire_dialog_notify_change),
337                                 dialog);
338
339       psppire_selector_update_subjects (PSPPIRE_SELECTOR (w));
340     }
341
342   if (GTK_IS_EDITABLE (w))
343     {
344       g_signal_connect_swapped (w, "changed",
345                                 G_CALLBACK (psppire_dialog_notify_change),
346                                 dialog);
347     }
348
349   if (GTK_IS_CELL_EDITABLE (w))
350     {
351       g_signal_connect_swapped (w, "editing-done",
352                                 G_CALLBACK (psppire_dialog_notify_change),
353                                 dialog);
354     }
355
356   if (GTK_IS_TEXT_VIEW (w))
357     {
358       GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
359
360       g_signal_connect_swapped (buffer, "changed",
361                                 G_CALLBACK (psppire_dialog_notify_change),
362                                 dialog);
363     }
364
365   if (GTK_IS_TREE_VIEW (w))
366     {
367       gint i = 0;
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);
373
374       if (model)
375         {
376           g_signal_connect_swapped (model, "row-changed",
377                                     G_CALLBACK (psppire_dialog_notify_change),
378                                     dialog);
379
380           g_signal_connect_swapped (model, "row-deleted",
381                                     G_CALLBACK (psppire_dialog_notify_change),
382                                     dialog);
383
384           g_signal_connect_swapped (model, "row-inserted",
385                                     G_CALLBACK (psppire_dialog_notify_change),
386                                     dialog);
387
388         }
389
390       g_signal_connect_swapped (selection, "changed",
391                                 G_CALLBACK (psppire_dialog_notify_change),
392                                 dialog);
393
394       while ((col = gtk_tree_view_get_column (tv, i++)))
395         {
396           GList *renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (col));
397           GList *start = renderers;
398           while (renderers)
399             {
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;
404             }
405           g_list_free (start);
406         }
407     }
408 }
409
410
411 gint
412 psppire_dialog_run (PsppireDialog *dialog)
413 {
414   gchar *title = NULL;
415   g_object_get (dialog, "title", &title, NULL);
416
417   if (title == NULL)
418     g_warning ("PsppireDialog %s has no title", gtk_widget_get_name (GTK_WIDGET (dialog)));
419
420   if (dialog->contents_are_valid != NULL)
421     gtk_container_foreach (GTK_CONTAINER (gtk_bin_get_child(GTK_BIN(dialog))),
422                            connect_notify_signal,
423                            dialog);
424
425   dialog->loop = g_main_loop_new (NULL, FALSE);
426
427   gtk_widget_show (GTK_WIDGET (dialog));
428   psppire_dialog_notify_change (dialog);
429
430   g_main_loop_run (dialog->loop);
431
432   g_main_loop_unref (dialog->loop);
433
434   g_signal_emit (dialog, signals [RESPONSE], 0, dialog->response);
435
436   g_free (title);
437
438   return dialog->response;
439 }
440
441
442 void
443 psppire_dialog_reload (PsppireDialog *dialog)
444 {
445   g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
446 }
447
448
449 void
450 psppire_dialog_help (PsppireDialog *dialog)
451 {
452   const char *page = NULL;
453
454   g_object_get (dialog, "help-page", &page, NULL);
455
456   online_help (page);
457
458   g_signal_emit (dialog, signals [DIALOG_HELP], 0, page);
459 }
460
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
464    disabled. */
465 void
466 psppire_dialog_set_valid_predicate (PsppireDialog *dialog,
467                                     ContentsAreValid contents_are_valid,
468                                     gpointer data)
469 {
470   dialog->contents_are_valid = contents_are_valid;
471   dialog->validity_data = data;
472 }
473
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.)
479
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. */
483 void
484 psppire_dialog_set_accept_predicate (PsppireDialog *dialog,
485                                      ContentsAreValid contents_are_acceptable,
486                                      gpointer data)
487 {
488   dialog->contents_are_acceptable = contents_are_acceptable;
489   dialog->acceptable_data = data;
490 }
491
492 gboolean
493 psppire_dialog_is_acceptable (const PsppireDialog *dialog)
494 {
495   return (dialog->contents_are_acceptable == NULL
496           || dialog->contents_are_acceptable (dialog->acceptable_data));
497 }