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