547ff2a4da25c155411b177fbc8ded3ce8ff943b
[pspp] / src / ui / gui / psppire-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 2010, 2011, 2012  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
43 static void psppire_dialog_buildable_init (GtkBuildableIface *iface);
44
45
46 GType
47 psppire_dialog_get_type (void)
48 {
49   static GType dialog_type = 0;
50
51   if (!dialog_type)
52     {
53       static const GTypeInfo dialog_info =
54       {
55         sizeof (PsppireDialogClass),
56         NULL, /* base_init */
57         NULL, /* base_finalize */
58         (GClassInitFunc) psppire_dialog_class_init,
59         NULL, /* class_finalize */
60         NULL, /* class_data */
61         sizeof (PsppireDialog),
62         0,
63         (GInstanceInitFunc) psppire_dialog_init,
64       };
65
66       static const GInterfaceInfo buildable_info =
67       {
68         (GInterfaceInitFunc) psppire_dialog_buildable_init,
69         NULL,
70         NULL
71       };
72
73       dialog_type = g_type_register_static (PSPPIRE_TYPE_WINDOW_BASE,
74                                             "PsppireDialog", &dialog_info, 0);
75
76       g_type_add_interface_static (dialog_type,
77                                    GTK_TYPE_BUILDABLE,
78                                    &buildable_info);
79     }
80
81   return dialog_type;
82 }
83
84
85
86 static GObjectClass     *parent_class = NULL;
87
88
89 static void
90 psppire_dialog_finalize (GObject *object)
91 {
92   PsppireDialog *dialog ;
93
94   g_return_if_fail (object != NULL);
95   g_return_if_fail (PSPPIRE_IS_DIALOG (object));
96
97   dialog = PSPPIRE_DIALOG (object);
98
99   if (G_OBJECT_CLASS (parent_class)->finalize)
100     G_OBJECT_CLASS (parent_class)->finalize (object);
101 }
102
103
104
105 /* Properties */
106 enum
107 {
108   PROP_0,
109   PROP_ORIENTATION,
110   PROP_SLIDING
111 };
112
113
114 static void
115 psppire_dialog_get_property (GObject         *object,
116                              guint            prop_id,
117                              GValue          *value,
118                              GParamSpec      *pspec)
119 {
120   PsppireDialog *dialog = PSPPIRE_DIALOG (object);
121
122   switch (prop_id)
123     {
124     case PROP_ORIENTATION:
125       {
126         if ( GTK_IS_VBOX (dialog->box) || GTK_VPANED (dialog->box))
127           g_value_set_enum (value, PSPPIRE_VERTICAL);
128         else if ( GTK_IS_HBOX (dialog->box) || GTK_HPANED (dialog->box))
129           g_value_set_enum (value, PSPPIRE_HORIZONTAL);
130         else if ( GTK_IS_TABLE (dialog->box))
131           g_value_set_enum (value, PSPPIRE_TABULAR);
132       }
133       break;
134     case PROP_SLIDING:
135       g_value_set_boolean (value, dialog->slidable);
136       break;
137     default:
138       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
139       break;
140     };
141 }
142
143
144 static void
145 dialog_set_container (PsppireDialog *dialog)
146 {
147   if ( dialog->box != NULL)
148     {
149       gtk_container_remove (GTK_CONTAINER (dialog), dialog->box);
150     }
151
152   switch (dialog->orientation)
153     {
154     case PSPPIRE_HORIZONTAL:
155       if ( dialog->slidable)
156         dialog->box = gtk_hpaned_new();
157       else
158         dialog->box = gtk_hbox_new (FALSE, 5);
159       break;
160     case PSPPIRE_VERTICAL:
161       if ( dialog->slidable)
162         dialog->box = gtk_vpaned_new();
163       else
164         dialog->box = gtk_vbox_new (FALSE, 5);
165       break;
166     case PSPPIRE_TABULAR:
167       dialog->box = gtk_table_new (2, 3, FALSE);
168       g_object_set (dialog->box,
169                     "row-spacing", 5,
170                     "column-spacing", 5,
171                     NULL);
172       break;
173     }
174
175   gtk_widget_show_all (dialog->box);
176   gtk_container_add (GTK_CONTAINER (dialog), dialog->box);
177 }
178
179
180 static void
181 psppire_dialog_set_property (GObject         *object,
182                              guint            prop_id,
183                              const GValue    *value,
184                              GParamSpec      *pspec)
185
186 {
187   PsppireDialog *dialog = PSPPIRE_DIALOG (object);
188
189   switch (prop_id)
190     {
191     case PROP_SLIDING:
192       dialog->slidable = g_value_get_boolean (value);
193       break;
194     case PROP_ORIENTATION:
195       dialog->orientation = g_value_get_enum (value);
196       break;
197     default:
198       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
199       break;
200     };
201
202   dialog_set_container (dialog);
203 }
204
205
206 static GParamSpec *orientation_spec ;
207
208 static void
209 psppire_dialog_class_init (PsppireDialogClass *class)
210 {
211   GObjectClass *object_class = (GObjectClass *) class;
212
213   GParamSpec *sliding_spec ;
214
215   orientation_spec =
216     g_param_spec_enum ("orientation",
217                        "Orientation",
218                        "Which way widgets are packed",
219                        PSPPIRE_TYPE_ORIENTATION,
220                        PSPPIRE_HORIZONTAL /* default value */,
221                        G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
222
223   sliding_spec =
224     g_param_spec_boolean ("slidable",
225                           "Slidable",
226                           "Can the container be sized by the user",
227                           FALSE,
228                           G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
229
230   object_class->set_property = psppire_dialog_set_property;
231   object_class->get_property = psppire_dialog_get_property;
232
233   g_object_class_install_property (object_class,
234                                    PROP_ORIENTATION,
235                                    orientation_spec);
236
237
238   g_object_class_install_property (object_class,
239                                    PROP_SLIDING,
240                                    sliding_spec);
241
242   signals [DIALOG_REFRESH] =
243     g_signal_new ("refresh",
244                   G_TYPE_FROM_CLASS (class),
245                   G_SIGNAL_RUN_FIRST,
246                   0,
247                   NULL, NULL,
248                   g_cclosure_marshal_VOID__VOID,
249                   G_TYPE_NONE,
250                   0);
251
252
253   signals [RESPONSE] =
254     g_signal_new ("response",
255                   G_TYPE_FROM_CLASS (class),
256                   G_SIGNAL_RUN_FIRST,
257                   0,
258                   NULL, NULL,
259                   g_cclosure_marshal_VOID__INT,
260                   G_TYPE_NONE,
261                   1,
262                   G_TYPE_INT);
263
264
265   signals [VALIDITY_CHANGED] =
266     g_signal_new ("validity-changed",
267                   G_TYPE_FROM_CLASS (class),
268                   G_SIGNAL_RUN_FIRST,
269                   0,
270                   NULL, NULL,
271                   g_cclosure_marshal_VOID__BOOLEAN,
272                   G_TYPE_NONE,
273                   1,
274                   G_TYPE_BOOLEAN);
275
276
277   signals [DIALOG_HELP] =
278     g_signal_new ("help",
279                   G_TYPE_FROM_CLASS (class),
280                   G_SIGNAL_RUN_FIRST,
281                   0,
282                   NULL, NULL,
283                   g_cclosure_marshal_VOID__STRING,
284                   G_TYPE_NONE,
285                   1,
286                   G_TYPE_STRING);
287
288
289   object_class->finalize = psppire_dialog_finalize;
290
291   parent_class = g_type_class_peek_parent (class);
292 }
293
294
295
296
297 static void
298 close_dialog (GtkWidget *w, gpointer data)
299 {
300   PsppireDialog *dialog = data;
301
302   psppire_dialog_close (dialog);
303 }
304
305 void
306 psppire_dialog_close (PsppireDialog *dialog)
307 {
308   g_main_loop_quit (dialog->loop);
309   gtk_widget_hide (GTK_WIDGET (dialog));
310 }
311
312
313 static void
314 delete_event_callback (GtkWidget *w, GdkEvent *e, gpointer data)
315 {
316   close_dialog (w, data);
317 }
318
319
320 static void
321 psppire_dialog_init (PsppireDialog *dialog)
322 {
323   GValue value = {0};
324   dialog->box = NULL;
325   dialog->contents_are_valid = NULL;
326   dialog->validity_data = NULL;
327   dialog->contents_are_acceptable = NULL;
328   dialog->acceptable_data = NULL;
329   dialog->slidable = FALSE;
330
331   g_value_init (&value, orientation_spec->value_type);
332   g_param_value_set_default (orientation_spec, &value);
333
334   gtk_window_set_type_hint (GTK_WINDOW (dialog),
335         GDK_WINDOW_TYPE_HINT_DIALOG);
336
337   g_value_unset (&value);
338
339   g_signal_connect (dialog, "delete-event",
340                     G_CALLBACK (delete_event_callback),
341                     dialog);
342
343   gtk_window_set_type_hint (GTK_WINDOW (dialog),
344         GDK_WINDOW_TYPE_HINT_DIALOG);
345
346   g_object_set (dialog, "icon-name", "pspp", NULL);
347 }
348
349
350 GtkWidget*
351 psppire_dialog_new (void)
352 {
353   PsppireDialog *dialog ;
354
355   dialog = g_object_new (psppire_dialog_get_type (),
356                          NULL);
357
358   return GTK_WIDGET (dialog) ;
359 }
360
361
362 void
363 psppire_dialog_notify_change (PsppireDialog *dialog)
364 {
365   if ( dialog->contents_are_valid )
366     {
367       gboolean valid = dialog->contents_are_valid (dialog->validity_data);
368
369       g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, valid);
370     }
371 }
372
373
374 /* Descend the widget tree, connecting appropriate signals to the
375    psppire_dialog_notify_change callback */
376 static void
377 connect_notify_signal (GtkWidget *w, gpointer data)
378 {
379   PsppireDialog *dialog = data;
380
381   if ( PSPPIRE_IS_BUTTONBOX (w))
382     return;
383
384   if ( GTK_IS_CONTAINER (w))
385     {
386       gtk_container_foreach (GTK_CONTAINER (w),
387                              connect_notify_signal,
388                              dialog);
389     }
390
391
392   /* It's unfortunate that GTK+ doesn't have a generic
393      "user-modified-state-changed" signal.  Instead, we have to try and
394      predict what widgets and signals are likely to exist in our dialogs. */
395
396   if ( GTK_IS_TOGGLE_BUTTON (w))
397     {
398       g_signal_connect_swapped (w, "toggled",
399                                 G_CALLBACK (psppire_dialog_notify_change),
400                                 dialog);
401     }
402
403   if ( PSPPIRE_IS_SELECTOR (w))
404     {
405       g_signal_connect_swapped (w, "selected",
406                                 G_CALLBACK (psppire_dialog_notify_change),
407                                 dialog);
408
409       g_signal_connect_swapped (w, "de-selected",
410                                 G_CALLBACK (psppire_dialog_notify_change),
411                                 dialog);
412     }
413
414   if ( GTK_IS_EDITABLE (w))
415     {
416       g_signal_connect_swapped (w, "changed",
417                                 G_CALLBACK (psppire_dialog_notify_change),
418                                 dialog);
419     }
420
421   if ( GTK_IS_CELL_EDITABLE (w))
422     {
423       g_signal_connect_swapped (w, "editing-done",
424                                 G_CALLBACK (psppire_dialog_notify_change),
425                                 dialog);
426     }
427
428   if ( GTK_IS_TEXT_VIEW (w))
429     {
430       GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
431
432       g_signal_connect_swapped (buffer, "changed",
433                                 G_CALLBACK (psppire_dialog_notify_change),
434                                 dialog);
435     }
436
437   if ( GTK_IS_TREE_VIEW (w))
438     {
439       gint i = 0;
440       GtkTreeView *tv = GTK_TREE_VIEW (w);
441       GtkTreeSelection *selection =
442         gtk_tree_view_get_selection (tv);
443       GtkTreeViewColumn *col;
444       GtkTreeModel *model = gtk_tree_view_get_model (tv);
445
446       if ( model)
447         {
448       g_signal_connect_swapped (model, "row-changed",
449                                 G_CALLBACK (psppire_dialog_notify_change),
450                                 dialog);
451
452       g_signal_connect_swapped (model, "row-deleted",
453                                 G_CALLBACK (psppire_dialog_notify_change),
454                                 dialog);
455
456       g_signal_connect_swapped (model, "row-inserted",
457                                 G_CALLBACK (psppire_dialog_notify_change),
458                                 dialog);
459         }
460
461       g_signal_connect_swapped (selection, "changed",
462                                 G_CALLBACK (psppire_dialog_notify_change),
463                                 dialog);
464
465       while ((col = gtk_tree_view_get_column (tv, i++)))
466         {
467           GList *renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (col));
468           GList *start = renderers;
469           while (renderers)
470             {
471               if ( GTK_IS_CELL_RENDERER_TOGGLE (renderers->data))
472                 g_signal_connect_swapped (renderers->data, "toggled",
473                                           G_CALLBACK (psppire_dialog_notify_change), dialog);
474               renderers = renderers->next;
475             }
476           g_list_free (start);
477         }
478     }
479 }
480
481
482 gint
483 psppire_dialog_run (PsppireDialog *dialog)
484 {
485   if ( dialog->contents_are_valid != NULL )
486     gtk_container_foreach (GTK_CONTAINER (dialog->box),
487                            connect_notify_signal,
488                            dialog);
489
490   dialog->loop = g_main_loop_new (NULL, FALSE);
491
492   gtk_widget_show (GTK_WIDGET (dialog));
493
494   if ( dialog->contents_are_valid != NULL)
495     g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, FALSE);
496
497   g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
498
499   gdk_threads_leave ();
500   g_main_loop_run (dialog->loop);
501   gdk_threads_enter ();
502
503   g_main_loop_unref (dialog->loop);
504
505   g_signal_emit (dialog, signals [RESPONSE], 0, dialog->response);
506
507   return dialog->response;
508 }
509
510
511 void
512 psppire_dialog_reload (PsppireDialog *dialog)
513 {
514   g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
515 }
516
517
518 void
519 psppire_dialog_help (PsppireDialog *dialog)
520 {
521   char *name = NULL;
522   g_object_get (dialog, "name", &name, NULL);
523
524   online_help (name);
525
526   g_signal_emit (dialog, signals [DIALOG_HELP], 0, name);
527 }
528
529
530 GType
531 psppire_orientation_get_type (void)
532 {
533   static GType etype = 0;
534   if (etype == 0)
535     {
536       static const GEnumValue values[] =
537         {
538           { PSPPIRE_HORIZONTAL, "PSPPIRE_HORIZONTAL", "Horizontal" },
539           { PSPPIRE_VERTICAL,   "PSPPIRE_VERTICAL",   "Vertical" },
540           { PSPPIRE_TABULAR,   "PSPPIRE_TABULAR",   "Tabular" },
541           { 0, NULL, NULL }
542         };
543
544       etype = g_enum_register_static
545         (g_intern_static_string ("PsppireOrientation"), values);
546
547     }
548   return etype;
549 }
550
551
552 /* Sets a predicate function that is checked after each change that the user
553    makes to the dialog's state.  If the predicate function returns false, then
554    "OK" and other buttons that accept the dialog's settings will be
555    disabled. */
556 void
557 psppire_dialog_set_valid_predicate (PsppireDialog *dialog,
558                                     ContentsAreValid contents_are_valid,
559                                     gpointer data)
560 {
561   dialog->contents_are_valid = contents_are_valid;
562   dialog->validity_data = data;
563 }
564
565 /* Sets a predicate function that is called after "OK" or another button that
566    accepts the dialog's settings is pushed.  If the predicate function returns
567    false, then the button push is ignored.  (If the predicate function returns
568    false, then it should take some action to notify the user why the contents
569    are unacceptable, e.g. pop up a dialog box.)
570
571    An accept predicate is preferred over a validity predicate when the reason
572    why the dialog settings are unacceptable may not be obvious to the user, so
573    that the user needs a helpful message to explain. */
574 void
575 psppire_dialog_set_accept_predicate (PsppireDialog *dialog,
576                                      ContentsAreValid contents_are_acceptable,
577                                      gpointer data)
578 {
579   dialog->contents_are_acceptable = contents_are_acceptable;
580   dialog->acceptable_data = data;
581 }
582
583 gboolean
584 psppire_dialog_is_acceptable (const PsppireDialog *dialog)
585 {
586   return (dialog->contents_are_acceptable == NULL
587           || dialog->contents_are_acceptable (dialog->acceptable_data));
588 }
589
590
591
592
593 static GObject *
594 get_internal_child    (GtkBuildable *buildable,
595                        GtkBuilder *builder,
596                        const gchar *childname)
597 {
598   PsppireDialog *dialog = PSPPIRE_DIALOG (buildable);
599
600   if ( 0 == strcmp (childname, "hbox"))
601     return G_OBJECT (dialog->box);
602
603   return NULL;
604 }
605
606
607
608 static void
609 psppire_dialog_buildable_init (GtkBuildableIface *iface)
610 {
611   iface->get_internal_child = get_internal_child;
612 }