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