New abstract class PsppireDialogAction
[pspp-builds.git] / src / ui / gui / psppire-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 2010, 2011, 2012  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 "psppire-dialog.h"
22 #include "psppire-buttonbox.h"
23 #include "psppire-selector.h"
24 #include "psppire-conf.h"
25 #include <string.h>
26 #include "builder-wrapper.h"
27 #include "help-menu.h"
28
29 static void psppire_dialog_class_init          (PsppireDialogClass *);
30 static void psppire_dialog_init                (PsppireDialog      *);
31
32
33 enum  {DIALOG_REFRESH,
34        RESPONSE,
35        VALIDITY_CHANGED,
36        DIALOG_HELP,
37        n_SIGNALS};
38
39 static guint signals [n_SIGNALS];
40
41
42 static void psppire_dialog_buildable_init (GtkBuildableIface *iface);
43
44
45 GType
46 psppire_dialog_get_type (void)
47 {
48   static GType dialog_type = 0;
49
50   if (!dialog_type)
51     {
52       static const GTypeInfo dialog_info =
53       {
54         sizeof (PsppireDialogClass),
55         NULL, /* base_init */
56         NULL, /* base_finalize */
57         (GClassInitFunc) psppire_dialog_class_init,
58         NULL, /* class_finalize */
59         NULL, /* class_data */
60         sizeof (PsppireDialog),
61         0,
62         (GInstanceInitFunc) psppire_dialog_init,
63       };
64
65       static const GInterfaceInfo buildable_info =
66       {
67         (GInterfaceInitFunc) psppire_dialog_buildable_init,
68         NULL,
69         NULL
70       };
71
72       dialog_type = g_type_register_static (GTK_TYPE_WINDOW,
73                                             "PsppireDialog", &dialog_info, 0);
74
75       g_type_add_interface_static (dialog_type,
76                                    GTK_TYPE_BUILDABLE,
77                                    &buildable_info);
78     }
79
80   return dialog_type;
81 }
82
83
84
85 static GObjectClass     *parent_class = NULL;
86
87
88 static void
89 psppire_dialog_finalize (GObject *object)
90 {
91   PsppireDialog *dialog ;
92
93   g_return_if_fail (object != NULL);
94   g_return_if_fail (PSPPIRE_IS_DIALOG (object));
95
96   dialog = PSPPIRE_DIALOG (object);
97
98   if (G_OBJECT_CLASS (parent_class)->finalize)
99     G_OBJECT_CLASS (parent_class)->finalize (object);
100 }
101
102
103
104 /* Properties */
105 enum
106 {
107   PROP_0,
108   PROP_ORIENTATION,
109   PROP_SLIDING
110 };
111
112
113 static void
114 psppire_dialog_get_property (GObject         *object,
115                              guint            prop_id,
116                              GValue          *value,
117                              GParamSpec      *pspec)
118 {
119   PsppireDialog *dialog = PSPPIRE_DIALOG (object);
120
121   switch (prop_id)
122     {
123     case PROP_ORIENTATION:
124       {
125         if ( GTK_IS_VBOX (dialog->box) || GTK_VPANED (dialog->box))
126           g_value_set_enum (value, PSPPIRE_VERTICAL);
127         else if ( GTK_IS_HBOX (dialog->box) || GTK_HPANED (dialog->box))
128           g_value_set_enum (value, PSPPIRE_HORIZONTAL);
129         else if ( GTK_IS_TABLE (dialog->box))
130           g_value_set_enum (value, PSPPIRE_TABULAR);
131       }
132       break;
133     case PROP_SLIDING:
134       g_value_set_boolean (value, dialog->slidable);
135       break;
136     default:
137       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
138       break;
139     };
140 }
141
142
143 static void
144 dialog_set_container (PsppireDialog *dialog)
145 {
146   if ( dialog->box != NULL)
147     {
148       gtk_container_remove (GTK_CONTAINER (dialog), dialog->box);
149     }
150
151   switch (dialog->orientation)
152     {
153     case PSPPIRE_HORIZONTAL:
154       if ( dialog->slidable)
155         dialog->box = gtk_hpaned_new();
156       else
157         dialog->box = gtk_hbox_new (FALSE, 5);
158       break;
159     case PSPPIRE_VERTICAL:
160       if ( dialog->slidable)
161         dialog->box = gtk_vpaned_new();
162       else
163         dialog->box = gtk_vbox_new (FALSE, 5);
164       break;
165     case PSPPIRE_TABULAR:
166       dialog->box = gtk_table_new (2, 3, FALSE);
167       g_object_set (dialog->box,
168                     "row-spacing", 5,
169                     "column-spacing", 5,
170                     NULL);
171       break;
172     }
173
174   gtk_widget_show_all (dialog->box);
175   gtk_container_add (GTK_CONTAINER (dialog), dialog->box);
176 }
177
178
179 static void
180 psppire_dialog_set_property (GObject         *object,
181                              guint            prop_id,
182                              const GValue    *value,
183                              GParamSpec      *pspec)
184
185 {
186   PsppireDialog *dialog = PSPPIRE_DIALOG (object);
187
188   switch (prop_id)
189     {
190     case PROP_SLIDING:
191       dialog->slidable = g_value_get_boolean (value);
192       break;
193     case PROP_ORIENTATION:
194       dialog->orientation = g_value_get_enum (value);
195       break;
196     default:
197       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
198       break;
199     };
200
201   dialog_set_container (dialog);
202 }
203
204
205 static GParamSpec *orientation_spec ;
206
207 static void
208 psppire_dialog_class_init (PsppireDialogClass *class)
209 {
210   GObjectClass *object_class = (GObjectClass *) class;
211
212   GParamSpec *sliding_spec ;
213
214   orientation_spec =
215     g_param_spec_enum ("orientation",
216                        "Orientation",
217                        "Which way widgets are packed",
218                        PSPPIRE_TYPE_ORIENTATION,
219                        PSPPIRE_HORIZONTAL /* default value */,
220                        G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
221
222   sliding_spec =
223     g_param_spec_boolean ("slidable",
224                           "Slidable",
225                           "Can the container be sized by the user",
226                           FALSE,
227                           G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
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   signals [DIALOG_REFRESH] =
242     g_signal_new ("refresh",
243                   G_TYPE_FROM_CLASS (class),
244                   G_SIGNAL_RUN_FIRST,
245                   0,
246                   NULL, NULL,
247                   g_cclosure_marshal_VOID__VOID,
248                   G_TYPE_NONE,
249                   0);
250
251
252   signals [RESPONSE] =
253     g_signal_new ("response",
254                   G_TYPE_FROM_CLASS (class),
255                   G_SIGNAL_RUN_FIRST,
256                   0,
257                   NULL, NULL,
258                   g_cclosure_marshal_VOID__INT,
259                   G_TYPE_NONE,
260                   1,
261                   G_TYPE_INT);
262
263
264   signals [VALIDITY_CHANGED] =
265     g_signal_new ("validity-changed",
266                   G_TYPE_FROM_CLASS (class),
267                   G_SIGNAL_RUN_FIRST,
268                   0,
269                   NULL, NULL,
270                   g_cclosure_marshal_VOID__BOOLEAN,
271                   G_TYPE_NONE,
272                   1,
273                   G_TYPE_BOOLEAN);
274
275
276   signals [DIALOG_HELP] =
277     g_signal_new ("help",
278                   G_TYPE_FROM_CLASS (class),
279                   G_SIGNAL_RUN_FIRST,
280                   0,
281                   NULL, NULL,
282                   g_cclosure_marshal_VOID__STRING,
283                   G_TYPE_NONE,
284                   1,
285                   G_TYPE_STRING);
286
287
288   object_class->finalize = psppire_dialog_finalize;
289 }
290
291
292
293
294 static void
295 close_dialog (GtkWidget *w, gpointer data)
296 {
297   PsppireDialog *dialog = data;
298
299   psppire_dialog_close (dialog);
300 }
301
302 void
303 psppire_dialog_close (PsppireDialog *dialog)
304 {
305   g_main_loop_quit (dialog->loop);
306   gtk_widget_hide (GTK_WIDGET (dialog));
307 }
308
309
310 static void
311 delete_event_callback (GtkWidget *w, GdkEvent *e, gpointer data)
312 {
313   close_dialog (w, data);
314 }
315
316
317 static gboolean
318 configure_event_callback (GtkDialog *dialog,
319                           GdkEvent *event, gpointer data)
320 {
321   const gchar *base;
322
323   PsppireConf *conf = psppire_conf_new ();
324
325   if ( ! gtk_widget_get_mapped (GTK_WIDGET (dialog)))
326     return FALSE;
327
328   base = gtk_buildable_get_name (GTK_BUILDABLE (dialog));
329
330   psppire_conf_save_window_geometry (conf, base, GTK_WINDOW (dialog));
331
332   return FALSE;
333 }
334
335
336 static void
337 on_realize (GtkWindow *dialog, gpointer data)
338 {
339   PsppireConf *conf = psppire_conf_new ();
340
341   const gchar *base = gtk_buildable_get_name (GTK_BUILDABLE (dialog));
342
343   psppire_conf_set_window_geometry (conf, base, dialog);
344 }
345
346
347
348 static void
349 psppire_dialog_init (PsppireDialog *dialog)
350 {
351   GValue value = {0};
352   dialog->box = NULL;
353   dialog->contents_are_valid = NULL;
354   dialog->validity_data = NULL;
355   dialog->slidable = FALSE;
356
357   g_value_init (&value, orientation_spec->value_type);
358   g_param_value_set_default (orientation_spec, &value);
359
360   gtk_window_set_type_hint (GTK_WINDOW (dialog),
361         GDK_WINDOW_TYPE_HINT_DIALOG);
362
363   g_value_unset (&value);
364
365   g_signal_connect (dialog, "delete-event",
366                     G_CALLBACK (delete_event_callback),
367                     dialog);
368
369   g_signal_connect (dialog, "configure-event",
370                     G_CALLBACK (configure_event_callback),
371                     dialog);
372
373   g_signal_connect (dialog, "realize",
374                     G_CALLBACK (on_realize),
375                     dialog);
376
377
378   gtk_window_set_type_hint (GTK_WINDOW (dialog),
379         GDK_WINDOW_TYPE_HINT_DIALOG);
380
381   g_object_set (dialog, "icon-name", "pspp", NULL);
382 }
383
384
385 GtkWidget*
386 psppire_dialog_new (void)
387 {
388   PsppireDialog *dialog ;
389
390   dialog = g_object_new (psppire_dialog_get_type (),
391                          NULL);
392
393   return GTK_WIDGET (dialog) ;
394 }
395
396
397 void
398 psppire_dialog_notify_change (PsppireDialog *dialog)
399 {
400   if ( dialog->contents_are_valid )
401     {
402       gboolean valid = dialog->contents_are_valid (dialog->validity_data);
403
404       g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, valid);
405     }
406 }
407
408
409 /* Descend the widget tree, connecting appropriate signals to the
410    psppire_dialog_notify_change callback */
411 static void
412 connect_notify_signal (GtkWidget *w, gpointer data)
413 {
414   PsppireDialog *dialog = data;
415
416   if ( PSPPIRE_IS_BUTTONBOX (w))
417     return;
418
419   if ( GTK_IS_CONTAINER (w))
420     {
421       gtk_container_foreach (GTK_CONTAINER (w),
422                              connect_notify_signal,
423                              dialog);
424     }
425
426
427   /* It's unfortunate that GTK+ doesn't have a generic
428      "user-modified-state-changed" signal.  Instead, we have to try and
429      predict what widgets and signals are likely to exist in our dialogs. */
430
431   if ( GTK_IS_TOGGLE_BUTTON (w))
432     {
433       g_signal_connect_swapped (w, "toggled",
434                                 G_CALLBACK (psppire_dialog_notify_change),
435                                 dialog);
436     }
437
438   if ( PSPPIRE_IS_SELECTOR (w))
439     {
440       g_signal_connect_swapped (w, "selected",
441                                 G_CALLBACK (psppire_dialog_notify_change),
442                                 dialog);
443
444       g_signal_connect_swapped (w, "de-selected",
445                                 G_CALLBACK (psppire_dialog_notify_change),
446                                 dialog);
447     }
448
449   if ( GTK_IS_EDITABLE (w))
450     {
451       g_signal_connect_swapped (w, "changed",
452                                 G_CALLBACK (psppire_dialog_notify_change),
453                                 dialog);
454     }
455
456   if ( GTK_IS_CELL_EDITABLE (w))
457     {
458       g_signal_connect_swapped (w, "editing-done",
459                                 G_CALLBACK (psppire_dialog_notify_change),
460                                 dialog);
461     }
462
463   if ( GTK_IS_TEXT_VIEW (w))
464     {
465       GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
466
467       g_signal_connect_swapped (buffer, "changed",
468                                 G_CALLBACK (psppire_dialog_notify_change),
469                                 dialog);
470     }
471
472   if ( GTK_IS_TREE_VIEW (w))
473     {
474       gint i = 0;
475       GtkTreeView *tv = GTK_TREE_VIEW (w);
476       GtkTreeSelection *selection =
477         gtk_tree_view_get_selection (tv);
478       GtkTreeViewColumn *col;
479       GtkTreeModel *model = gtk_tree_view_get_model (tv);
480
481       if ( model)
482         {
483       g_signal_connect_swapped (model, "row-changed",
484                                 G_CALLBACK (psppire_dialog_notify_change),
485                                 dialog);
486
487       g_signal_connect_swapped (model, "row-deleted",
488                                 G_CALLBACK (psppire_dialog_notify_change),
489                                 dialog);
490
491       g_signal_connect_swapped (model, "row-inserted",
492                                 G_CALLBACK (psppire_dialog_notify_change),
493                                 dialog);
494         }
495
496       g_signal_connect_swapped (selection, "changed",
497                                 G_CALLBACK (psppire_dialog_notify_change),
498                                 dialog);
499
500       while ((col = gtk_tree_view_get_column (tv, i++)))
501         {
502           GList *renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (col));
503           GList *start = renderers;
504           while (renderers)
505             {
506               if ( GTK_IS_CELL_RENDERER_TOGGLE (renderers->data))
507                 g_signal_connect_swapped (renderers->data, "toggled",
508                                           G_CALLBACK (psppire_dialog_notify_change), dialog);
509               renderers = renderers->next;
510             }
511           g_list_free (start);
512         }
513     }
514 }
515
516
517 gint
518 psppire_dialog_run (PsppireDialog *dialog)
519 {
520   if ( dialog->contents_are_valid != NULL )
521     gtk_container_foreach (GTK_CONTAINER (dialog->box),
522                            connect_notify_signal,
523                            dialog);
524
525   dialog->loop = g_main_loop_new (NULL, FALSE);
526
527   gtk_widget_show (GTK_WIDGET (dialog));
528
529   if ( dialog->contents_are_valid != NULL)
530     g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, FALSE);
531
532   g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
533
534   gdk_threads_leave ();
535   g_main_loop_run (dialog->loop);
536   gdk_threads_enter ();
537
538   g_main_loop_unref (dialog->loop);
539
540   g_signal_emit (dialog, signals [RESPONSE], 0, dialog->response);
541
542   return dialog->response;
543 }
544
545
546 void
547 psppire_dialog_reload (PsppireDialog *dialog)
548 {
549   g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
550 }
551
552
553 void
554 psppire_dialog_help (PsppireDialog *dialog)
555 {
556   char *name = NULL;
557   g_object_get (dialog, "name", &name, NULL);
558
559   online_help (name);
560
561   g_signal_emit (dialog, signals [DIALOG_HELP], 0, name);
562 }
563
564
565 GType
566 psppire_orientation_get_type (void)
567 {
568   static GType etype = 0;
569   if (etype == 0)
570     {
571       static const GEnumValue values[] =
572         {
573           { PSPPIRE_HORIZONTAL, "PSPPIRE_HORIZONTAL", "Horizontal" },
574           { PSPPIRE_VERTICAL,   "PSPPIRE_VERTICAL",   "Vertical" },
575           { PSPPIRE_TABULAR,   "PSPPIRE_TABULAR",   "Tabular" },
576           { 0, NULL, NULL }
577         };
578
579       etype = g_enum_register_static
580         (g_intern_static_string ("PsppireOrientation"), values);
581
582     }
583   return etype;
584 }
585
586
587 void
588 psppire_dialog_set_valid_predicate (PsppireDialog *dialog,
589                                     ContentsAreValid contents_are_valid,
590                                     gpointer data)
591 {
592   dialog->contents_are_valid = contents_are_valid;
593   dialog->validity_data = data;
594 }
595
596
597
598
599
600 static GObject *
601 get_internal_child    (GtkBuildable *buildable,
602                        GtkBuilder *builder,
603                        const gchar *childname)
604 {
605   PsppireDialog *dialog = PSPPIRE_DIALOG (buildable);
606
607   if ( 0 == strcmp (childname, "hbox"))
608     return G_OBJECT (dialog->box);
609
610   return NULL;
611 }
612
613
614
615 static void
616 psppire_dialog_buildable_init (GtkBuildableIface *iface)
617 {
618   iface->get_internal_child = get_internal_child;
619 }