Implemented the recode dialogs.
[pspp-builds.git] / 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 "psppire-dialog.h"
23 #include "psppire-buttonbox.h"
24 #include "psppire-selector.h"
25
26 static void psppire_dialog_class_init          (PsppireDialogClass *);
27 static void psppire_dialog_init                (PsppireDialog      *);
28
29
30 enum  {DIALOG_REFRESH,
31        VALIDITY_CHANGED,
32        n_SIGNALS};
33
34 static guint signals [n_SIGNALS];
35
36
37 GType
38 psppire_dialog_get_type (void)
39 {
40   static GType dialog_type = 0;
41
42   if (!dialog_type)
43     {
44       static const GTypeInfo dialog_info =
45       {
46         sizeof (PsppireDialogClass),
47         NULL, /* base_init */
48         NULL, /* base_finalize */
49         (GClassInitFunc) psppire_dialog_class_init,
50         NULL, /* class_finalize */
51         NULL, /* class_data */
52         sizeof (PsppireDialog),
53         0,
54         (GInstanceInitFunc) psppire_dialog_init,
55       };
56
57       dialog_type = g_type_register_static (GTK_TYPE_WINDOW,
58                                             "PsppireDialog", &dialog_info, 0);
59     }
60
61   return dialog_type;
62 }
63
64
65
66 static GObjectClass     *parent_class = NULL;
67
68
69 static void
70 psppire_dialog_finalize (GObject *object)
71 {
72   PsppireDialog *dialog ;
73
74   g_return_if_fail (object != NULL);
75   g_return_if_fail (PSPPIRE_IS_DIALOG (object));
76
77   dialog = PSPPIRE_DIALOG (object);
78
79   if (G_OBJECT_CLASS (parent_class)->finalize)
80     G_OBJECT_CLASS (parent_class)->finalize (object);
81 }
82
83
84
85 /* Properties */
86 enum
87 {
88   PROP_0,
89   PROP_ORIENTATION
90 };
91
92
93 static void
94 psppire_dialog_get_property (GObject         *object,
95                              guint            prop_id,
96                              GValue          *value,
97                              GParamSpec      *pspec)
98 {
99   PsppireDialog *dialog = PSPPIRE_DIALOG (object);
100
101   switch (prop_id)
102     {
103     case PROP_ORIENTATION:
104       {
105         if ( GTK_IS_VBOX (dialog->box) )
106           g_value_set_enum (value, PSPPIRE_VERTICAL);
107         else if ( GTK_IS_HBOX (dialog->box))
108           g_value_set_enum (value, PSPPIRE_HORIZONTAL);
109         else if ( GTK_IS_TABLE (dialog->box))
110           g_value_set_enum (value, PSPPIRE_TABULAR);
111       }
112       break;
113     default:
114       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
115       break;
116     };
117 }
118
119
120 static void
121 dialog_set_orientation (PsppireDialog *dialog, const GValue *orval)
122 {
123   PsppireOrientation orientation = g_value_get_enum (orval);
124
125   if ( dialog->box != NULL)
126     {
127       gtk_container_remove (GTK_CONTAINER (dialog), dialog->box);
128     }
129
130   switch ( orientation )
131     {
132     case PSPPIRE_HORIZONTAL:
133       dialog->box = gtk_hbox_new (FALSE, 5);
134       break;
135     case PSPPIRE_VERTICAL:
136       dialog->box = gtk_vbox_new (FALSE, 5);
137       break;
138     case PSPPIRE_TABULAR:
139       dialog->box = gtk_table_new (2, 3, FALSE);
140       g_object_set (dialog->box,
141                     "row-spacing", 5,
142                     "column-spacing", 5,
143                     NULL);
144       break;
145     }
146
147   gtk_container_add (GTK_CONTAINER (dialog), dialog->box);
148 }
149
150
151 static void
152 psppire_dialog_set_property (GObject         *object,
153                              guint            prop_id,
154                              const GValue    *value,
155                              GParamSpec      *pspec)
156
157 {
158   PsppireDialog *dialog = PSPPIRE_DIALOG (object);
159
160   switch (prop_id)
161     {
162     case PROP_ORIENTATION:
163       dialog_set_orientation (dialog, value);
164       break;
165     default:
166       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
167       break;
168     };
169 }
170
171
172 static GParamSpec *orientation_spec ;
173
174 static void
175 psppire_dialog_class_init (PsppireDialogClass *class)
176 {
177   GObjectClass *object_class = (GObjectClass *) class;
178
179
180   orientation_spec =
181     g_param_spec_enum ("orientation",
182                        "Orientation",
183                        "Which way widgets are packed",
184                        G_TYPE_PSPPIRE_ORIENTATION,
185                        PSPPIRE_HORIZONTAL /* default value */,
186                        G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
187
188
189   object_class->set_property = psppire_dialog_set_property;
190   object_class->get_property = psppire_dialog_get_property;
191
192   g_object_class_install_property (object_class,
193                                    PROP_ORIENTATION,
194                                    orientation_spec);
195
196
197
198   signals [DIALOG_REFRESH] =
199     g_signal_new ("refresh",
200                   G_TYPE_FROM_CLASS (class),
201                   G_SIGNAL_RUN_FIRST,
202                   0,
203                   NULL, NULL,
204                   g_cclosure_marshal_VOID__VOID,
205                   G_TYPE_NONE,
206                   0);
207
208
209   signals [VALIDITY_CHANGED] =
210     g_signal_new ("validity-changed",
211                   G_TYPE_FROM_CLASS (class),
212                   G_SIGNAL_RUN_FIRST,
213                   0,
214                   NULL, NULL,
215                   g_cclosure_marshal_VOID__BOOLEAN,
216                   G_TYPE_NONE,
217                   1,
218                   G_TYPE_BOOLEAN);
219
220
221   object_class->finalize = psppire_dialog_finalize;
222 }
223
224
225
226
227 static void
228 close_dialog (GtkWidget *w, gpointer data)
229 {
230   PsppireDialog *dialog = data;
231
232   psppire_dialog_close (dialog);
233 }
234
235 void
236 psppire_dialog_close (PsppireDialog *dialog)
237 {
238   g_main_loop_quit (dialog->loop);
239   gtk_widget_hide (GTK_WIDGET (dialog));
240 }
241
242
243 static void
244 delete_event_callback (GtkWidget *w, GdkEvent *e, gpointer data)
245 {
246   close_dialog (w, data);
247 }
248
249
250 static void
251 psppire_dialog_init (PsppireDialog *dialog)
252 {
253   GValue value = {0};
254   dialog->box = NULL;
255   dialog->contents_are_valid = NULL;
256   dialog->validity_data = NULL;
257
258   g_value_init (&value, orientation_spec->value_type);
259   g_param_value_set_default (orientation_spec, &value);
260
261   gtk_window_set_type_hint (GTK_WINDOW (dialog),
262         GDK_WINDOW_TYPE_HINT_DIALOG);
263
264   dialog_set_orientation (dialog, &value);
265
266   g_value_unset (&value);
267
268   g_signal_connect (G_OBJECT (dialog), "delete-event",
269                     G_CALLBACK (delete_event_callback),
270                     dialog);
271
272   gtk_window_set_type_hint (GTK_WINDOW (dialog),
273         GDK_WINDOW_TYPE_HINT_DIALOG);
274
275   gtk_widget_show_all (dialog->box);
276 }
277
278
279 GtkWidget*
280 psppire_dialog_new (void)
281 {
282   PsppireDialog *dialog ;
283
284   dialog = g_object_new (psppire_dialog_get_type (), NULL);
285
286   return GTK_WIDGET (dialog) ;
287 }
288
289
290 void
291 psppire_dialog_notify_change (PsppireDialog *dialog)
292 {
293   if ( dialog->contents_are_valid )
294     {
295       gboolean valid = dialog->contents_are_valid (dialog->validity_data);
296
297       g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, valid);
298     }
299 }
300
301
302 /* Descend the widget tree, connecting appropriate signals to the
303    psppire_dialog_notify_change callback */
304 static void
305 connect_notify_signal (GtkWidget *w, gpointer data)
306 {
307   PsppireDialog *dialog = data;
308
309   if ( PSPPIRE_IS_BUTTONBOX (w))
310     return;
311
312
313
314   if ( GTK_IS_CONTAINER (w))
315     {
316       gtk_container_foreach (GTK_CONTAINER (w),
317                              connect_notify_signal,
318                              dialog);
319     }
320
321
322   /* It's unfortunate that GTK+ doesn't have a generic
323      "user-modified-state-changed" signal.  Instead, we have to try and
324      predict what widgets and signals are likely to exist in our dialogs. */
325
326   if ( GTK_IS_TOGGLE_BUTTON (w))
327     {
328       g_signal_connect_swapped (w, "toggled",
329                                 G_CALLBACK (psppire_dialog_notify_change),
330                                 dialog);
331     }
332
333   if ( PSPPIRE_IS_SELECTOR (w))
334     {
335       g_signal_connect_swapped (w, "selected",
336                                 G_CALLBACK (psppire_dialog_notify_change),
337                                 dialog);
338
339       g_signal_connect_swapped (w, "de-selected",
340                                 G_CALLBACK (psppire_dialog_notify_change),
341                                 dialog);
342     }
343
344   if ( GTK_IS_EDITABLE (w))
345     {
346       g_signal_connect_swapped (w, "changed",
347                                 G_CALLBACK (psppire_dialog_notify_change),
348                                 dialog);
349     }
350
351   if ( GTK_IS_CELL_EDITABLE (w))
352     {
353       g_signal_connect_swapped (w, "editing-done",
354                                 G_CALLBACK (psppire_dialog_notify_change),
355                                 dialog);
356     }
357
358   if ( GTK_IS_TEXT_VIEW (w))
359     {
360       GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
361
362       g_signal_connect_swapped (buffer, "changed",
363                                 G_CALLBACK (psppire_dialog_notify_change),
364                                 dialog);
365     }
366
367   if ( GTK_IS_TREE_VIEW (w))
368     {
369       gint i = 0;
370       GtkTreeView *tv = GTK_TREE_VIEW (w);
371       GtkTreeSelection *selection =
372         gtk_tree_view_get_selection (tv);
373       GtkTreeViewColumn *col;
374       GtkTreeModel *model = gtk_tree_view_get_model (tv);
375
376       g_signal_connect_swapped (model, "row-changed",
377                                 G_CALLBACK (psppire_dialog_notify_change),
378                                 dialog);
379
380       g_signal_connect_swapped (model, "row-deleted",
381                                 G_CALLBACK (psppire_dialog_notify_change),
382                                 dialog);
383
384       g_signal_connect_swapped (model, "row-inserted",
385                                 G_CALLBACK (psppire_dialog_notify_change),
386                                 dialog);
387
388       g_signal_connect_swapped (selection, "changed",
389                                 G_CALLBACK (psppire_dialog_notify_change),
390                                 dialog);
391
392       while ((col = gtk_tree_view_get_column (tv, i++)))
393         {
394           GList *renderers = gtk_tree_view_column_get_cell_renderers (col);
395           GList *start = renderers;
396           while (renderers)
397             {
398               if ( GTK_IS_CELL_RENDERER_TOGGLE (renderers->data))
399                 g_signal_connect_swapped (renderers->data, "toggled",
400                                           G_CALLBACK (psppire_dialog_notify_change), dialog);
401               renderers = renderers->next;
402             }
403           g_list_free (start);
404         }
405     }
406 }
407
408
409 gint
410 psppire_dialog_run (PsppireDialog *dialog)
411 {
412   if ( dialog->contents_are_valid != NULL )
413     gtk_container_foreach (GTK_CONTAINER (dialog->box),
414                            connect_notify_signal,
415                            dialog);
416
417   dialog->loop = g_main_loop_new (NULL, FALSE);
418
419   gtk_widget_show (GTK_WIDGET (dialog));
420
421   if ( dialog->contents_are_valid != NULL)
422     g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, FALSE);
423
424   g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
425
426   g_main_loop_run (dialog->loop);
427
428   return dialog->response;
429 }
430
431
432 void
433 psppire_dialog_reload (PsppireDialog *dialog)
434 {
435   g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
436 }
437
438
439
440
441 GType
442 psppire_orientation_get_type (void)
443 {
444   static GType etype = 0;
445   if (etype == 0)
446     {
447       static const GEnumValue values[] =
448         {
449           { PSPPIRE_HORIZONTAL, "PSPPIRE_HORIZONTAL", "Horizontal" },
450           { PSPPIRE_VERTICAL,   "PSPPIRE_VERTICAL",   "Vertical" },
451           { PSPPIRE_TABULAR,   "PSPPIRE_TABULAR",   "Tabular" },
452           { 0, NULL, NULL }
453         };
454
455       etype = g_enum_register_static
456         (g_intern_static_string ("PsppireOrientation"), values);
457
458     }
459   return etype;
460 }
461
462
463 void
464 psppire_dialog_set_valid_predicate (PsppireDialog *dialog,
465                                     ContentsAreValid contents_are_valid,
466                                     gpointer data)
467 {
468   dialog->contents_are_valid = contents_are_valid;
469   dialog->validity_data = data;
470 }
471
472