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