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