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