Merge "output" into "master"
[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 #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   gchar *base = NULL;
300
301   PsppireConf *conf = psppire_conf_new ();
302
303   if ( ! GTK_WIDGET_MAPPED (dialog))
304     return FALSE;
305
306   g_object_get (dialog, "name", &base, NULL);
307
308   psppire_conf_save_window_geometry (conf, base, event);
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 = NULL;
320
321   g_object_get (dialog, "name", &base, NULL);
322
323   psppire_conf_set_window_geometry (conf, base, dialog);
324 }
325
326
327
328 static void
329 psppire_dialog_init (PsppireDialog *dialog)
330 {
331   GValue value = {0};
332   dialog->box = NULL;
333   dialog->contents_are_valid = NULL;
334   dialog->validity_data = NULL;
335   dialog->slidable = FALSE;
336
337   g_value_init (&value, orientation_spec->value_type);
338   g_param_value_set_default (orientation_spec, &value);
339
340   gtk_window_set_type_hint (GTK_WINDOW (dialog),
341         GDK_WINDOW_TYPE_HINT_DIALOG);
342
343   g_value_unset (&value);
344
345   g_signal_connect (dialog, "delete-event",
346                     G_CALLBACK (delete_event_callback),
347                     dialog);
348
349   g_signal_connect (dialog, "configure-event",
350                     G_CALLBACK (configure_event_callback),
351                     dialog);
352
353   g_signal_connect (dialog, "realize",
354                     G_CALLBACK (on_realize),
355                     dialog);
356
357
358   gtk_window_set_type_hint (GTK_WINDOW (dialog),
359         GDK_WINDOW_TYPE_HINT_DIALOG);
360
361   g_object_set (dialog, "icon-name", "psppicon", NULL);
362 }
363
364
365 GtkWidget*
366 psppire_dialog_new (void)
367 {
368   PsppireDialog *dialog ;
369
370   dialog = g_object_new (psppire_dialog_get_type (),
371                          NULL);
372
373   return GTK_WIDGET (dialog) ;
374 }
375
376
377 void
378 psppire_dialog_notify_change (PsppireDialog *dialog)
379 {
380   if ( dialog->contents_are_valid )
381     {
382       gboolean valid = dialog->contents_are_valid (dialog->validity_data);
383
384       g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, valid);
385     }
386 }
387
388
389 /* Descend the widget tree, connecting appropriate signals to the
390    psppire_dialog_notify_change callback */
391 static void
392 connect_notify_signal (GtkWidget *w, gpointer data)
393 {
394   PsppireDialog *dialog = data;
395
396   if ( PSPPIRE_IS_BUTTONBOX (w))
397     return;
398
399
400
401   if ( GTK_IS_CONTAINER (w))
402     {
403       gtk_container_foreach (GTK_CONTAINER (w),
404                              connect_notify_signal,
405                              dialog);
406     }
407
408
409   /* It's unfortunate that GTK+ doesn't have a generic
410      "user-modified-state-changed" signal.  Instead, we have to try and
411      predict what widgets and signals are likely to exist in our dialogs. */
412
413   if ( GTK_IS_TOGGLE_BUTTON (w))
414     {
415       g_signal_connect_swapped (w, "toggled",
416                                 G_CALLBACK (psppire_dialog_notify_change),
417                                 dialog);
418     }
419
420   if ( PSPPIRE_IS_SELECTOR (w))
421     {
422       g_signal_connect_swapped (w, "selected",
423                                 G_CALLBACK (psppire_dialog_notify_change),
424                                 dialog);
425
426       g_signal_connect_swapped (w, "de-selected",
427                                 G_CALLBACK (psppire_dialog_notify_change),
428                                 dialog);
429     }
430
431   if ( GTK_IS_EDITABLE (w))
432     {
433       g_signal_connect_swapped (w, "changed",
434                                 G_CALLBACK (psppire_dialog_notify_change),
435                                 dialog);
436     }
437
438   if ( GTK_IS_CELL_EDITABLE (w))
439     {
440       g_signal_connect_swapped (w, "editing-done",
441                                 G_CALLBACK (psppire_dialog_notify_change),
442                                 dialog);
443     }
444
445   if ( GTK_IS_TEXT_VIEW (w))
446     {
447       GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
448
449       g_signal_connect_swapped (buffer, "changed",
450                                 G_CALLBACK (psppire_dialog_notify_change),
451                                 dialog);
452     }
453
454   if ( GTK_IS_TREE_VIEW (w))
455     {
456       gint i = 0;
457       GtkTreeView *tv = GTK_TREE_VIEW (w);
458       GtkTreeSelection *selection =
459         gtk_tree_view_get_selection (tv);
460       GtkTreeViewColumn *col;
461       GtkTreeModel *model = gtk_tree_view_get_model (tv);
462
463       if ( model)
464         {
465       g_signal_connect_swapped (model, "row-changed",
466                                 G_CALLBACK (psppire_dialog_notify_change),
467                                 dialog);
468
469       g_signal_connect_swapped (model, "row-deleted",
470                                 G_CALLBACK (psppire_dialog_notify_change),
471                                 dialog);
472
473       g_signal_connect_swapped (model, "row-inserted",
474                                 G_CALLBACK (psppire_dialog_notify_change),
475                                 dialog);
476         }
477
478       g_signal_connect_swapped (selection, "changed",
479                                 G_CALLBACK (psppire_dialog_notify_change),
480                                 dialog);
481
482       while ((col = gtk_tree_view_get_column (tv, i++)))
483         {
484           GList *renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (col));
485           GList *start = renderers;
486           while (renderers)
487             {
488               if ( GTK_IS_CELL_RENDERER_TOGGLE (renderers->data))
489                 g_signal_connect_swapped (renderers->data, "toggled",
490                                           G_CALLBACK (psppire_dialog_notify_change), dialog);
491               renderers = renderers->next;
492             }
493           g_list_free (start);
494         }
495     }
496 }
497
498
499 gint
500 psppire_dialog_run (PsppireDialog *dialog)
501 {
502   if ( dialog->contents_are_valid != NULL )
503     gtk_container_foreach (GTK_CONTAINER (dialog->box),
504                            connect_notify_signal,
505                            dialog);
506
507   dialog->loop = g_main_loop_new (NULL, FALSE);
508
509   gtk_widget_show (GTK_WIDGET (dialog));
510
511   if ( dialog->contents_are_valid != NULL)
512     g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, FALSE);
513
514   g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
515
516   gdk_threads_leave ();
517   g_main_loop_run (dialog->loop);
518   gdk_threads_enter ();
519
520   g_main_loop_unref (dialog->loop);
521
522   return dialog->response;
523 }
524
525
526 void
527 psppire_dialog_reload (PsppireDialog *dialog)
528 {
529   g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
530 }
531
532
533
534
535 GType
536 psppire_orientation_get_type (void)
537 {
538   static GType etype = 0;
539   if (etype == 0)
540     {
541       static const GEnumValue values[] =
542         {
543           { PSPPIRE_HORIZONTAL, "PSPPIRE_HORIZONTAL", "Horizontal" },
544           { PSPPIRE_VERTICAL,   "PSPPIRE_VERTICAL",   "Vertical" },
545           { PSPPIRE_TABULAR,   "PSPPIRE_TABULAR",   "Tabular" },
546           { 0, NULL, NULL }
547         };
548
549       etype = g_enum_register_static
550         (g_intern_static_string ("PsppireOrientation"), values);
551
552     }
553   return etype;
554 }
555
556
557 void
558 psppire_dialog_set_valid_predicate (PsppireDialog *dialog,
559                                     ContentsAreValid contents_are_valid,
560                                     gpointer data)
561 {
562   dialog->contents_are_valid = contents_are_valid;
563   dialog->validity_data = data;
564 }
565
566
567
568
569
570 static GObject *
571 get_internal_child    (GtkBuildable *buildable,
572                        GtkBuilder *builder,
573                        const gchar *childname)
574 {
575   PsppireDialog *dialog = PSPPIRE_DIALOG (buildable);
576
577   if ( 0 == strcmp (childname, "hbox"))
578     return G_OBJECT (dialog->box);
579
580   return NULL;
581 }
582
583
584
585 static void
586 psppire_dialog_buildable_init (GtkBuildableIface *iface)
587 {
588   iface->get_internal_child = get_internal_child;
589 }