Merge commit 'origin/stable'
[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 <string.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
78   return dialog_type;
79 }
80
81
82
83 static GObjectClass     *parent_class = NULL;
84
85
86 static void
87 psppire_dialog_finalize (GObject *object)
88 {
89   PsppireDialog *dialog ;
90
91   g_return_if_fail (object != NULL);
92   g_return_if_fail (PSPPIRE_IS_DIALOG (object));
93
94   dialog = PSPPIRE_DIALOG (object);
95
96   if (G_OBJECT_CLASS (parent_class)->finalize)
97     G_OBJECT_CLASS (parent_class)->finalize (object);
98 }
99
100
101
102 /* Properties */
103 enum
104 {
105   PROP_0,
106   PROP_ORIENTATION
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) )
123           g_value_set_enum (value, PSPPIRE_VERTICAL);
124         else if ( GTK_IS_HBOX (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     default:
131       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
132       break;
133     };
134 }
135
136
137 static void
138 dialog_set_orientation (PsppireDialog *dialog, const GValue *orval)
139 {
140   PsppireOrientation orientation = g_value_get_enum (orval);
141
142   if ( dialog->box != NULL)
143     {
144       gtk_container_remove (GTK_CONTAINER (dialog), dialog->box);
145     }
146
147   switch ( orientation )
148     {
149     case PSPPIRE_HORIZONTAL:
150       dialog->box = gtk_hbox_new (FALSE, 5);
151       break;
152     case PSPPIRE_VERTICAL:
153       dialog->box = gtk_vbox_new (FALSE, 5);
154       break;
155     case PSPPIRE_TABULAR:
156       dialog->box = gtk_table_new (2, 3, FALSE);
157       g_object_set (dialog->box,
158                     "row-spacing", 5,
159                     "column-spacing", 5,
160                     NULL);
161       break;
162     }
163
164   gtk_container_add (GTK_CONTAINER (dialog), dialog->box);
165 }
166
167
168 static void
169 psppire_dialog_set_property (GObject         *object,
170                              guint            prop_id,
171                              const GValue    *value,
172                              GParamSpec      *pspec)
173
174 {
175   PsppireDialog *dialog = PSPPIRE_DIALOG (object);
176
177   switch (prop_id)
178     {
179     case PROP_ORIENTATION:
180       dialog_set_orientation (dialog, value);
181       break;
182     default:
183       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
184       break;
185     };
186 }
187
188
189 static GParamSpec *orientation_spec ;
190
191 static void
192 psppire_dialog_class_init (PsppireDialogClass *class)
193 {
194   GObjectClass *object_class = (GObjectClass *) class;
195
196
197   orientation_spec =
198     g_param_spec_enum ("orientation",
199                        "Orientation",
200                        "Which way widgets are packed",
201                        G_TYPE_PSPPIRE_ORIENTATION,
202                        PSPPIRE_HORIZONTAL /* default value */,
203                        G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
204
205
206   object_class->set_property = psppire_dialog_set_property;
207   object_class->get_property = psppire_dialog_get_property;
208
209   g_object_class_install_property (object_class,
210                                    PROP_ORIENTATION,
211                                    orientation_spec);
212
213
214
215   signals [DIALOG_REFRESH] =
216     g_signal_new ("refresh",
217                   G_TYPE_FROM_CLASS (class),
218                   G_SIGNAL_RUN_FIRST,
219                   0,
220                   NULL, NULL,
221                   g_cclosure_marshal_VOID__VOID,
222                   G_TYPE_NONE,
223                   0);
224
225
226   signals [VALIDITY_CHANGED] =
227     g_signal_new ("validity-changed",
228                   G_TYPE_FROM_CLASS (class),
229                   G_SIGNAL_RUN_FIRST,
230                   0,
231                   NULL, NULL,
232                   g_cclosure_marshal_VOID__BOOLEAN,
233                   G_TYPE_NONE,
234                   1,
235                   G_TYPE_BOOLEAN);
236
237
238   object_class->finalize = psppire_dialog_finalize;
239 }
240
241
242
243
244 static void
245 close_dialog (GtkWidget *w, gpointer data)
246 {
247   PsppireDialog *dialog = data;
248
249   psppire_dialog_close (dialog);
250 }
251
252 void
253 psppire_dialog_close (PsppireDialog *dialog)
254 {
255   g_main_loop_quit (dialog->loop);
256   gtk_widget_hide (GTK_WIDGET (dialog));
257 }
258
259
260 static void
261 delete_event_callback (GtkWidget *w, GdkEvent *e, gpointer data)
262 {
263   close_dialog (w, data);
264 }
265
266
267 static void
268 psppire_dialog_init (PsppireDialog *dialog)
269 {
270   GValue value = {0};
271   dialog->box = NULL;
272   dialog->contents_are_valid = NULL;
273   dialog->validity_data = NULL;
274
275   g_value_init (&value, orientation_spec->value_type);
276   g_param_value_set_default (orientation_spec, &value);
277
278   gtk_window_set_type_hint (GTK_WINDOW (dialog),
279         GDK_WINDOW_TYPE_HINT_DIALOG);
280
281   dialog_set_orientation (dialog, &value);
282
283   g_value_unset (&value);
284
285   g_signal_connect (G_OBJECT (dialog), "delete-event",
286                     G_CALLBACK (delete_event_callback),
287                     dialog);
288
289   gtk_window_set_type_hint (GTK_WINDOW (dialog),
290         GDK_WINDOW_TYPE_HINT_DIALOG);
291
292   gtk_widget_show_all (dialog->box);
293 }
294
295
296 GtkWidget*
297 psppire_dialog_new (void)
298 {
299   PsppireDialog *dialog ;
300
301   dialog = g_object_new (psppire_dialog_get_type (), NULL);
302
303   return GTK_WIDGET (dialog) ;
304 }
305
306
307 void
308 psppire_dialog_notify_change (PsppireDialog *dialog)
309 {
310   if ( dialog->contents_are_valid )
311     {
312       gboolean valid = dialog->contents_are_valid (dialog->validity_data);
313
314       g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, valid);
315     }
316 }
317
318
319 /* Descend the widget tree, connecting appropriate signals to the
320    psppire_dialog_notify_change callback */
321 static void
322 connect_notify_signal (GtkWidget *w, gpointer data)
323 {
324   PsppireDialog *dialog = data;
325
326   if ( PSPPIRE_IS_BUTTONBOX (w))
327     return;
328
329
330
331   if ( GTK_IS_CONTAINER (w))
332     {
333       gtk_container_foreach (GTK_CONTAINER (w),
334                              connect_notify_signal,
335                              dialog);
336     }
337
338
339   /* It's unfortunate that GTK+ doesn't have a generic
340      "user-modified-state-changed" signal.  Instead, we have to try and
341      predict what widgets and signals are likely to exist in our dialogs. */
342
343   if ( GTK_IS_TOGGLE_BUTTON (w))
344     {
345       g_signal_connect_swapped (w, "toggled",
346                                 G_CALLBACK (psppire_dialog_notify_change),
347                                 dialog);
348     }
349
350   if ( PSPPIRE_IS_SELECTOR (w))
351     {
352       g_signal_connect_swapped (w, "selected",
353                                 G_CALLBACK (psppire_dialog_notify_change),
354                                 dialog);
355
356       g_signal_connect_swapped (w, "de-selected",
357                                 G_CALLBACK (psppire_dialog_notify_change),
358                                 dialog);
359     }
360
361   if ( GTK_IS_EDITABLE (w))
362     {
363       g_signal_connect_swapped (w, "changed",
364                                 G_CALLBACK (psppire_dialog_notify_change),
365                                 dialog);
366     }
367
368   if ( GTK_IS_CELL_EDITABLE (w))
369     {
370       g_signal_connect_swapped (w, "editing-done",
371                                 G_CALLBACK (psppire_dialog_notify_change),
372                                 dialog);
373     }
374
375   if ( GTK_IS_TEXT_VIEW (w))
376     {
377       GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
378
379       g_signal_connect_swapped (buffer, "changed",
380                                 G_CALLBACK (psppire_dialog_notify_change),
381                                 dialog);
382     }
383
384   if ( GTK_IS_TREE_VIEW (w))
385     {
386       gint i = 0;
387       GtkTreeView *tv = GTK_TREE_VIEW (w);
388       GtkTreeSelection *selection =
389         gtk_tree_view_get_selection (tv);
390       GtkTreeViewColumn *col;
391       GtkTreeModel *model = gtk_tree_view_get_model (tv);
392
393       if ( model)
394         {
395       g_signal_connect_swapped (model, "row-changed",
396                                 G_CALLBACK (psppire_dialog_notify_change),
397                                 dialog);
398
399       g_signal_connect_swapped (model, "row-deleted",
400                                 G_CALLBACK (psppire_dialog_notify_change),
401                                 dialog);
402
403       g_signal_connect_swapped (model, "row-inserted",
404                                 G_CALLBACK (psppire_dialog_notify_change),
405                                 dialog);
406         }
407
408       g_signal_connect_swapped (selection, "changed",
409                                 G_CALLBACK (psppire_dialog_notify_change),
410                                 dialog);
411
412       while ((col = gtk_tree_view_get_column (tv, i++)))
413         {
414           GList *renderers = gtk_tree_view_column_get_cell_renderers (col);
415           GList *start = renderers;
416           while (renderers)
417             {
418               if ( GTK_IS_CELL_RENDERER_TOGGLE (renderers->data))
419                 g_signal_connect_swapped (renderers->data, "toggled",
420                                           G_CALLBACK (psppire_dialog_notify_change), dialog);
421               renderers = renderers->next;
422             }
423           g_list_free (start);
424         }
425     }
426 }
427
428
429 gint
430 psppire_dialog_run (PsppireDialog *dialog)
431 {
432   if ( dialog->contents_are_valid != NULL )
433     gtk_container_foreach (GTK_CONTAINER (dialog->box),
434                            connect_notify_signal,
435                            dialog);
436
437   dialog->loop = g_main_loop_new (NULL, FALSE);
438
439   gtk_widget_show (GTK_WIDGET (dialog));
440
441   if ( dialog->contents_are_valid != NULL)
442     g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, FALSE);
443
444   g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
445
446   g_main_loop_run (dialog->loop);
447
448   g_main_loop_unref (dialog->loop);
449
450   return dialog->response;
451 }
452
453
454 void
455 psppire_dialog_reload (PsppireDialog *dialog)
456 {
457   g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
458 }
459
460
461
462
463 GType
464 psppire_orientation_get_type (void)
465 {
466   static GType etype = 0;
467   if (etype == 0)
468     {
469       static const GEnumValue values[] =
470         {
471           { PSPPIRE_HORIZONTAL, "PSPPIRE_HORIZONTAL", "Horizontal" },
472           { PSPPIRE_VERTICAL,   "PSPPIRE_VERTICAL",   "Vertical" },
473           { PSPPIRE_TABULAR,   "PSPPIRE_TABULAR",   "Tabular" },
474           { 0, NULL, NULL }
475         };
476
477       etype = g_enum_register_static
478         (g_intern_static_string ("PsppireOrientation"), values);
479
480     }
481   return etype;
482 }
483
484
485 void
486 psppire_dialog_set_valid_predicate (PsppireDialog *dialog,
487                                     ContentsAreValid contents_are_valid,
488                                     gpointer data)
489 {
490   dialog->contents_are_valid = contents_are_valid;
491   dialog->validity_data = data;
492 }
493
494
495
496
497
498 static GObject *
499 get_internal_child    (GtkBuildable *buildable,
500                        GtkBuilder *builder,
501                        const gchar *childname)
502 {
503   PsppireDialog *dialog = PSPPIRE_DIALOG (buildable);
504
505   if ( 0 == strcmp (childname, "hbox"))
506     return G_OBJECT (dialog->box);
507
508   return NULL;
509 }
510
511
512
513 static void
514 psppire_dialog_buildable_init (GtkBuildableIface *iface)
515 {
516   iface->get_internal_child = get_internal_child;
517 }