Fix display of window icon
[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 <string.h>
27
28 static void psppire_dialog_class_init          (PsppireDialogClass *);
29 static void psppire_dialog_init                (PsppireDialog      *);
30
31
32 enum  {DIALOG_REFRESH,
33        VALIDITY_CHANGED,
34        n_SIGNALS};
35
36 static guint signals [n_SIGNALS];
37
38
39 static void psppire_dialog_buildable_init (GtkBuildableIface *iface);
40
41
42 GType
43 psppire_dialog_get_type (void)
44 {
45   static GType dialog_type = 0;
46
47   if (!dialog_type)
48     {
49       static const GTypeInfo dialog_info =
50       {
51         sizeof (PsppireDialogClass),
52         NULL, /* base_init */
53         NULL, /* base_finalize */
54         (GClassInitFunc) psppire_dialog_class_init,
55         NULL, /* class_finalize */
56         NULL, /* class_data */
57         sizeof (PsppireDialog),
58         0,
59         (GInstanceInitFunc) psppire_dialog_init,
60       };
61
62       static const GInterfaceInfo buildable_info =
63       {
64         (GInterfaceInitFunc) psppire_dialog_buildable_init,
65         NULL,
66         NULL
67       };
68
69       dialog_type = g_type_register_static (GTK_TYPE_WINDOW,
70                                             "PsppireDialog", &dialog_info, 0);
71
72       g_type_add_interface_static (dialog_type,
73                                    GTK_TYPE_BUILDABLE,
74                                    &buildable_info);
75
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 };
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) )
123           g_value_set_enum (value, PSPPIRE_VERTICAL);
124         else if ( GTK_IS_HBOX (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     default:
131       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
132       break;
133     };
134 }
135
136
137 static void
138 dialog_set_orientation (PsppireDialog *dialog, const GValue *orval)
139 {
140   PsppireOrientation orientation = g_value_get_enum (orval);
141
142   if ( dialog->box != NULL)
143     {
144       gtk_container_remove (GTK_CONTAINER (dialog), dialog->box);
145     }
146
147   switch ( orientation )
148     {
149     case PSPPIRE_HORIZONTAL:
150       dialog->box = gtk_hbox_new (FALSE, 5);
151       break;
152     case PSPPIRE_VERTICAL:
153       dialog->box = gtk_vbox_new (FALSE, 5);
154       break;
155     case PSPPIRE_TABULAR:
156       dialog->box = gtk_table_new (2, 3, FALSE);
157       g_object_set (dialog->box,
158                     "row-spacing", 5,
159                     "column-spacing", 5,
160                     NULL);
161       break;
162     }
163
164   gtk_container_add (GTK_CONTAINER (dialog), dialog->box);
165 }
166
167
168 static void
169 psppire_dialog_set_property (GObject         *object,
170                              guint            prop_id,
171                              const GValue    *value,
172                              GParamSpec      *pspec)
173
174 {
175   PsppireDialog *dialog = PSPPIRE_DIALOG (object);
176
177   switch (prop_id)
178     {
179     case PROP_ORIENTATION:
180       dialog_set_orientation (dialog, value);
181       break;
182     default:
183       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
184       break;
185     };
186 }
187
188
189 static GParamSpec *orientation_spec ;
190
191 static void
192 psppire_dialog_class_init (PsppireDialogClass *class)
193 {
194   GObjectClass *object_class = (GObjectClass *) class;
195
196
197   orientation_spec =
198     g_param_spec_enum ("orientation",
199                        "Orientation",
200                        "Which way widgets are packed",
201                        G_TYPE_PSPPIRE_ORIENTATION,
202                        PSPPIRE_HORIZONTAL /* default value */,
203                        G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
204
205
206   object_class->set_property = psppire_dialog_set_property;
207   object_class->get_property = psppire_dialog_get_property;
208
209   g_object_class_install_property (object_class,
210                                    PROP_ORIENTATION,
211                                    orientation_spec);
212
213
214
215   signals [DIALOG_REFRESH] =
216     g_signal_new ("refresh",
217                   G_TYPE_FROM_CLASS (class),
218                   G_SIGNAL_RUN_FIRST,
219                   0,
220                   NULL, NULL,
221                   g_cclosure_marshal_VOID__VOID,
222                   G_TYPE_NONE,
223                   0);
224
225
226   signals [VALIDITY_CHANGED] =
227     g_signal_new ("validity-changed",
228                   G_TYPE_FROM_CLASS (class),
229                   G_SIGNAL_RUN_FIRST,
230                   0,
231                   NULL, NULL,
232                   g_cclosure_marshal_VOID__BOOLEAN,
233                   G_TYPE_NONE,
234                   1,
235                   G_TYPE_BOOLEAN);
236
237
238   object_class->finalize = psppire_dialog_finalize;
239 }
240
241
242
243
244 static void
245 close_dialog (GtkWidget *w, gpointer data)
246 {
247   PsppireDialog *dialog = data;
248
249   psppire_dialog_close (dialog);
250 }
251
252 void
253 psppire_dialog_close (PsppireDialog *dialog)
254 {
255   g_main_loop_quit (dialog->loop);
256   gtk_widget_hide (GTK_WIDGET (dialog));
257 }
258
259
260 static void
261 delete_event_callback (GtkWidget *w, GdkEvent *e, gpointer data)
262 {
263   close_dialog (w, data);
264 }
265
266
267 static void
268 psppire_dialog_init (PsppireDialog *dialog)
269 {
270   GValue value = {0};
271   dialog->box = NULL;
272   dialog->contents_are_valid = NULL;
273   dialog->validity_data = NULL;
274
275   g_value_init (&value, orientation_spec->value_type);
276   g_param_value_set_default (orientation_spec, &value);
277
278   gtk_window_set_type_hint (GTK_WINDOW (dialog),
279         GDK_WINDOW_TYPE_HINT_DIALOG);
280
281   dialog_set_orientation (dialog, &value);
282
283   g_value_unset (&value);
284
285   g_signal_connect (G_OBJECT (dialog), "delete-event",
286                     G_CALLBACK (delete_event_callback),
287                     dialog);
288
289   gtk_window_set_type_hint (GTK_WINDOW (dialog),
290         GDK_WINDOW_TYPE_HINT_DIALOG);
291
292   g_object_set (dialog, "icon-name", "psppicon", NULL);
293
294   gtk_widget_show_all (dialog->box);
295 }
296
297
298 GtkWidget*
299 psppire_dialog_new (void)
300 {
301   PsppireDialog *dialog ;
302
303   dialog = g_object_new (psppire_dialog_get_type (),
304                          NULL);
305
306   return GTK_WIDGET (dialog) ;
307 }
308
309
310 void
311 psppire_dialog_notify_change (PsppireDialog *dialog)
312 {
313   if ( dialog->contents_are_valid )
314     {
315       gboolean valid = dialog->contents_are_valid (dialog->validity_data);
316
317       g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, valid);
318     }
319 }
320
321
322 /* Descend the widget tree, connecting appropriate signals to the
323    psppire_dialog_notify_change callback */
324 static void
325 connect_notify_signal (GtkWidget *w, gpointer data)
326 {
327   PsppireDialog *dialog = data;
328
329   if ( PSPPIRE_IS_BUTTONBOX (w))
330     return;
331
332
333
334   if ( GTK_IS_CONTAINER (w))
335     {
336       gtk_container_foreach (GTK_CONTAINER (w),
337                              connect_notify_signal,
338                              dialog);
339     }
340
341
342   /* It's unfortunate that GTK+ doesn't have a generic
343      "user-modified-state-changed" signal.  Instead, we have to try and
344      predict what widgets and signals are likely to exist in our dialogs. */
345
346   if ( GTK_IS_TOGGLE_BUTTON (w))
347     {
348       g_signal_connect_swapped (w, "toggled",
349                                 G_CALLBACK (psppire_dialog_notify_change),
350                                 dialog);
351     }
352
353   if ( PSPPIRE_IS_SELECTOR (w))
354     {
355       g_signal_connect_swapped (w, "selected",
356                                 G_CALLBACK (psppire_dialog_notify_change),
357                                 dialog);
358
359       g_signal_connect_swapped (w, "de-selected",
360                                 G_CALLBACK (psppire_dialog_notify_change),
361                                 dialog);
362     }
363
364   if ( GTK_IS_EDITABLE (w))
365     {
366       g_signal_connect_swapped (w, "changed",
367                                 G_CALLBACK (psppire_dialog_notify_change),
368                                 dialog);
369     }
370
371   if ( GTK_IS_CELL_EDITABLE (w))
372     {
373       g_signal_connect_swapped (w, "editing-done",
374                                 G_CALLBACK (psppire_dialog_notify_change),
375                                 dialog);
376     }
377
378   if ( GTK_IS_TEXT_VIEW (w))
379     {
380       GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
381
382       g_signal_connect_swapped (buffer, "changed",
383                                 G_CALLBACK (psppire_dialog_notify_change),
384                                 dialog);
385     }
386
387   if ( GTK_IS_TREE_VIEW (w))
388     {
389       gint i = 0;
390       GtkTreeView *tv = GTK_TREE_VIEW (w);
391       GtkTreeSelection *selection =
392         gtk_tree_view_get_selection (tv);
393       GtkTreeViewColumn *col;
394       GtkTreeModel *model = gtk_tree_view_get_model (tv);
395
396       if ( model)
397         {
398       g_signal_connect_swapped (model, "row-changed",
399                                 G_CALLBACK (psppire_dialog_notify_change),
400                                 dialog);
401
402       g_signal_connect_swapped (model, "row-deleted",
403                                 G_CALLBACK (psppire_dialog_notify_change),
404                                 dialog);
405
406       g_signal_connect_swapped (model, "row-inserted",
407                                 G_CALLBACK (psppire_dialog_notify_change),
408                                 dialog);
409         }
410
411       g_signal_connect_swapped (selection, "changed",
412                                 G_CALLBACK (psppire_dialog_notify_change),
413                                 dialog);
414
415       while ((col = gtk_tree_view_get_column (tv, i++)))
416         {
417           GList *renderers = gtk_tree_view_column_get_cell_renderers (col);
418           GList *start = renderers;
419           while (renderers)
420             {
421               if ( GTK_IS_CELL_RENDERER_TOGGLE (renderers->data))
422                 g_signal_connect_swapped (renderers->data, "toggled",
423                                           G_CALLBACK (psppire_dialog_notify_change), dialog);
424               renderers = renderers->next;
425             }
426           g_list_free (start);
427         }
428     }
429 }
430
431
432 gint
433 psppire_dialog_run (PsppireDialog *dialog)
434 {
435   if ( dialog->contents_are_valid != NULL )
436     gtk_container_foreach (GTK_CONTAINER (dialog->box),
437                            connect_notify_signal,
438                            dialog);
439
440   dialog->loop = g_main_loop_new (NULL, FALSE);
441
442   gtk_widget_show (GTK_WIDGET (dialog));
443
444   if ( dialog->contents_are_valid != NULL)
445     g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, FALSE);
446
447   g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
448
449   g_main_loop_run (dialog->loop);
450
451   g_main_loop_unref (dialog->loop);
452
453   return dialog->response;
454 }
455
456
457 void
458 psppire_dialog_reload (PsppireDialog *dialog)
459 {
460   g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
461 }
462
463
464
465
466 GType
467 psppire_orientation_get_type (void)
468 {
469   static GType etype = 0;
470   if (etype == 0)
471     {
472       static const GEnumValue values[] =
473         {
474           { PSPPIRE_HORIZONTAL, "PSPPIRE_HORIZONTAL", "Horizontal" },
475           { PSPPIRE_VERTICAL,   "PSPPIRE_VERTICAL",   "Vertical" },
476           { PSPPIRE_TABULAR,   "PSPPIRE_TABULAR",   "Tabular" },
477           { 0, NULL, NULL }
478         };
479
480       etype = g_enum_register_static
481         (g_intern_static_string ("PsppireOrientation"), values);
482
483     }
484   return etype;
485 }
486
487
488 void
489 psppire_dialog_set_valid_predicate (PsppireDialog *dialog,
490                                     ContentsAreValid contents_are_valid,
491                                     gpointer data)
492 {
493   dialog->contents_are_valid = contents_are_valid;
494   dialog->validity_data = data;
495 }
496
497
498
499
500
501 static GObject *
502 get_internal_child    (GtkBuildable *buildable,
503                        GtkBuilder *builder,
504                        const gchar *childname)
505 {
506   PsppireDialog *dialog = PSPPIRE_DIALOG (buildable);
507
508   if ( 0 == strcmp (childname, "hbox"))
509     return G_OBJECT (dialog->box);
510
511   return NULL;
512 }
513
514
515
516 static void
517 psppire_dialog_buildable_init (GtkBuildableIface *iface)
518 {
519   iface->get_internal_child = get_internal_child;
520 }