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