1c692d42ba2e5af8533398c6962c95b4acf432b1
[pspp] / src / ui / gui / psppire-buttonbox.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 2010, 2011  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 #if !GTK_CHECK_VERSION(2,20,0)
85 /**
86  * gtk_widget_get_realized:
87  * @widget: a #GtkWidget
88  *
89  * Determines whether @widget is realized.
90  *
91  * Return value: %TRUE if @widget is realized, %FALSE otherwise
92  *
93  * Since: 2.20
94  **/
95 static inline gboolean
96 gtk_widget_get_realized (GtkWidget *widget)
97 {
98   g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
99
100   return (GTK_WIDGET_FLAGS (widget) & GTK_REALIZED) != 0;
101 }
102 #endif  /* gtk < 2.20 */
103
104 static void
105 psppire_buttonbox_set_property (GObject         *object,
106                                 guint            prop_id,
107                                 const GValue    *value,
108                                 GParamSpec      *pspec)
109 {
110   gint i;
111   guint flags;
112   PsppireButtonBox *bb = PSPPIRE_BUTTONBOX (object);
113
114   switch (prop_id)
115     {
116     case PROP_BUTTONS:
117       flags = g_value_get_flags (value);
118       for (i = 0 ; i < n_PsppireButtonBoxButtons ; ++i )
119         g_object_set (bb->button[i], "visible", 0x01 & (flags >> i)  , NULL);
120       break;
121
122     case PROP_DEFAULT:
123       bb->def = g_value_get_flags (value);
124       if (gtk_widget_get_realized (GTK_WIDGET (bb)))
125         set_default (bb);
126       break;
127
128     default:
129       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
130     }
131 }
132
133 static void
134 psppire_buttonbox_get_property (GObject         *object,
135                                 guint            prop_id,
136                                 GValue          *value,
137                                 GParamSpec      *pspec)
138 {
139   guint flags = 0;
140   gint i;
141
142   PsppireButtonBox *bb = PSPPIRE_BUTTONBOX (object);
143
144   switch (prop_id)
145     {
146     case PROP_BUTTONS:
147       for (i = 0 ; i < n_PsppireButtonBoxButtons ; ++i )
148         {
149           gboolean visibility;
150           g_object_get (bb->button[i], "visible", &visibility, NULL);
151
152           if ( visibility )
153             flags |= (0x01 << i);
154         }
155
156       g_value_set_flags (value, flags);
157       break;
158
159     case PROP_DEFAULT:
160       g_value_set_flags (value, bb->def);
161
162     default:
163       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
164       break;
165     }
166 }
167
168
169 typedef enum
170   {
171     PSPPIRE_BUTTON_OK_MASK     = (1 << PSPPIRE_BUTTON_OK),
172     PSPPIRE_BUTTON_GOTO_MASK   = (1 << PSPPIRE_BUTTON_GOTO),
173     PSPPIRE_BUTTON_CONTINUE_MASK = (1 << PSPPIRE_BUTTON_CONTINUE),
174     PSPPIRE_BUTTON_CANCEL_MASK = (1 << PSPPIRE_BUTTON_CANCEL),
175     PSPPIRE_BUTTON_HELP_MASK   = (1 << PSPPIRE_BUTTON_HELP),
176     PSPPIRE_BUTTON_RESET_MASK  = (1 << PSPPIRE_BUTTON_RESET),
177     PSPPIRE_BUTTON_PASTE_MASK  = (1 << PSPPIRE_BUTTON_PASTE)
178   } PsppireButtonMask;
179
180 static GParamSpec *button_flags;
181 static GParamSpec *default_flags;
182
183 static void
184 psppire_button_box_class_init (PsppireButtonBoxClass *class)
185 {
186   GObjectClass *object_class = G_OBJECT_CLASS (class);
187
188   object_class->set_property = psppire_buttonbox_set_property;
189   object_class->get_property = psppire_buttonbox_get_property;
190
191   button_flags =
192     g_param_spec_flags ("buttons",
193                         "Buttons",
194                         "The mask that decides what buttons appear in the button box",
195                         PSPPIRE_TYPE_BUTTON_MASK,
196                         PSPPIRE_BUTTON_OK_MASK |
197                         PSPPIRE_BUTTON_CANCEL_MASK |
198                         PSPPIRE_BUTTON_RESET_MASK |
199                         PSPPIRE_BUTTON_HELP_MASK |
200                         PSPPIRE_BUTTON_PASTE_MASK,
201                         G_PARAM_READWRITE);
202   g_object_class_install_property (object_class,
203                                    PROP_BUTTONS,
204                                    button_flags);
205
206   default_flags =
207     g_param_spec_flags ("default",
208                         "Default",
209                         "The mask that decides what what button grabs the default",
210                         PSPPIRE_TYPE_BUTTON_MASK,
211                         0,
212                         G_PARAM_READWRITE);
213   g_object_class_install_property (object_class,
214                                    PROP_DEFAULT,
215                                    default_flags);
216 }
217
218 static void
219 close_and_respond (GtkWidget *w, gint response)
220 {
221   PsppireDialog *dialog;
222
223   GtkWidget *toplevel = gtk_widget_get_toplevel (w);
224
225   /* If we're not in a psppire dialog (for example when in glade)
226      then do nothing */
227   if ( ! PSPPIRE_IS_DIALOG (toplevel))
228     return;
229
230   dialog = PSPPIRE_DIALOG (toplevel);
231
232   dialog->response = response;
233
234   psppire_dialog_close (dialog);
235 }
236
237
238 static void
239 close_dialog (GtkWidget *w, gpointer data)
240 {
241   close_and_respond (w, GTK_RESPONSE_CLOSE);
242 }
243
244 static void
245 continue_button_clicked (GtkWidget *w, gpointer data)
246 {
247   close_and_respond (w, PSPPIRE_RESPONSE_CONTINUE);
248 }
249
250
251 static void
252 ok_button_clicked (GtkWidget *w, gpointer data)
253 {
254   close_and_respond (w, GTK_RESPONSE_OK);
255 }
256
257
258 static void
259 paste_button_clicked (GtkWidget *w, gpointer data)
260 {
261   close_and_respond (w, PSPPIRE_RESPONSE_PASTE);
262 }
263
264 static void
265 goto_button_clicked (GtkWidget *w, gpointer data)
266 {
267   close_and_respond (w, PSPPIRE_RESPONSE_GOTO);
268 }
269
270
271 static void
272 refresh_clicked (GtkWidget *w, gpointer data)
273 {
274   GtkWidget *toplevel = gtk_widget_get_toplevel (w);
275   PsppireDialog *dialog;
276
277   if ( ! PSPPIRE_IS_DIALOG (toplevel))
278     return;
279
280   dialog = PSPPIRE_DIALOG (toplevel);
281
282   psppire_dialog_reload (dialog);
283 }
284
285
286
287 static void
288 on_validity_change (GtkWidget *toplevel, gboolean valid, gpointer data)
289 {
290   PsppireButtonBox *bb = data;
291
292   /* Set the sensitivity of all the 'executive order' buttons */
293   gtk_widget_set_sensitive (GTK_WIDGET (bb->button[PSPPIRE_BUTTON_OK]), valid);
294   gtk_widget_set_sensitive (GTK_WIDGET (bb->button[PSPPIRE_BUTTON_PASTE]), valid);
295   gtk_widget_set_sensitive (GTK_WIDGET (bb->button[PSPPIRE_BUTTON_GOTO]), valid);
296   gtk_widget_set_sensitive (GTK_WIDGET (bb->button[PSPPIRE_BUTTON_CONTINUE]), valid);
297 }
298
299 static void
300 on_realize (GtkWidget *buttonbox, gpointer data)
301 {
302   GtkWidget *toplevel = gtk_widget_get_toplevel (buttonbox);
303
304   if ( PSPPIRE_IS_DIALOG (toplevel))
305     {
306       g_signal_connect (toplevel, "validity-changed",
307                         G_CALLBACK (on_validity_change), buttonbox);
308     }
309   set_default (PSPPIRE_BUTTONBOX (buttonbox));
310 }
311
312 static void
313 psppire_button_box_init (PsppireButtonBox *bb)
314 {
315   bb->def = PSPPIRE_BUTTON_CONTINUE;
316
317   bb->button[PSPPIRE_BUTTON_OK] = gtk_button_new_from_stock (GTK_STOCK_OK);
318   psppire_box_pack_start_defaults (GTK_BOX (bb), bb->button[PSPPIRE_BUTTON_OK]);
319   g_signal_connect (bb->button[PSPPIRE_BUTTON_OK], "clicked",
320                     G_CALLBACK (ok_button_clicked), NULL);
321   g_object_set (bb->button[PSPPIRE_BUTTON_OK], "no-show-all", TRUE, NULL);
322
323
324   bb->button[PSPPIRE_BUTTON_GOTO] =
325     gtk_button_new_from_stock (GTK_STOCK_JUMP_TO);
326   psppire_box_pack_start_defaults (GTK_BOX (bb), bb->button[PSPPIRE_BUTTON_GOTO]);
327   g_signal_connect (bb->button[PSPPIRE_BUTTON_GOTO], "clicked",
328                     G_CALLBACK (goto_button_clicked), NULL);
329   g_object_set (bb->button[PSPPIRE_BUTTON_GOTO], "no-show-all", TRUE, NULL);
330
331
332   bb->button[PSPPIRE_BUTTON_CONTINUE] =
333     gtk_button_new_with_mnemonic (_("Continue"));
334
335   psppire_box_pack_start_defaults (GTK_BOX (bb),
336                                bb->button[PSPPIRE_BUTTON_CONTINUE]);
337   g_signal_connect (bb->button[PSPPIRE_BUTTON_CONTINUE], "clicked",
338                     G_CALLBACK (continue_button_clicked), NULL);
339
340   g_object_set (bb->button[PSPPIRE_BUTTON_CONTINUE],
341                 "no-show-all", TRUE, NULL);
342
343
344
345   bb->button[PSPPIRE_BUTTON_PASTE] = gtk_button_new_from_stock (GTK_STOCK_PASTE);
346   g_signal_connect (bb->button[PSPPIRE_BUTTON_PASTE], "clicked",
347                     G_CALLBACK (paste_button_clicked), NULL);
348   psppire_box_pack_start_defaults (GTK_BOX (bb), bb->button[PSPPIRE_BUTTON_PASTE]);
349   g_object_set (bb->button[PSPPIRE_BUTTON_PASTE], "no-show-all", TRUE, NULL);
350
351   bb->button[PSPPIRE_BUTTON_CANCEL] = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
352   g_signal_connect (bb->button[PSPPIRE_BUTTON_CANCEL], "clicked",
353                     G_CALLBACK (close_dialog), NULL);
354   psppire_box_pack_start_defaults (GTK_BOX (bb), bb->button[PSPPIRE_BUTTON_CANCEL]);
355   g_object_set (bb->button[PSPPIRE_BUTTON_CANCEL], "no-show-all", TRUE, NULL);
356
357
358   bb->button[PSPPIRE_BUTTON_RESET] = gtk_button_new_from_stock ("pspp-stock-reset");
359   g_signal_connect (bb->button[PSPPIRE_BUTTON_RESET], "clicked",
360                     G_CALLBACK (refresh_clicked), NULL);
361   psppire_box_pack_start_defaults (GTK_BOX (bb), bb->button[PSPPIRE_BUTTON_RESET]);
362   g_object_set (bb->button[PSPPIRE_BUTTON_RESET], "no-show-all", TRUE, NULL);
363
364
365   bb->button[PSPPIRE_BUTTON_HELP] = gtk_button_new_from_stock (GTK_STOCK_HELP);
366   psppire_box_pack_start_defaults (GTK_BOX (bb), bb->button[PSPPIRE_BUTTON_HELP]);
367   g_object_set (bb->button[PSPPIRE_BUTTON_HELP], "no-show-all", TRUE, NULL);
368
369
370   /* Set the default visibilities */
371   {
372     GValue value = { 0 };
373     guint flags;
374     gint i;
375     g_value_init (&value, button_flags->value_type);
376     g_param_value_set_default(button_flags, &value);
377
378
379     flags = g_value_get_flags (&value);
380
381     for (i = 0 ; i < n_PsppireButtonBoxButtons ; ++i )
382       g_object_set (bb->button[i], "visible", 0x01 & (flags >> i)  , NULL);
383
384     g_value_unset (&value);
385   }
386
387
388   g_signal_connect (bb, "realize", G_CALLBACK (on_realize), NULL);
389 }
390
391
392 /* This function is lifted verbatim from the Gtk2.10.6 library */
393
394 void
395 _psppire_button_box_child_requisition (GtkWidget *widget,
396                                        int       *nvis_children,
397                                        int       *nvis_secondaries,
398                                        int       *width,
399                                        int       *height)
400 {
401   GtkButtonBox *bbox;
402   GtkBoxChild *child;
403   GList *children;
404   gint nchildren;
405   gint nsecondaries;
406   gint needed_width;
407   gint needed_height;
408   GtkRequisition child_requisition;
409   gint ipad_w;
410   gint ipad_h;
411   gint width_default;
412   gint height_default;
413   gint ipad_x_default;
414   gint ipad_y_default;
415
416   gint child_min_width;
417   gint child_min_height;
418   gint ipad_x;
419   gint ipad_y;
420
421   g_return_if_fail (GTK_IS_BUTTON_BOX (widget));
422
423   bbox = GTK_BUTTON_BOX (widget);
424
425   gtk_widget_style_get (widget,
426                         "child-min-width", &width_default,
427                         "child-min-height", &height_default,
428                         "child-internal-pad-x", &ipad_x_default,
429                         "child-internal-pad-y", &ipad_y_default,
430                         NULL);
431
432   child_min_width = bbox->child_min_width   != GTK_BUTTONBOX_DEFAULT
433     ? bbox->child_min_width : width_default;
434   child_min_height = bbox->child_min_height !=GTK_BUTTONBOX_DEFAULT
435     ? bbox->child_min_height : height_default;
436   ipad_x = bbox->child_ipad_x != GTK_BUTTONBOX_DEFAULT
437     ? bbox->child_ipad_x : ipad_x_default;
438   ipad_y = bbox->child_ipad_y != GTK_BUTTONBOX_DEFAULT
439     ? bbox->child_ipad_y : ipad_y_default;
440
441   nchildren = 0;
442   nsecondaries = 0;
443   children = GTK_BOX(bbox)->children;
444   needed_width = child_min_width;
445   needed_height = child_min_height;
446   ipad_w = ipad_x * 2;
447   ipad_h = ipad_y * 2;
448
449   while (children)
450     {
451       child = children->data;
452       children = children->next;
453
454       if (GTK_WIDGET_VISIBLE (child->widget))
455         {
456           nchildren += 1;
457           gtk_widget_size_request (child->widget, &child_requisition);
458
459           if (child_requisition.width + ipad_w > needed_width)
460             needed_width = child_requisition.width + ipad_w;
461           if (child_requisition.height + ipad_h > needed_height)
462             needed_height = child_requisition.height + ipad_h;
463           if (child->is_secondary)
464             nsecondaries++;
465         }
466     }
467
468   if (nvis_children)
469     *nvis_children = nchildren;
470   if (nvis_secondaries)
471     *nvis_secondaries = nsecondaries;
472   if (width)
473     *width = needed_width;
474   if (height)
475     *height = needed_height;
476 }
477
478
479 GType
480 psppire_button_flags_get_type (void)
481 {
482   static GType ftype = 0;
483   if (ftype == 0)
484     {
485       static const GFlagsValue values[] =
486         {
487           { PSPPIRE_BUTTON_OK_MASK,     "PSPPIRE_BUTTON_OK_MASK",     N_("OK") },
488           { PSPPIRE_BUTTON_GOTO_MASK,   "PSPPIRE_BUTTON_GOTO_MASK", N_("Go To") },
489           { PSPPIRE_BUTTON_CONTINUE_MASK,"PSPPIRE_BUTTON_CONTINUE_MASK", N_("Continue") },
490           { PSPPIRE_BUTTON_CANCEL_MASK, "PSPPIRE_BUTTON_CANCEL_MASK", N_("Cancel") },
491           { PSPPIRE_BUTTON_HELP_MASK,   "PSPPIRE_BUTTON_HELP_MASK",   N_("Help") },
492           { PSPPIRE_BUTTON_RESET_MASK,  "PSPPIRE_BUTTON_RESET_MASK",  N_("Reset") },
493           { PSPPIRE_BUTTON_PASTE_MASK,  "PSPPIRE_BUTTON_PASTE_MASK",  N_("Paste") },
494           { 0, NULL, NULL }
495         };
496
497       ftype = g_flags_register_static
498         (g_intern_static_string ("PsppireButtonFlags"), values);
499
500     }
501   return ftype;
502 }
503