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