Added feature to make OK and PASTE buttons insensitive until the dialog box
[pspp] / 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 "psppire-dialog.h"
23 #include "psppire-buttonbox.h"
24 #include "psppire-selector.h"
25
26 static void psppire_dialog_class_init          (PsppireDialogClass *);
27 static void psppire_dialog_init                (PsppireDialog      *);
28
29
30 enum  {DIALOG_REFRESH,
31        VALIDITY_CHANGED,
32        n_SIGNALS};
33
34 static guint signals [n_SIGNALS];
35
36
37 GType
38 psppire_dialog_get_type (void)
39 {
40   static GType dialog_type = 0;
41
42   if (!dialog_type)
43     {
44       static const GTypeInfo dialog_info =
45       {
46         sizeof (PsppireDialogClass),
47         NULL, /* base_init */
48         NULL, /* base_finalize */
49         (GClassInitFunc) psppire_dialog_class_init,
50         NULL, /* class_finalize */
51         NULL, /* class_data */
52         sizeof (PsppireDialog),
53         0,
54         (GInstanceInitFunc) psppire_dialog_init,
55       };
56
57       dialog_type = g_type_register_static (GTK_TYPE_WINDOW,
58                                             "PsppireDialog", &dialog_info, 0);
59     }
60
61   return dialog_type;
62 }
63
64
65
66 static GObjectClass     *parent_class = NULL;
67
68
69 static void
70 psppire_dialog_finalize (GObject *object)
71 {
72   PsppireDialog *dialog ;
73
74   g_return_if_fail (object != NULL);
75   g_return_if_fail (PSPPIRE_IS_DIALOG (object));
76
77   dialog = PSPPIRE_DIALOG (object);
78
79   if (G_OBJECT_CLASS (parent_class)->finalize)
80     G_OBJECT_CLASS (parent_class)->finalize (object);
81 }
82
83
84
85 /* Properties */
86 enum
87 {
88   PROP_0,
89   PROP_ORIENTATION
90 };
91
92
93 static void
94 psppire_dialog_get_property (GObject         *object,
95                              guint            prop_id,
96                              GValue          *value,
97                              GParamSpec      *pspec)
98 {
99   PsppireDialog *dialog = PSPPIRE_DIALOG (object);
100
101   switch (prop_id)
102     {
103     case PROP_ORIENTATION:
104       {
105         if ( GTK_IS_VBOX (dialog->box) )
106           g_value_set_enum (value, PSPPIRE_VERTICAL);
107         else if ( GTK_IS_HBOX (dialog->box))
108           g_value_set_enum (value, PSPPIRE_HORIZONTAL);
109       }
110       break;
111     default:
112       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
113       break;
114     };
115 }
116
117
118 static void
119 dialog_set_orientation (PsppireDialog *dialog, const GValue *orval)
120 {
121   PsppireOrientation orientation = g_value_get_enum (orval);
122
123   if ( dialog->box != NULL)
124     {
125       gtk_container_remove (GTK_CONTAINER (dialog), dialog->box);
126     }
127
128   if ( orientation == PSPPIRE_HORIZONTAL)
129     {
130       dialog->box = gtk_hbox_new (FALSE, 5);
131     }
132   else
133     {
134       dialog->box = gtk_vbox_new (FALSE, 5);
135     }
136
137   gtk_container_add (GTK_CONTAINER (dialog), dialog->box);
138 }
139
140
141 static void
142 psppire_dialog_set_property (GObject         *object,
143                              guint            prop_id,
144                              const GValue    *value,
145                              GParamSpec      *pspec)
146
147 {
148   PsppireDialog *dialog = PSPPIRE_DIALOG (object);
149
150   switch (prop_id)
151     {
152     case PROP_ORIENTATION:
153       dialog_set_orientation (dialog, value);
154       break;
155     default:
156       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
157       break;
158     };
159 }
160
161
162 static GParamSpec *orientation_spec ;
163
164 static void
165 psppire_dialog_class_init (PsppireDialogClass *class)
166 {
167   GObjectClass *object_class = (GObjectClass *) class;
168
169
170   orientation_spec =
171     g_param_spec_enum ("orientation",
172                        "Orientation",
173                        "Which way widgets are packed",
174                        G_TYPE_PSPPIRE_ORIENTATION,
175                        PSPPIRE_HORIZONTAL /* default value */,
176                        G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
177
178
179   object_class->set_property = psppire_dialog_set_property;
180   object_class->get_property = psppire_dialog_get_property;
181
182   g_object_class_install_property (object_class,
183                                    PROP_ORIENTATION,
184                                    orientation_spec);
185
186
187
188   signals [DIALOG_REFRESH] =
189     g_signal_new ("refresh",
190                   G_TYPE_FROM_CLASS (class),
191                   G_SIGNAL_RUN_FIRST,
192                   0,
193                   NULL, NULL,
194                   g_cclosure_marshal_VOID__VOID,
195                   G_TYPE_NONE,
196                   0);
197
198
199   signals [VALIDITY_CHANGED] =
200     g_signal_new ("validity-changed",
201                   G_TYPE_FROM_CLASS (class),
202                   G_SIGNAL_RUN_FIRST,
203                   0,
204                   NULL, NULL,
205                   g_cclosure_marshal_VOID__BOOLEAN,
206                   G_TYPE_NONE,
207                   1,
208                   G_TYPE_BOOLEAN);
209
210
211   object_class->finalize = psppire_dialog_finalize;
212 }
213
214
215
216
217 static void
218 close_dialog (GtkWidget *w, gpointer data)
219 {
220   PsppireDialog *dialog = data;
221
222   psppire_dialog_close (dialog);
223 }
224
225 void
226 psppire_dialog_close (PsppireDialog *dialog)
227 {
228   g_main_loop_quit (dialog->loop);
229   gtk_widget_hide (GTK_WIDGET (dialog));
230 }
231
232
233 static void
234 delete_event_callback (GtkWidget *w, GdkEvent *e, gpointer data)
235 {
236   close_dialog (w, data);
237 }
238
239
240 static void
241 psppire_dialog_init (PsppireDialog *dialog)
242 {
243   GValue value = {0};
244   dialog->box = NULL;
245   dialog->contents_are_valid = NULL;
246   dialog->validity_data = NULL;
247
248   g_value_init (&value, orientation_spec->value_type);
249   g_param_value_set_default (orientation_spec, &value);
250
251   gtk_window_set_type_hint (GTK_WINDOW (dialog),
252         GDK_WINDOW_TYPE_HINT_DIALOG);
253
254   dialog_set_orientation (dialog, &value);
255
256   g_value_unset (&value);
257
258   g_signal_connect (G_OBJECT (dialog), "delete-event",
259                     G_CALLBACK (delete_event_callback),
260                     dialog);
261
262   gtk_window_set_type_hint (GTK_WINDOW (dialog),
263         GDK_WINDOW_TYPE_HINT_DIALOG);
264
265   gtk_widget_show_all (dialog->box);
266 }
267
268
269 GtkWidget*
270 psppire_dialog_new (void)
271 {
272   PsppireDialog *dialog ;
273
274   dialog = g_object_new (psppire_dialog_get_type (), NULL);
275
276   return GTK_WIDGET (dialog) ;
277 }
278
279
280 static void
281 notify_change (PsppireDialog *dialog)
282 {
283   if ( dialog->contents_are_valid )
284     {
285       gboolean valid = dialog->contents_are_valid (dialog->validity_data);
286
287       g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, valid);
288     }
289 }
290
291
292 /* Descend the widget tree, connecting appropriate signals to the
293    notify_change callback */
294 static void
295 connect_notify_signal (GtkWidget *w, gpointer data)
296 {
297   PsppireDialog *dialog = data;
298
299   if ( PSPPIRE_IS_BUTTONBOX (w))
300     return;
301
302
303
304   if ( GTK_IS_CONTAINER (w))
305     {
306       gtk_container_foreach (GTK_CONTAINER (w),
307                              connect_notify_signal,
308                              dialog);
309     }
310
311
312   /* It's unfortunate that GTK+ doesn't have a generic
313      "user-modified-state-changed" signal.  Instead, we have to try and
314      predict what widgets and signals are likely to exist in our dialogs. */
315
316   if ( GTK_IS_TOGGLE_BUTTON (w))
317     {
318       g_signal_connect_swapped (w, "toggled", G_CALLBACK (notify_change),
319                                 dialog);
320     }
321
322   if ( PSPPIRE_IS_SELECTOR (w))
323     {
324       g_signal_connect_swapped (w, "selected", G_CALLBACK (notify_change),
325                                 dialog);
326
327       g_signal_connect_swapped (w, "de-selected", G_CALLBACK (notify_change),
328                                 dialog);
329     }
330
331   if ( GTK_IS_EDITABLE (w))
332     {
333       g_signal_connect_swapped (w, "changed", G_CALLBACK (notify_change),
334                                 dialog);
335     }
336
337   if ( GTK_IS_CELL_EDITABLE (w))
338     {
339       g_signal_connect_swapped (w, "editing-done", G_CALLBACK (notify_change),
340                                 dialog);
341     }
342
343   if ( GTK_IS_TEXT_VIEW (w))
344     {
345       GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
346
347       g_signal_connect_swapped (buffer, "changed", G_CALLBACK (notify_change),
348                                 dialog);
349     }
350
351   if ( GTK_IS_TREE_VIEW (w))
352     {
353       gint i = 0;
354       GtkTreeView *tv = GTK_TREE_VIEW (w);
355       GtkTreeSelection *selection =
356         gtk_tree_view_get_selection (tv);
357       GtkTreeViewColumn *col;
358
359       g_signal_connect_swapped (selection, "changed",
360                                 G_CALLBACK (notify_change), dialog);
361
362       while ((col = gtk_tree_view_get_column (tv, i++)))
363         {
364           GList *renderers = gtk_tree_view_column_get_cell_renderers (col);
365           GList *start = renderers;
366           while (renderers)
367             {
368               if ( GTK_IS_CELL_RENDERER_TOGGLE (renderers->data))
369                 g_signal_connect_swapped (renderers->data, "toggled",
370                                           G_CALLBACK (notify_change), dialog);
371               renderers = renderers->next;
372             }
373           g_list_free (start);
374         }
375     }
376 }
377
378
379 gint
380 psppire_dialog_run (PsppireDialog *dialog)
381 {
382   if ( dialog->contents_are_valid != NULL )
383     gtk_container_foreach (GTK_CONTAINER (dialog->box),
384                            connect_notify_signal,
385                            dialog);
386
387   dialog->loop = g_main_loop_new (NULL, FALSE);
388
389   gtk_widget_show (GTK_WIDGET (dialog));
390
391   if ( dialog->contents_are_valid != NULL)
392     g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, FALSE);
393
394   g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
395
396   g_main_loop_run (dialog->loop);
397
398   return dialog->response;
399 }
400
401
402 void
403 psppire_dialog_reload (PsppireDialog *dialog)
404 {
405   g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
406 }
407
408
409
410
411 GType
412 psppire_orientation_get_type (void)
413 {
414   static GType etype = 0;
415   if (etype == 0)
416     {
417       static const GEnumValue values[] =
418         {
419           { PSPPIRE_HORIZONTAL, "PSPPIRE_HORIZONTAL", "Horizontal" },
420           { PSPPIRE_VERTICAL,   "PSPPIRE_VERTICAL",   "Vertical" },
421           { 0, NULL, NULL }
422         };
423
424       etype = g_enum_register_static
425         (g_intern_static_string ("PsppireOrientation"), values);
426
427     }
428   return etype;
429 }
430
431
432 void
433 psppire_dialog_set_valid_predicate (PsppireDialog *dialog,
434                                     ContentsAreValid contents_are_valid,
435                                     gpointer data)
436 {
437   dialog->contents_are_valid = contents_are_valid;
438   dialog->validity_data = data;
439 }
440
441