Merge 'master' into 'gtk3'.
[pspp] / src / ui / gui / psppire-buttonbox.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 <glib.h>
21 #include <gtk/gtk.h>
22 #include "psppire-buttonbox.h"
23 #include "psppire-dialog.h"
24
25 #include "helper.h"
26
27 #include <gettext.h>
28
29 #define _(msgid) gettext (msgid)
30 #define N_(msgid) msgid
31
32 GType psppire_button_flags_get_type (void);
33
34
35 static void psppire_button_box_class_init          (PsppireButtonBoxClass *);
36 static void psppire_button_box_init                (PsppireButtonBox      *);
37
38
39 GType
40 psppire_button_box_get_type (void)
41 {
42   static GType button_box_type = 0;
43
44   if (!button_box_type)
45     {
46       static const GTypeInfo button_box_info =
47       {
48         sizeof (PsppireButtonBoxClass),
49         NULL, /* base_init */
50         NULL, /* base_finalize */
51         (GClassInitFunc) psppire_button_box_class_init,
52         NULL, /* class_finalize */
53         NULL, /* class_data */
54         sizeof (PsppireButtonBox),
55         0,
56         (GInstanceInitFunc) psppire_button_box_init,
57       };
58
59       button_box_type = g_type_register_static (GTK_TYPE_BUTTON_BOX,
60                                             "PsppireButtonBox", &button_box_info, G_TYPE_FLAG_ABSTRACT);
61     }
62
63   return button_box_type;
64 }
65
66 enum {
67   PROP_BUTTONS = 1,
68   PROP_DEFAULT = 2
69 };
70
71 static void
72 set_default (PsppireButtonBox *bb)
73 {
74   int i;
75
76   for (i = 0 ; i < n_PsppireButtonBoxButtons ; ++i )
77     if (bb->def == (1 << i))
78       {
79         gtk_widget_set_can_default (bb->button[i], TRUE);
80         gtk_widget_grab_default (bb->button[i]);
81       }
82 }
83
84 static void
85 psppire_buttonbox_set_property (GObject         *object,
86                                 guint            prop_id,
87                                 const GValue    *value,
88                                 GParamSpec      *pspec)
89 {
90   gint i;
91   guint flags;
92   PsppireButtonBox *bb = PSPPIRE_BUTTONBOX (object);
93
94   switch (prop_id)
95     {
96     case PROP_BUTTONS:
97       flags = g_value_get_flags (value);
98       for (i = 0 ; i < n_PsppireButtonBoxButtons ; ++i )
99         g_object_set (bb->button[i], "visible", 0x01 & (flags >> i)  , NULL);
100       break;
101
102     case PROP_DEFAULT:
103       bb->def = g_value_get_flags (value);
104       if (gtk_widget_get_realized (GTK_WIDGET (bb)))
105         set_default (bb);
106       break;
107
108     default:
109       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
110     }
111 }
112
113 static void
114 psppire_buttonbox_get_property (GObject         *object,
115                                 guint            prop_id,
116                                 GValue          *value,
117                                 GParamSpec      *pspec)
118 {
119   guint flags = 0;
120   gint i;
121
122   PsppireButtonBox *bb = PSPPIRE_BUTTONBOX (object);
123
124   switch (prop_id)
125     {
126     case PROP_BUTTONS:
127       for (i = 0 ; i < n_PsppireButtonBoxButtons ; ++i )
128         {
129           gboolean visibility;
130           g_object_get (bb->button[i], "visible", &visibility, NULL);
131
132           if ( visibility )
133             flags |= (0x01 << i);
134         }
135
136       g_value_set_flags (value, flags);
137       break;
138
139     case PROP_DEFAULT:
140       g_value_set_flags (value, bb->def);
141
142     default:
143       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
144       break;
145     }
146 }
147
148
149 typedef enum
150   {
151     PSPPIRE_BUTTON_OK_MASK     = (1 << PSPPIRE_BUTTON_OK),
152     PSPPIRE_BUTTON_GOTO_MASK   = (1 << PSPPIRE_BUTTON_GOTO),
153     PSPPIRE_BUTTON_CONTINUE_MASK = (1 << PSPPIRE_BUTTON_CONTINUE),
154     PSPPIRE_BUTTON_CANCEL_MASK = (1 << PSPPIRE_BUTTON_CANCEL),
155     PSPPIRE_BUTTON_CLOSE_MASK  = (1 << PSPPIRE_BUTTON_CLOSE),
156     PSPPIRE_BUTTON_HELP_MASK   = (1 << PSPPIRE_BUTTON_HELP),
157     PSPPIRE_BUTTON_RESET_MASK  = (1 << PSPPIRE_BUTTON_RESET),
158     PSPPIRE_BUTTON_PASTE_MASK  = (1 << PSPPIRE_BUTTON_PASTE)
159   } PsppireButtonMask;
160
161 static GParamSpec *button_flags;
162 static GParamSpec *default_flags;
163
164 static void
165 psppire_button_box_class_init (PsppireButtonBoxClass *class)
166 {
167   GObjectClass *object_class = G_OBJECT_CLASS (class);
168
169   object_class->set_property = psppire_buttonbox_set_property;
170   object_class->get_property = psppire_buttonbox_get_property;
171
172   button_flags =
173     g_param_spec_flags ("buttons",
174                         "Buttons",
175                         "The mask that decides what buttons appear in the button box",
176                         PSPPIRE_TYPE_BUTTON_MASK,
177                         PSPPIRE_BUTTON_OK_MASK |
178                         PSPPIRE_BUTTON_CANCEL_MASK |
179                         PSPPIRE_BUTTON_RESET_MASK |
180                         PSPPIRE_BUTTON_HELP_MASK |
181                         PSPPIRE_BUTTON_PASTE_MASK,
182                         G_PARAM_READWRITE);
183   g_object_class_install_property (object_class,
184                                    PROP_BUTTONS,
185                                    button_flags);
186
187   default_flags =
188     g_param_spec_flags ("default",
189                         "Default",
190                         "The mask that decides what what button grabs the default",
191                         PSPPIRE_TYPE_BUTTON_MASK,
192                         0,
193                         G_PARAM_READWRITE);
194   g_object_class_install_property (object_class,
195                                    PROP_DEFAULT,
196                                    default_flags);
197 }
198
199 static void
200 close_and_respond (GtkWidget *w, gint response)
201 {
202   PsppireDialog *dialog;
203
204   GtkWidget *toplevel = gtk_widget_get_toplevel (w);
205
206   /* If we're not in a psppire dialog (for example when in glade)
207      then do nothing */
208   if ( ! PSPPIRE_IS_DIALOG (toplevel))
209     return;
210
211   dialog = PSPPIRE_DIALOG (toplevel);
212
213   dialog->response = response;
214
215   psppire_dialog_close (dialog);
216 }
217
218 static gboolean
219 is_acceptable (GtkWidget *w)
220 {
221   GtkWidget *toplevel = gtk_widget_get_toplevel (w);
222
223   return (PSPPIRE_IS_DIALOG (toplevel)
224           && psppire_dialog_is_acceptable (PSPPIRE_DIALOG (toplevel)));
225 }
226
227 static void
228 close_dialog (GtkWidget *w, gpointer data)
229 {
230   close_and_respond (w, GTK_RESPONSE_CLOSE);
231 }
232
233 static void
234 continue_button_clicked (GtkWidget *w, gpointer data)
235 {
236   if (is_acceptable (w))
237     close_and_respond (w, PSPPIRE_RESPONSE_CONTINUE);
238 }
239
240
241 static void
242 ok_button_clicked (GtkWidget *w, gpointer data)
243 {
244   if (is_acceptable (w))
245     close_and_respond (w, GTK_RESPONSE_OK);
246 }
247
248
249 static void
250 paste_button_clicked (GtkWidget *w, gpointer data)
251 {
252   if (is_acceptable (w))
253     close_and_respond (w, PSPPIRE_RESPONSE_PASTE);
254 }
255
256 static void
257 goto_button_clicked (GtkWidget *w, gpointer data)
258 {
259   if (is_acceptable (w))
260     close_and_respond (w, PSPPIRE_RESPONSE_GOTO);
261 }
262
263
264 static void
265 refresh_clicked (GtkWidget *w, gpointer data)
266 {
267   GtkWidget *toplevel = gtk_widget_get_toplevel (w);
268   PsppireDialog *dialog;
269
270   if ( ! PSPPIRE_IS_DIALOG (toplevel))
271     return;
272
273   dialog = PSPPIRE_DIALOG (toplevel);
274
275   psppire_dialog_reload (dialog);
276 }
277
278
279
280 static void
281 help_clicked (GtkWidget *w, gpointer data)
282 {
283   GtkWidget *toplevel = gtk_widget_get_toplevel (w);
284   PsppireDialog *dialog;
285
286   if ( ! PSPPIRE_IS_DIALOG (toplevel))
287     return;
288
289   dialog = PSPPIRE_DIALOG (toplevel);
290
291   psppire_dialog_help (dialog);
292 }
293
294
295
296 static void
297 on_validity_change (GtkWidget *toplevel, gboolean valid, gpointer data)
298 {
299   PsppireButtonBox *bb = data;
300
301   /* Set the sensitivity of all the 'executive order' buttons */
302   gtk_widget_set_sensitive (GTK_WIDGET (bb->button[PSPPIRE_BUTTON_OK]), valid);
303   gtk_widget_set_sensitive (GTK_WIDGET (bb->button[PSPPIRE_BUTTON_PASTE]), valid);
304   gtk_widget_set_sensitive (GTK_WIDGET (bb->button[PSPPIRE_BUTTON_GOTO]), valid);
305   gtk_widget_set_sensitive (GTK_WIDGET (bb->button[PSPPIRE_BUTTON_CONTINUE]), valid);
306 }
307
308 static void
309 on_realize (GtkWidget *buttonbox, gpointer data)
310 {
311   GtkWidget *toplevel = gtk_widget_get_toplevel (buttonbox);
312
313   if ( PSPPIRE_IS_DIALOG (toplevel))
314     {
315       g_signal_connect (toplevel, "validity-changed",
316                         G_CALLBACK (on_validity_change), buttonbox);
317     }
318   set_default (PSPPIRE_BUTTONBOX (buttonbox));
319 }
320
321 static void
322 psppire_button_box_init (PsppireButtonBox *bb)
323 {
324   bb->def = PSPPIRE_BUTTON_CONTINUE;
325
326   bb->button[PSPPIRE_BUTTON_OK] = gtk_button_new_from_stock (GTK_STOCK_OK);
327   psppire_box_pack_start_defaults (GTK_BOX (bb), bb->button[PSPPIRE_BUTTON_OK]);
328   g_signal_connect (bb->button[PSPPIRE_BUTTON_OK], "clicked",
329                     G_CALLBACK (ok_button_clicked), NULL);
330   g_object_set (bb->button[PSPPIRE_BUTTON_OK], "no-show-all", TRUE, NULL);
331
332
333   bb->button[PSPPIRE_BUTTON_GOTO] =
334     gtk_button_new_from_stock (GTK_STOCK_JUMP_TO);
335   psppire_box_pack_start_defaults (GTK_BOX (bb), bb->button[PSPPIRE_BUTTON_GOTO]);
336   g_signal_connect (bb->button[PSPPIRE_BUTTON_GOTO], "clicked",
337                     G_CALLBACK (goto_button_clicked), NULL);
338   g_object_set (bb->button[PSPPIRE_BUTTON_GOTO], "no-show-all", TRUE, NULL);
339
340
341   bb->button[PSPPIRE_BUTTON_CONTINUE] =
342     gtk_button_new_with_mnemonic (_("Continue"));
343
344   psppire_box_pack_start_defaults (GTK_BOX (bb),
345                                bb->button[PSPPIRE_BUTTON_CONTINUE]);
346   g_signal_connect (bb->button[PSPPIRE_BUTTON_CONTINUE], "clicked",
347                     G_CALLBACK (continue_button_clicked), NULL);
348
349   g_object_set (bb->button[PSPPIRE_BUTTON_CONTINUE],
350                 "no-show-all", TRUE, NULL);
351
352
353
354   bb->button[PSPPIRE_BUTTON_PASTE] = gtk_button_new_from_stock (GTK_STOCK_PASTE);
355   g_signal_connect (bb->button[PSPPIRE_BUTTON_PASTE], "clicked",
356                     G_CALLBACK (paste_button_clicked), NULL);
357   psppire_box_pack_start_defaults (GTK_BOX (bb), bb->button[PSPPIRE_BUTTON_PASTE]);
358   g_object_set (bb->button[PSPPIRE_BUTTON_PASTE], "no-show-all", TRUE, NULL);
359
360   bb->button[PSPPIRE_BUTTON_CANCEL] = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
361   g_signal_connect (bb->button[PSPPIRE_BUTTON_CANCEL], "clicked",
362                     G_CALLBACK (close_dialog), NULL);
363   psppire_box_pack_start_defaults (GTK_BOX (bb), bb->button[PSPPIRE_BUTTON_CANCEL]);
364   g_object_set (bb->button[PSPPIRE_BUTTON_CANCEL], "no-show-all", TRUE, NULL);
365
366   bb->button[PSPPIRE_BUTTON_CLOSE] = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
367   g_signal_connect (bb->button[PSPPIRE_BUTTON_CLOSE], "clicked",
368                     G_CALLBACK (close_dialog), NULL);
369   psppire_box_pack_start_defaults (GTK_BOX (bb), bb->button[PSPPIRE_BUTTON_CLOSE]);
370   g_object_set (bb->button[PSPPIRE_BUTTON_CLOSE], "no-show-all", TRUE, NULL);
371
372
373   bb->button[PSPPIRE_BUTTON_RESET] = gtk_button_new_from_stock ("pspp-stock-reset");
374   g_signal_connect (bb->button[PSPPIRE_BUTTON_RESET], "clicked",
375                     G_CALLBACK (refresh_clicked), NULL);
376   psppire_box_pack_start_defaults (GTK_BOX (bb), bb->button[PSPPIRE_BUTTON_RESET]);
377   g_object_set (bb->button[PSPPIRE_BUTTON_RESET], "no-show-all", TRUE, NULL);
378
379
380   bb->button[PSPPIRE_BUTTON_HELP] = gtk_button_new_from_stock (GTK_STOCK_HELP);
381   g_signal_connect (bb->button[PSPPIRE_BUTTON_HELP], "clicked",
382                     G_CALLBACK (help_clicked), NULL);
383   psppire_box_pack_start_defaults (GTK_BOX (bb), bb->button[PSPPIRE_BUTTON_HELP]);
384   g_object_set (bb->button[PSPPIRE_BUTTON_HELP], "no-show-all", TRUE, NULL);
385
386
387   /* Set the default visibilities */
388   {
389     GValue value = { 0 };
390     guint flags;
391     gint i;
392     g_value_init (&value, button_flags->value_type);
393     g_param_value_set_default(button_flags, &value);
394
395
396     flags = g_value_get_flags (&value);
397
398     for (i = 0 ; i < n_PsppireButtonBoxButtons ; ++i )
399       g_object_set (bb->button[i], "visible", 0x01 & (flags >> i)  , NULL);
400
401     g_value_unset (&value);
402   }
403
404
405   g_signal_connect (bb, "realize", G_CALLBACK (on_realize), NULL);
406 }
407
408
409 /* This function was lifted verbatim from the Gtk2.10.6 library.
410    But later modified to fit Gtk2.24
411  */
412 void
413 _psppire_button_box_child_requisition (GtkWidget *widget,
414                                        int       *nvis_children,
415                                        int       *nvis_secondaries,
416                                        int       *width,
417                                        int       *height)
418 {
419   GtkButtonBox *bbox;
420   GList *children;
421   gint nchildren;
422   gint nsecondaries;
423   gint needed_width;
424   gint needed_height;
425   GtkRequisition child_requisition;
426   gint ipad_w;
427   gint ipad_h;
428   gint width_default;
429   gint height_default;
430   gint ipad_x_default;
431   gint ipad_y_default;
432
433   gint child_min_width;
434   gint child_min_height;
435   gint ipad_x;
436   gint ipad_y;
437
438   g_return_if_fail (GTK_IS_BUTTON_BOX (widget));
439
440   bbox = GTK_BUTTON_BOX (widget);
441
442   gtk_widget_style_get (widget,
443                         "child-min-width", &width_default,
444                         "child-min-height", &height_default,
445                         "child-internal-pad-x", &ipad_x_default,
446                         "child-internal-pad-y", &ipad_y_default,
447                         NULL);
448
449   child_min_width = width_default;
450   child_min_height = height_default;
451   ipad_x = ipad_x_default;
452   ipad_y = ipad_y_default;
453
454   nchildren = 0;
455   nsecondaries = 0;
456
457   needed_width = child_min_width;
458   needed_height = child_min_height;
459   ipad_w = ipad_x * 2;
460   ipad_h = ipad_y * 2;
461
462   children = gtk_container_get_children (GTK_CONTAINER (bbox));
463   while (children)
464     {
465       GtkWidget *child = children->data;
466       children = children->next;
467
468       if (gtk_widget_get_visible (child))
469         {
470           gboolean is_secondary = FALSE;
471           nchildren += 1;
472           gtk_widget_size_request (child, &child_requisition);
473
474           if (child_requisition.width + ipad_w > needed_width)
475             needed_width = child_requisition.width + ipad_w;
476           if (child_requisition.height + ipad_h > needed_height)
477             needed_height = child_requisition.height + ipad_h;
478
479           gtk_container_child_get (GTK_CONTAINER (bbox), child, "secondary", &is_secondary, NULL);
480
481           if (is_secondary)
482             nsecondaries++;
483         }
484     }
485
486   if (nvis_children)
487     *nvis_children = nchildren;
488   if (nvis_secondaries)
489     *nvis_secondaries = nsecondaries;
490   if (width)
491     *width = needed_width;
492   if (height)
493     *height = needed_height;
494 }
495
496
497 GType
498 psppire_button_flags_get_type (void)
499 {
500   static GType ftype = 0;
501   if (ftype == 0)
502     {
503       static const GFlagsValue values[] =
504         {
505           { PSPPIRE_BUTTON_OK_MASK,      "PSPPIRE_BUTTON_OK_MASK",       "Accept dialog and run it" },
506           { PSPPIRE_BUTTON_GOTO_MASK,    "PSPPIRE_BUTTON_GOTO_MASK",     "Goto case/variable" },
507           { PSPPIRE_BUTTON_CONTINUE_MASK,"PSPPIRE_BUTTON_CONTINUE_MASK", "Accept and close the subdialog" },
508           { PSPPIRE_BUTTON_CANCEL_MASK,  "PSPPIRE_BUTTON_CANCEL_MASK",   "Close dialog and discard settings" },
509           { PSPPIRE_BUTTON_CLOSE_MASK,   "PSPPIRE_BUTTON_CLOSE_MASK",    "Close dialog" },
510           { PSPPIRE_BUTTON_HELP_MASK,    "PSPPIRE_BUTTON_HELP_MASK",     "Invoke context sensitive help" },
511           { PSPPIRE_BUTTON_RESET_MASK,   "PSPPIRE_BUTTON_RESET_MASK",    "Restore dialog to its default settings" },
512           { PSPPIRE_BUTTON_PASTE_MASK,   "PSPPIRE_BUTTON_PASTE_MASK",    "Accept dialog and paste syntax" },
513           { 0, NULL, NULL }
514         };
515       
516       ftype = g_flags_register_static
517         (g_intern_static_string ("PsppireButtonFlags"), values);
518
519     }
520   return ftype;
521 }
522