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