45a087e0d9d845fd0a605c3df3aa149a486a831f
[geeqie.git] / src / ui_misc.c
1 /*
2  * (SLIK) SimpLIstic sKin functions
3  * (C) 2004 John Ellis
4  * Copyright (C) 2008 - 2012 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13 #ifdef HAVE_CONFIG_H
14 #  include "config.h"
15 #endif
16 #include "intl.h"
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21
22 #include <gtk/gtk.h>
23 #include <gdk/gdkkeysyms.h>
24
25 #include "main.h"
26 #include "ui_misc.h"
27
28 #include "history_list.h"
29
30
31 /*
32  *-----------------------------------------------------------------------------
33  * widget and layout utilities
34  *-----------------------------------------------------------------------------
35  */
36
37 GtkWidget *pref_box_new(GtkWidget *parent_box, gboolean fill,
38                         GtkOrientation orientation, gboolean padding)
39 {
40         GtkWidget *box;
41
42         if (orientation == GTK_ORIENTATION_HORIZONTAL)
43                 {
44                 box = gtk_hbox_new(FALSE, padding);
45                 }
46         else
47                 {
48                 box = gtk_vbox_new(FALSE, padding);
49                 }
50
51         gtk_box_pack_start(GTK_BOX(parent_box), box, fill, fill, 0);
52         gtk_widget_show(box);
53
54         return box;
55 }
56
57 GtkWidget *pref_group_new(GtkWidget *parent_box, gboolean fill,
58                           const gchar *text, GtkOrientation orientation)
59 {
60         GtkWidget *box;
61         GtkWidget *vbox;
62         GtkWidget *hbox;
63         GtkWidget *label;
64
65         vbox = gtk_vbox_new(FALSE, PREF_PAD_GAP);
66
67         /* add additional spacing if necessary */
68         if (GTK_IS_VBOX(parent_box))
69                 {
70                 GList *list = gtk_container_get_children(GTK_CONTAINER(parent_box));
71                 if (list)
72                         {
73                         pref_spacer(vbox, PREF_PAD_GROUP - PREF_PAD_GAP);
74                         }
75                 g_list_free(list);
76                 }
77
78         gtk_box_pack_start(GTK_BOX(parent_box), vbox, fill, fill, 0);
79         gtk_widget_show(vbox);
80
81         label = gtk_label_new(text);
82         gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
83         pref_label_bold(label, TRUE, FALSE);
84
85         gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
86         gtk_widget_show(label);
87
88         hbox = gtk_hbox_new(FALSE, PREF_PAD_INDENT);
89         gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
90         gtk_widget_show(hbox);
91
92         /* indent using empty box */
93         pref_spacer(hbox, 0);
94
95         if (orientation == GTK_ORIENTATION_HORIZONTAL)
96                 {
97                 box = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
98                 }
99         else
100                 {
101                 box = gtk_vbox_new(FALSE, PREF_PAD_GAP);
102                 }
103         gtk_box_pack_start(GTK_BOX(hbox), box, TRUE, TRUE, 0);
104         gtk_widget_show(box);
105
106         g_object_set_data(G_OBJECT(box), "pref_group", vbox);
107
108         return box;
109 }
110
111 GtkWidget *pref_group_parent(GtkWidget *child)
112 {
113         GtkWidget *parent;
114
115         parent = child;
116         while (parent)
117                 {
118                 GtkWidget *group;
119
120                 group = g_object_get_data(G_OBJECT(parent), "pref_group");
121                 if (group && GTK_IS_WIDGET(group)) return group;
122
123                 parent = gtk_widget_get_parent(parent);
124                 }
125
126         return child;
127 }
128
129 GtkWidget *pref_frame_new(GtkWidget *parent_box, gboolean fill,
130                           const gchar *text,
131                           GtkOrientation orientation, gboolean padding)
132 {
133         GtkWidget *box;
134         GtkWidget *frame = NULL;
135
136         frame = gtk_frame_new(text);
137         gtk_box_pack_start(GTK_BOX(parent_box), frame, fill, fill, 0);
138         gtk_widget_show(frame);
139
140         if (orientation == GTK_ORIENTATION_HORIZONTAL)
141                 {
142                 box = gtk_hbox_new(FALSE, padding);
143                 }
144         else
145                 {
146                 box = gtk_vbox_new(FALSE, padding);
147                 }
148         gtk_container_add(GTK_CONTAINER(frame), box);
149         gtk_container_set_border_width(GTK_CONTAINER(box), PREF_PAD_BORDER);
150         gtk_widget_show(box);
151
152         return box;
153 }
154
155 GtkWidget *pref_spacer(GtkWidget *parent_box, gboolean padding)
156 {
157         GtkWidget *spacer;
158
159         spacer = gtk_hbox_new(FALSE, 0);
160         gtk_box_pack_start(GTK_BOX(parent_box), spacer, FALSE, FALSE, padding / 2);
161         gtk_widget_show(spacer);
162
163         return spacer;
164 }
165
166 GtkWidget *pref_line(GtkWidget *parent_box, gboolean padding)
167 {
168         GtkWidget *spacer;
169
170         if (GTK_IS_HBOX(parent_box))
171                 {
172                 spacer = gtk_vseparator_new();
173                 }
174         else
175                 {
176                 spacer = gtk_hseparator_new();
177                 }
178
179         gtk_box_pack_start(GTK_BOX(parent_box), spacer, FALSE, FALSE, padding / 2);
180         gtk_widget_show(spacer);
181
182         return spacer;
183 }
184
185 GtkWidget *pref_label_new(GtkWidget *parent_box, const gchar *text)
186 {
187         GtkWidget *label;
188
189         label = gtk_label_new(text);
190         gtk_box_pack_start(GTK_BOX(parent_box), label, FALSE, FALSE, 0);
191         gtk_widget_show(label);
192
193         return label;
194 }
195
196 GtkWidget *pref_label_new_mnemonic(GtkWidget *parent_box, const gchar *text, GtkWidget *widget)
197 {
198         GtkWidget *label;
199
200         label = gtk_label_new_with_mnemonic(text);
201         gtk_label_set_mnemonic_widget(GTK_LABEL(label), widget);
202         gtk_box_pack_start(GTK_BOX(parent_box), label, FALSE, FALSE, 0);
203         gtk_widget_show(label);
204
205         return label;
206 }
207
208 void pref_label_bold(GtkWidget *label, gboolean bold, gboolean increase_size)
209 {
210         PangoAttrList *pal;
211         PangoAttribute *pa;
212
213         if (!bold && !increase_size) return;
214
215         pal = pango_attr_list_new();
216
217         if (bold)
218                 {
219                 pa = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
220                 pa->start_index = 0;
221                 pa->end_index = G_MAXINT;
222                 pango_attr_list_insert(pal, pa);
223                 }
224
225         if (increase_size)
226                 {
227                 pa = pango_attr_scale_new(PANGO_SCALE_LARGE);
228                 pa->start_index = 0;
229                 pa->end_index = G_MAXINT;
230                 pango_attr_list_insert(pal, pa);
231                 }
232
233         gtk_label_set_attributes(GTK_LABEL(label), pal);
234         pango_attr_list_unref(pal);
235 }
236
237 GtkWidget *pref_button_new(GtkWidget *parent_box, const gchar *stock_id,
238                            const gchar *text, gboolean hide_stock_text,
239                            GCallback func, gpointer data)
240 {
241         GtkWidget *button;
242
243         if (stock_id && !text && !hide_stock_text)
244                 {
245                 button = gtk_button_new_from_stock(stock_id);
246                 }
247         else
248                 {
249                 GtkWidget *image = NULL;
250                 GtkWidget *label = NULL;
251
252                 button = gtk_button_new();
253
254                 if (stock_id) image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_BUTTON);
255                 if (text)
256                         {
257                         label = gtk_label_new_with_mnemonic(text);
258                         gtk_misc_set_alignment(GTK_MISC(label), 0.5, 0.5);
259                         gtk_label_set_mnemonic_widget(GTK_LABEL(label), button);
260                         }
261
262                 if (image && label)
263                         {
264                         GtkWidget *align;
265                         GtkWidget *hbox;
266
267                         hbox = gtk_hbox_new(FALSE, PREF_PAD_BUTTON_ICON_GAP);
268
269                         align = gtk_alignment_new(0.5, 0.5, 0.0, 0.0);
270                         gtk_container_add(GTK_CONTAINER(button), align);
271                         gtk_widget_show(align);
272
273                         gtk_container_add(GTK_CONTAINER(align), hbox);
274                         gtk_widget_show(hbox);
275
276                         gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
277                         gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0);
278                         }
279                 else
280                         {
281                         if (image)
282                                 {
283                                 gtk_container_add(GTK_CONTAINER(button), image);
284                                 }
285                         else if (label)
286                                 {
287                                 gtk_container_add(GTK_CONTAINER(button), label);
288                                 }
289                         }
290
291                 if (image) gtk_widget_show(image);
292                 if (label) gtk_widget_show(label);
293                 }
294
295         if (func) g_signal_connect(G_OBJECT(button), "clicked", func, data);
296
297         if (parent_box)
298                 {
299                 gtk_box_pack_start(GTK_BOX(parent_box), button, FALSE, FALSE, 0);
300                 gtk_widget_show(button);
301                 }
302
303         return button;
304 }
305
306 static GtkWidget *real_pref_checkbox_new(GtkWidget *parent_box, const gchar *text, gboolean mnemonic_text,
307                                          gboolean active, GCallback func, gpointer data)
308 {
309         GtkWidget *button;
310
311         if (mnemonic_text)
312                 {
313                 button = gtk_check_button_new_with_mnemonic(text);
314                 }
315         else
316                 {
317                 button = gtk_check_button_new_with_label(text);
318                 }
319         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), active);
320         if (func) g_signal_connect(G_OBJECT(button), "clicked", func, data);
321
322         gtk_box_pack_start(GTK_BOX(parent_box), button, FALSE, FALSE, 0);
323         gtk_widget_show(button);
324
325         return button;
326 }
327
328 GtkWidget *pref_checkbox_new(GtkWidget *parent_box, const gchar *text, gboolean active,
329                              GCallback func, gpointer data)
330 {
331         return real_pref_checkbox_new(parent_box, text, FALSE, active, func, data);
332 }
333
334 GtkWidget *pref_checkbox_new_mnemonic(GtkWidget *parent_box, const gchar *text, gboolean active,
335                                       GCallback func, gpointer data)
336 {
337         return real_pref_checkbox_new(parent_box, text, TRUE, active, func, data);
338 }
339
340 static void pref_checkbox_int_cb(GtkWidget *widget, gpointer data)
341 {
342         gboolean *result = data;
343
344         *result = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
345 }
346
347 GtkWidget *pref_checkbox_new_int(GtkWidget *parent_box, const gchar *text, gboolean active,
348                                  gboolean *result)
349 {
350         GtkWidget *button;
351
352         button = pref_checkbox_new(parent_box, text, active,
353                                    G_CALLBACK(pref_checkbox_int_cb), result);
354         *result = active;
355
356         return button;
357 }
358
359 static void pref_checkbox_link_sensitivity_cb(GtkWidget *button, gpointer data)
360 {
361         GtkWidget *widget = data;
362
363         gtk_widget_set_sensitive(widget, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)));
364 }
365
366 void pref_checkbox_link_sensitivity(GtkWidget *button, GtkWidget *widget)
367 {
368         g_signal_connect(G_OBJECT(button), "toggled",
369                          G_CALLBACK(pref_checkbox_link_sensitivity_cb), widget);
370
371         pref_checkbox_link_sensitivity_cb(button, widget);
372 }
373
374 static void pref_checkbox_link_sensitivity_swap_cb(GtkWidget *button, gpointer data)
375 {
376         GtkWidget *widget = data;
377
378         gtk_widget_set_sensitive(widget, !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)));
379 }
380
381 void pref_checkbox_link_sensitivity_swap(GtkWidget *button, GtkWidget *widget)
382 {
383         g_signal_connect(G_OBJECT(button), "toggled",
384                          G_CALLBACK(pref_checkbox_link_sensitivity_swap_cb), widget);
385
386         pref_checkbox_link_sensitivity_swap_cb(button, widget);
387 }
388
389 static GtkWidget *real_pref_radiobutton_new(GtkWidget *parent_box, GtkWidget *sibling,
390                                             const gchar *text, gboolean mnemonic_text, gboolean active,
391                                             GCallback func, gpointer data)
392 {
393         GtkWidget *button;
394         GSList *group;
395
396         if (sibling)
397                 {
398                 group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(sibling));
399                 }
400         else
401                 {
402                 group = NULL;
403                 }
404
405         if (mnemonic_text)
406                 {
407                 button = gtk_radio_button_new_with_mnemonic(group, text);
408                 }
409         else
410                 {
411                 button = gtk_radio_button_new_with_label(group, text);
412                 }
413
414         if (active) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), active);
415         if (func) g_signal_connect(G_OBJECT(button), "clicked", func, data);
416
417         gtk_box_pack_start(GTK_BOX(parent_box), button, FALSE, FALSE, 0);
418         gtk_widget_show(button);
419
420         return button;
421 }
422
423 GtkWidget *pref_radiobutton_new(GtkWidget *parent_box, GtkWidget *sibling,
424                                 const gchar *text, gboolean active,
425                                 GCallback func, gpointer data)
426 {
427         return real_pref_radiobutton_new(parent_box, sibling, text, FALSE, active, func, data);
428 }
429
430 GtkWidget *pref_radiobutton_new_mnemonic(GtkWidget *parent_box, GtkWidget *sibling,
431                                          const gchar *text, gboolean active,
432                                          GCallback func, gpointer data)
433 {
434         return real_pref_radiobutton_new(parent_box, sibling, text, TRUE, active, func, data);
435 }
436
437 #define PREF_RADIO_VALUE_KEY "pref_radio_value"
438
439 static void pref_radiobutton_int_cb(GtkWidget *widget, gpointer data)
440 {
441         gboolean *result = data;
442
443         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
444                 {
445                 *result = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), PREF_RADIO_VALUE_KEY));
446                 }
447 }
448
449 GtkWidget *pref_radiobutton_new_int(GtkWidget *parent_box, GtkWidget *sibling,
450                                     const gchar *text, gboolean active,
451                                     gboolean *result, gboolean value,
452                                     GCallback func, gpointer data)
453 {
454         GtkWidget *button;
455
456         button = pref_radiobutton_new(parent_box, sibling, text, active,
457                                       G_CALLBACK(pref_radiobutton_int_cb), result);
458         g_object_set_data(G_OBJECT(button), PREF_RADIO_VALUE_KEY, GINT_TO_POINTER(value));
459         if (active) *result = value;
460
461         return button;
462 }
463
464 static GtkWidget *real_pref_spin_new(GtkWidget *parent_box, const gchar *text, const gchar *suffix,
465                                      gboolean mnemonic_text,
466                                      gdouble min, gdouble max, gdouble step, gint digits,
467                                      gdouble value,
468                                      GCallback func, gpointer data)
469 {
470         GtkWidget *spin;
471         GtkWidget *box;
472         GtkWidget *label;
473
474         box = pref_box_new(parent_box, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
475
476         spin = gtk_spin_button_new_with_range(min, max, step);
477         gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), digits);
478         gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), value);
479
480         if (func)
481                 {
482                 g_signal_connect(G_OBJECT(spin), "value_changed", G_CALLBACK(func), data);
483                 }
484
485         if (text)
486                 {
487                 if (mnemonic_text)
488                         {
489                         label = pref_label_new_mnemonic(box, text, spin);
490                         }
491                 else
492                         {
493                         label = pref_label_new(box, text);
494                         }
495                 pref_link_sensitivity(label, spin);
496                 }
497
498         gtk_box_pack_start(GTK_BOX(box), spin, FALSE, FALSE, 0);
499         gtk_widget_show(spin);
500
501         /* perhaps this should only be PREF_PAD_GAP distance from spinbutton ? */
502         if (suffix)
503                 {
504                 label =  pref_label_new(box, suffix);
505                 pref_link_sensitivity(label, spin);
506                 }
507
508         return spin;
509 }
510
511 GtkWidget *pref_spin_new(GtkWidget *parent_box, const gchar *text, const gchar *suffix,
512                          gdouble min, gdouble max, gdouble step, gint digits,
513                          gdouble value,
514                          GCallback func, gpointer data)
515 {
516         return real_pref_spin_new(parent_box, text, suffix, FALSE,
517                                   min, max, step, digits, value, func, data);
518 }
519
520 GtkWidget *pref_spin_new_mnemonic(GtkWidget *parent_box, const gchar *text, const gchar *suffix,
521                                   gdouble min, gdouble max, gdouble step, gint digits,
522                                   gdouble value,
523                                   GCallback func, gpointer data)
524 {
525         return real_pref_spin_new(parent_box, text, suffix, TRUE,
526                                   min, max, step, digits, value, func, data);
527 }
528
529 static void pref_spin_int_cb(GtkWidget *widget, gpointer data)
530 {
531         gint *var = data;
532         *var = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget));
533 }
534
535 GtkWidget *pref_spin_new_int(GtkWidget *parent_box, const gchar *text, const gchar *suffix,
536                              gint min, gint max, gint step,
537                              gint value, gint *value_var)
538 {
539         *value_var = value;
540         return pref_spin_new(parent_box, text, suffix,
541                              (gdouble)min, (gdouble)max, (gdouble)step, 0,
542                              value,
543                              G_CALLBACK(pref_spin_int_cb), value_var);
544 }
545
546 #if 0
547 void pref_spin_set_blocking(GtkWidget *spin, gdouble value, gpointer block_data)
548 {
549         g_signal_handlers_block_matched(G_OBJECT(spin), G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, block_data);
550         gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), value);
551         g_signal_handlers_unblock_matched(G_OBJECT(spin), G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, block_data);
552 }
553 #endif
554
555 static void pref_link_sensitivity_cb(GtkWidget *watch, GtkStateType prev_state, gpointer data)
556 {
557         GtkWidget *widget = data;
558
559         gtk_widget_set_sensitive(widget, gtk_widget_is_sensitive(watch));
560 }
561
562 void pref_link_sensitivity(GtkWidget *widget, GtkWidget *watch)
563 {
564         g_signal_connect(G_OBJECT(watch), "state_changed",
565                          G_CALLBACK(pref_link_sensitivity_cb), widget);
566 }
567
568 void pref_signal_block_data(GtkWidget *widget, gpointer data)
569 {
570         g_signal_handlers_block_matched(widget, G_SIGNAL_MATCH_DATA,
571                                         0, 0, NULL, NULL, data);
572 }
573
574 void pref_signal_unblock_data(GtkWidget *widget, gpointer data)
575 {
576         g_signal_handlers_unblock_matched(widget, G_SIGNAL_MATCH_DATA,
577                                           0, 0, NULL, NULL, data);
578 }
579
580 GtkWidget *pref_table_new(GtkWidget *parent_box, gint columns, gint rows,
581                           gboolean homogeneous, gboolean fill)
582 {
583         GtkWidget *table;
584
585         table = gtk_table_new(rows, columns, homogeneous);
586         gtk_table_set_row_spacings(GTK_TABLE(table), PREF_PAD_GAP);
587         gtk_table_set_col_spacings(GTK_TABLE(table), PREF_PAD_SPACE);
588
589         if (parent_box)
590                 {
591                 gtk_box_pack_start(GTK_BOX(parent_box), table, fill, fill, 0);
592                 gtk_widget_show(table);
593                 }
594
595         return table;
596 }
597
598 GtkWidget *pref_table_box(GtkWidget *table, gint column, gint row,
599                           GtkOrientation orientation, const gchar *text)
600 {
601         GtkWidget *box;
602         GtkWidget *shell;
603
604         if (text)
605                 {
606                 shell = gtk_vbox_new(FALSE, 0);
607                 box = pref_group_new(shell, TRUE, text, orientation);
608                 }
609         else
610                 {
611                 if (orientation == GTK_ORIENTATION_HORIZONTAL)
612                         {
613                         box = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
614                         }
615                 else
616                         {
617                         box = gtk_vbox_new(FALSE, PREF_PAD_GAP);
618                         }
619                 shell = box;
620                 }
621
622         gtk_table_attach(GTK_TABLE(table), shell, column, column + 1, row, row + 1,
623                          GTK_EXPAND | GTK_FILL, 0, 0, 0);
624
625         gtk_widget_show(shell);
626
627         return box;
628 }
629
630 GtkWidget *pref_table_label(GtkWidget *table, gint column, gint row,
631                             const gchar *text, gfloat alignment)
632 {
633         GtkWidget *label;
634         GtkWidget *align;
635
636         align = gtk_alignment_new(alignment, 0.50, 0.0, 0.0);
637         gtk_table_attach(GTK_TABLE(table), align, column, column + 1, row, row + 1,
638                          GTK_FILL, 0, 0, 0);
639         gtk_widget_show(align);
640         label = gtk_label_new(text);
641         gtk_container_add(GTK_CONTAINER(align), label);
642         gtk_widget_show(label);
643
644         return label;
645 }
646
647 GtkWidget *pref_table_button(GtkWidget *table, gint column, gint row,
648                              const gchar *stock_id, const gchar *text, gboolean hide_stock_text,
649                              GCallback func, gpointer data)
650 {
651         GtkWidget *button;
652
653         button = pref_button_new(NULL, stock_id, text, hide_stock_text, func, data);
654         gtk_table_attach(GTK_TABLE(table), button, column, column + 1, row, row + 1,
655                          GTK_FILL, 0, 0, 0);
656         gtk_widget_show(button);
657
658         return button;
659 }
660
661 #if 0
662 static GtkWidget *pref_table_checkbox(GtkWidget *table, gint column, gint row,
663                                       const gchar *text, gint active,
664                                       GCallback func, gpointer data)
665 {
666         GtkWidget *button;
667
668         button = gtk_check_button_new_with_label(text);
669         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), active);
670         if (func) g_signal_connect(G_OBJECT(button), "clicked", func, data);
671
672         gtk_table_attach(GTK_TABLE(table), button, column, column + 1, row, row + 1,
673                          GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
674         gtk_widget_show(button);
675
676         return button;
677 }
678 #endif
679
680 GtkWidget *pref_table_spin(GtkWidget *table, gint column, gint row,
681                            const gchar *text, const gchar *suffix,
682                            gdouble min, gdouble max, gdouble step, gint digits,
683                            gdouble value,
684                            GCallback func, gpointer data)
685 {
686         GtkWidget *spin;
687         GtkWidget *box;
688         GtkWidget *label;
689
690         spin = gtk_spin_button_new_with_range(min, max, step);
691         gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), digits);
692         gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), value);
693         if (func)
694                 {
695                 g_signal_connect(G_OBJECT(spin), "value_changed", G_CALLBACK(func), data);
696                 }
697
698         if (text)
699                 {
700                 label = pref_table_label(table, column, row, text, 1.0);
701                 pref_link_sensitivity(label, spin);
702                 column++;
703                 }
704
705         if (suffix)
706                 {
707                 box = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
708                 gtk_box_pack_start(GTK_BOX(box), spin, FALSE, FALSE, 0);
709                 gtk_widget_show(spin);
710
711                 label = pref_label_new(box, suffix);
712                 pref_link_sensitivity(label, spin);
713                 }
714         else
715                 {
716                 box = spin;
717                 }
718
719         gtk_table_attach(GTK_TABLE(table), box, column, column + 1, row, row + 1,
720                          GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
721         gtk_widget_show(box);
722
723         return spin;
724 }
725
726 GtkWidget *pref_table_spin_new_int(GtkWidget *table, gint column, gint row,
727                                    const gchar *text, const gchar *suffix,
728                                    gint min, gint max, gint step,
729                                    gint value, gint *value_var)
730 {
731         *value_var = value;
732         return pref_table_spin(table, column, row,
733                                text, suffix,
734                                (gdouble)min, (gdouble)max, (gdouble)step, 0,
735                                value,
736                                G_CALLBACK(pref_spin_int_cb), value_var);
737 }
738
739
740 GtkWidget *pref_toolbar_new(GtkWidget *parent_box, GtkToolbarStyle style)
741 {
742         GtkWidget *tbar;
743         
744         tbar = gtk_toolbar_new();
745         gtk_toolbar_set_style(GTK_TOOLBAR(tbar), style);
746
747         if (parent_box)
748                 {
749                 gtk_box_pack_start(GTK_BOX(parent_box), tbar, FALSE, FALSE, 0);
750                 gtk_widget_show(tbar);
751                 }
752         return tbar;
753 }
754
755 GtkWidget *pref_toolbar_button(GtkWidget *toolbar,
756                                const gchar *stock_id, const gchar *label, gboolean toggle,
757                                const gchar *description,
758                                GCallback func, gpointer data)
759 {
760         GtkWidget *item;
761
762         if (toggle)
763                 {
764                 if (stock_id)
765                         {
766                         item = GTK_WIDGET(gtk_toggle_tool_button_new_from_stock(stock_id));
767                         }
768                 else
769                         {
770                         item = GTK_WIDGET(gtk_toggle_tool_button_new());
771                         }
772                 }
773         else
774                 {
775                 if (stock_id)
776                         {
777                         item = GTK_WIDGET(gtk_tool_button_new_from_stock(stock_id));
778                         }
779                 else
780                         {
781                         item = GTK_WIDGET(gtk_tool_button_new(NULL, NULL));
782                         }
783                 }
784         gtk_tool_button_set_use_underline(GTK_TOOL_BUTTON(item), TRUE);
785
786         if (label) gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), label);
787
788         if (func) g_signal_connect(item, "clicked", func, data);
789         gtk_container_add(GTK_CONTAINER(toolbar), item);
790         gtk_widget_show(item);
791
792         if (description)
793                 {
794                 gtk_widget_set_tooltip_text(item, description);
795                 }
796
797         return item;
798 }
799
800 void pref_toolbar_button_set_icon(GtkWidget *button, GtkWidget *widget, const gchar *stock_id)
801 {
802         if (widget)
803                 {
804                 gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(button), widget);
805                 }
806         else if (stock_id)
807                 {
808                 gtk_tool_button_set_stock_id(GTK_TOOL_BUTTON(button), stock_id);
809                 }
810 }
811
812 GtkWidget *pref_toolbar_spacer(GtkWidget *toolbar)
813 {
814         GtkWidget *item;
815
816         item = GTK_WIDGET(gtk_separator_tool_item_new());
817         gtk_container_add(GTK_CONTAINER(toolbar), item);
818         gtk_widget_show(item);
819
820         return item;
821 }
822
823
824 /*
825  *-----------------------------------------------------------------------------
826  * date selection entry
827  *-----------------------------------------------------------------------------
828  */
829
830 #define DATE_SELECION_KEY "date_selection_data"
831
832
833 typedef struct _DateSelection DateSelection;
834 struct _DateSelection
835 {
836         GtkWidget *box;
837
838         GtkWidget *spin_d;
839         GtkWidget *spin_m;
840         GtkWidget *spin_y;
841
842         GtkWidget *button;
843
844         GtkWidget *window;
845         GtkWidget *calendar;
846 };
847
848
849 static void date_selection_popup_hide(DateSelection *ds)
850 {
851         if (!ds->window) return;
852
853         if (gtk_widget_has_grab(ds->window))
854                 {
855                 gtk_grab_remove(ds->window);
856                 gdk_keyboard_ungrab(GDK_CURRENT_TIME);
857                 gdk_pointer_ungrab(GDK_CURRENT_TIME);
858                 }
859
860         gtk_widget_hide(ds->window);
861
862         gtk_widget_destroy(ds->window);
863         ds->window = NULL;
864         ds->calendar = NULL;
865
866         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ds->button), FALSE);
867 }
868
869 static gboolean date_selection_popup_release_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
870 {
871         DateSelection *ds = data;
872
873         date_selection_popup_hide(ds);
874         return TRUE;
875 }
876
877 static gboolean date_selection_popup_press_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
878 {
879         DateSelection *ds = data;
880         gint x, y;
881         gint w, h;
882         gint xr, yr;
883         GdkWindow *window;
884
885         xr = (gint)event->x_root;
886         yr = (gint)event->y_root;
887
888         window = gtk_widget_get_window(ds->window);
889         gdk_window_get_origin(window, &x, &y);
890         w = gdk_window_get_width(window);
891         h = gdk_window_get_height(window);
892
893         if (xr < x || yr < y || xr > x + w || yr > y + h)
894                 {
895                 g_signal_connect(G_OBJECT(ds->window), "button_release_event",
896                                  G_CALLBACK(date_selection_popup_release_cb), ds);
897                 return TRUE;
898                 }
899
900         return FALSE;
901 }
902
903 static void date_selection_popup_sync(DateSelection *ds)
904 {
905         guint day, month, year;
906
907         gtk_calendar_get_date(GTK_CALENDAR(ds->calendar), &year, &month, &day);
908         date_selection_set(ds->box, day, month + 1, year);
909 }
910
911 static gboolean date_selection_popup_keypress_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
912 {
913         DateSelection *ds = data;
914
915         switch (event->keyval)
916                 {
917                 case GDK_KEY_Return:
918                 case GDK_KEY_KP_Enter:
919                 case GDK_KEY_Tab:
920                 case GDK_KEY_ISO_Left_Tab:
921                         date_selection_popup_sync(ds);
922                         date_selection_popup_hide(ds);
923                         break;
924                 case GDK_KEY_Escape:
925                         date_selection_popup_hide(ds);
926                         break;
927                 default:
928                         break;
929                 }
930
931         return FALSE;
932 }
933
934 static void date_selection_day_cb(GtkWidget *widget, gpointer data)
935 {
936         DateSelection *ds = data;
937
938         date_selection_popup_sync(ds);
939 }
940
941 static void date_selection_doubleclick_cb(GtkWidget *widget, gpointer data)
942 {
943         DateSelection *ds = data;
944
945         date_selection_popup_hide(ds);
946 }
947
948 static void date_selection_popup(DateSelection *ds)
949 {
950         gint x, y;
951         gint wx, wy;
952         gint day, month, year;
953         GtkAllocation button_allocation;
954         GtkAllocation window_allocation;
955
956         if (ds->window) return;
957
958         ds->window = gtk_window_new(GTK_WINDOW_POPUP);
959         gtk_window_set_resizable(GTK_WINDOW(ds->window), FALSE);
960         g_signal_connect(G_OBJECT(ds->window), "button_press_event",
961                          G_CALLBACK(date_selection_popup_press_cb), ds);
962         g_signal_connect(G_OBJECT(ds->window), "key_press_event",
963                          G_CALLBACK(date_selection_popup_keypress_cb), ds);
964
965         ds->calendar = gtk_calendar_new();
966         gtk_container_add(GTK_CONTAINER(ds->window), ds->calendar);
967         gtk_widget_show(ds->calendar);
968
969         date_selection_get(ds->box, &day, &month, &year);
970         gtk_calendar_select_month(GTK_CALENDAR(ds->calendar), month - 1, year);
971         gtk_calendar_select_day(GTK_CALENDAR(ds->calendar), day);
972
973         g_signal_connect(G_OBJECT(ds->calendar), "day_selected",
974                          G_CALLBACK(date_selection_day_cb), ds);
975         g_signal_connect(G_OBJECT(ds->calendar), "day_selected_double_click",
976                         G_CALLBACK(date_selection_doubleclick_cb), ds);
977
978         gtk_widget_realize(ds->window);
979
980         gdk_window_get_origin(gtk_widget_get_window(ds->button), &wx, &wy);
981
982         gtk_widget_get_allocation(ds->button, &button_allocation);
983         gtk_widget_get_allocation(ds->window, &window_allocation);
984         
985         x = wx + button_allocation.x + button_allocation.width - window_allocation.width;
986         y = wy + button_allocation.y + button_allocation.height;
987
988         if (y + window_allocation.height > gdk_screen_height())
989                 {
990                 y = wy + button_allocation.y - window_allocation.height;
991                 }
992         if (x < 0) x = 0;
993         if (y < 0) y = 0;
994
995         gtk_window_move(GTK_WINDOW(ds->window), x, y);
996         gtk_widget_show(ds->window);
997
998         gtk_widget_grab_focus(ds->calendar);
999         gdk_pointer_grab(gtk_widget_get_window(ds->window), TRUE,
1000                          GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK,
1001                          NULL, NULL, GDK_CURRENT_TIME);
1002         gdk_keyboard_grab(gtk_widget_get_window(ds->window), TRUE, GDK_CURRENT_TIME);
1003         gtk_grab_add(ds->window);
1004
1005         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ds->button), TRUE);
1006 }
1007
1008 static void date_selection_button_cb(GtkWidget *widget, gpointer data)
1009 {
1010         DateSelection *ds = data;
1011
1012         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ds->button)) == (!ds->window))
1013                 {
1014                 date_selection_popup(ds);
1015                 }
1016 }
1017
1018 static void button_size_allocate_cb(GtkWidget *button, GtkAllocation *allocation, gpointer data)
1019 {
1020         GtkWidget *spin = data;
1021         GtkRequisition spin_requisition;
1022         gtk_widget_get_requisition(spin, &spin_requisition);
1023
1024         if (allocation->height > spin_requisition.height)
1025                 {
1026                 GtkAllocation button_allocation;
1027                 GtkAllocation spin_allocation;
1028
1029                 gtk_widget_get_allocation(button, &button_allocation);
1030                 gtk_widget_get_allocation(spin, &spin_allocation);
1031                 button_allocation.height = spin_requisition.height;
1032                 button_allocation.y = spin_allocation.y +
1033                         (spin_allocation.height - spin_requisition.height) / 2;
1034                 gtk_widget_size_allocate(button, &button_allocation);
1035                 }
1036 }
1037
1038 static void spin_increase(GtkWidget *spin, gint value)
1039 {
1040         GtkRequisition req;
1041
1042         gtk_widget_size_request(spin, &req);
1043         gtk_widget_set_size_request(spin, req.width + value, -1);
1044 }
1045
1046 static void date_selection_destroy_cb(GtkWidget *widget, gpointer data)
1047 {
1048         DateSelection *ds = data;
1049
1050         date_selection_popup_hide(ds);
1051
1052         g_free(ds);
1053 }
1054
1055 GtkWidget *date_selection_new(void)
1056 {
1057         DateSelection *ds;
1058         GtkWidget *arrow;
1059
1060         ds = g_new0(DateSelection, 1);
1061
1062         ds->box = gtk_hbox_new(FALSE, 2);
1063         g_signal_connect(G_OBJECT(ds->box), "destroy",
1064                          G_CALLBACK(date_selection_destroy_cb), ds);
1065
1066         /* FIXME: use option menu with text format of month instead of a spin button */
1067         ds->spin_m = pref_spin_new(ds->box, NULL, NULL, 1, 12, 1, 0, 1, NULL, NULL);
1068         ds->spin_d = pref_spin_new(ds->box, NULL, NULL, 1, 31, 1, 0, 1, NULL, NULL);
1069         ds->spin_y = pref_spin_new(ds->box, NULL, NULL, 1900, 9999, 1, 0, 1900, NULL, NULL);
1070         spin_increase(ds->spin_y, 5);
1071
1072         ds->button = gtk_toggle_button_new();
1073         g_signal_connect(G_OBJECT(ds->button), "size_allocate",
1074                          G_CALLBACK(button_size_allocate_cb), ds->spin_y);
1075
1076         arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
1077         gtk_container_add(GTK_CONTAINER(ds->button), arrow);
1078         gtk_widget_show(arrow);
1079
1080         gtk_box_pack_start(GTK_BOX(ds->box), ds->button, FALSE, FALSE, 0);
1081         g_signal_connect(G_OBJECT(ds->button), "clicked",
1082                          G_CALLBACK(date_selection_button_cb), ds);
1083         gtk_widget_show(ds->button);
1084
1085         g_object_set_data(G_OBJECT(ds->box), DATE_SELECION_KEY, ds);
1086
1087         return ds->box;
1088 }
1089
1090 void date_selection_set(GtkWidget *widget, gint day, gint month, gint year)
1091 {
1092         DateSelection *ds;
1093
1094         ds = g_object_get_data(G_OBJECT(widget), DATE_SELECION_KEY);
1095         if (!ds) return;
1096
1097         gtk_spin_button_set_value(GTK_SPIN_BUTTON(ds->spin_d), (gdouble)day);
1098         gtk_spin_button_set_value(GTK_SPIN_BUTTON(ds->spin_m), (gdouble)month);
1099         gtk_spin_button_set_value(GTK_SPIN_BUTTON(ds->spin_y), (gdouble)year);
1100 }
1101
1102
1103 void date_selection_get(GtkWidget *widget, gint *day, gint *month, gint *year)
1104 {
1105         DateSelection *ds;
1106
1107         ds = g_object_get_data(G_OBJECT(widget), DATE_SELECION_KEY);
1108         if (!ds) return;
1109
1110         if (day) *day = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ds->spin_d));
1111         if (month) *month = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ds->spin_m));
1112         if (year) *year = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ds->spin_y));
1113 }
1114
1115 void date_selection_time_set(GtkWidget *widget, time_t t)
1116 {
1117         struct tm *lt;
1118
1119         lt = localtime(&t);
1120         if (!lt) return;
1121
1122         date_selection_set(widget, lt->tm_mday, lt->tm_mon + 1, lt->tm_year + 1900);
1123 }
1124
1125 time_t date_selection_time_get(GtkWidget *widget)
1126 {
1127         struct tm lt;
1128         gint day = 0;
1129         gint month = 0;
1130         gint year = 0;
1131
1132         date_selection_get(widget, &day, &month ,&year);
1133
1134         lt.tm_sec = 0;
1135         lt.tm_min = 0;
1136         lt.tm_hour = 0;
1137         lt.tm_mday = day;
1138         lt.tm_mon = month - 1;
1139         lt.tm_year = year - 1900;
1140         lt.tm_isdst = 0;
1141
1142         return mktime(&lt);
1143 }
1144
1145
1146 /*
1147  *-----------------------------------------------------------------------------
1148  * Sizer, without using a GtkPaned
1149  *-----------------------------------------------------------------------------
1150  */
1151
1152 #define SIZER_DATA_KEY "sizer_data"
1153
1154 typedef struct _SizerData SizerData;
1155 struct _SizerData
1156 {
1157         GtkWidget *sizer;
1158         GtkWidget *parent;
1159         GtkWidget *bounding_widget;
1160         SizerPositionType position;
1161
1162         gint hsize_min;
1163         gint hsize_max;
1164         gint vsize_min;
1165         gint vsize_max;
1166
1167         gboolean in_drag;
1168         gint press_x;
1169         gint press_y;
1170         gint press_width;
1171         gint press_height;
1172
1173         gboolean handle_prelit;
1174 };
1175
1176
1177 static gint sizer_default_handle_size(void)
1178 {
1179         gint handle_size = 5;
1180         GtkWidget *paned;
1181         GtkStyle *style;
1182
1183         paned = gtk_hpaned_new();
1184
1185         style = gtk_rc_get_style(paned);
1186         gtk_widget_set_style(paned, style);
1187         gtk_widget_style_get(paned, "handle_size", &handle_size, NULL);
1188
1189         gtk_widget_destroy(paned);
1190
1191         return handle_size;
1192 }
1193
1194 static gboolean sizer_motion_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1195 {
1196         SizerData *sd = data;
1197         gint x, y;
1198         gint w, h;
1199         GtkAllocation parent_allocation;
1200
1201         if (!sd->in_drag) return FALSE;
1202
1203         x = sd->press_x - bevent->x_root;
1204         y = sd->press_y - bevent->y_root;
1205
1206         w = sd->press_width;
1207         h = sd->press_height;
1208
1209         if (sd->position & SIZER_POS_LEFT)
1210                 {
1211                 w += x;
1212                 }
1213         else if (sd->position & SIZER_POS_RIGHT)
1214                 {
1215                 w -= x;
1216                 }
1217
1218         if (sd->position & SIZER_POS_TOP)
1219                 {
1220                 h += y;
1221                 }
1222         else if (sd->position & SIZER_POS_BOTTOM)
1223                 {
1224                 h -= y;
1225                 }
1226
1227         if (sd->hsize_min >= 0) w = MAX(w, sd->hsize_min);
1228         if (sd->vsize_min >= 0) h = MAX(h, sd->vsize_min);
1229
1230         if (sd->bounding_widget)
1231                 {
1232                 GtkAllocation sizer_allocation;
1233                 GtkAllocation bounding_allocation;
1234                 gtk_widget_get_allocation(sd->sizer, &sizer_allocation);
1235                 gtk_widget_get_allocation(sd->bounding_widget, &bounding_allocation);
1236                 w = CLAMP(w, sizer_allocation.width, bounding_allocation.width);
1237                 h = CLAMP(h, sizer_allocation.height, bounding_allocation.height);
1238                 }
1239         else
1240                 {
1241                 GtkAllocation sizer_allocation;
1242                 gtk_widget_get_allocation(sd->sizer, &sizer_allocation);
1243                 if (w < sizer_allocation.width) w = sizer_allocation.width;
1244                 if (h < sizer_allocation.height) h = sizer_allocation.height;
1245                 }
1246
1247         if (sd->hsize_max >= 0) w = MIN(w, sd->hsize_max);
1248         if (sd->vsize_max >= 0) h = MIN(h, sd->vsize_max);
1249
1250         gtk_widget_get_allocation(sd->parent, &parent_allocation);
1251         if (w == parent_allocation.width) w = -1;
1252         if (h == parent_allocation.height) h = -1;
1253
1254         if (w > 0 || h > 0) gtk_widget_set_size_request(sd->parent, w, h);
1255
1256         return TRUE;
1257 }
1258
1259 static gboolean sizer_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1260 {
1261         SizerData *sd = data;
1262         GtkAllocation parent_allocation;
1263
1264         if (bevent->button != MOUSE_BUTTON_LEFT) return FALSE;
1265
1266         sd->in_drag = TRUE;
1267         sd->press_x = bevent->x_root;
1268         sd->press_y = bevent->y_root;
1269
1270         gtk_widget_get_allocation(sd->parent, &parent_allocation);
1271         sd->press_width = parent_allocation.width;
1272         sd->press_height = parent_allocation.height;
1273
1274         gdk_pointer_grab(gtk_widget_get_window(sd->sizer), FALSE,
1275                          GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
1276                          NULL, NULL, bevent->time);
1277         gtk_grab_add(sd->sizer);
1278
1279         return TRUE;
1280 }
1281
1282 static gboolean sizer_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1283 {
1284         SizerData *sd = data;
1285
1286         if (bevent->button != MOUSE_BUTTON_LEFT) return FALSE;
1287
1288         if (gdk_pointer_is_grabbed() && gtk_widget_has_grab(sd->sizer))
1289                 {
1290                 gtk_grab_remove(sd->sizer);
1291                 gdk_pointer_ungrab(bevent->time);
1292                 }
1293
1294         sd->in_drag = FALSE;
1295
1296         return TRUE;
1297 }
1298
1299 static void sizer_set_prelight(SizerData *sd, gboolean prelit)
1300 {
1301         GtkAllocation sizer_allocation;
1302         gtk_widget_get_allocation(sd->sizer, &sizer_allocation);
1303
1304         sd->handle_prelit = prelit;
1305         gtk_widget_queue_draw_area(sd->sizer, 0, 0,
1306                                    sizer_allocation.width, sizer_allocation.height);
1307 }
1308
1309 static gboolean sizer_enter_cb(GtkWidget *widget, GdkEventCrossing *event, gpointer data)
1310 {
1311         SizerData *sd = data;
1312
1313         sizer_set_prelight(sd, TRUE);
1314         return TRUE;
1315 }
1316
1317 static gboolean sizer_leave_cb(GtkWidget *widget, GdkEventCrossing *event, gpointer data)
1318 {
1319         SizerData *sd = data;
1320
1321         sizer_set_prelight(sd, FALSE);
1322         return TRUE;
1323 }
1324
1325 static gboolean sizer_expose_cb(GtkWidget *widget, GdkEventExpose *event, gpointer data)
1326 {
1327 #if GTK_CHECK_VERSION(3,0,0)
1328         GtkAllocation allocation;
1329
1330         gtk_widget_get_allocation(widget, &allocation);
1331
1332         cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(widget));
1333
1334         gtk_render_handle (gtk_widget_get_style_context (widget),
1335                            cr, allocation.x, allocation.y, allocation.width, allocation.height);
1336         cairo_destroy(cr);
1337 #else
1338         SizerData *sd = data;
1339         GdkRectangle clip;
1340         GtkOrientation orientation;
1341         GtkStateType state;
1342         GtkAllocation allocation;
1343
1344         gtk_widget_get_allocation(widget, &allocation);
1345
1346         if (sd->position & SIZER_POS_LEFT || sd->position & SIZER_POS_RIGHT)
1347                 {
1348                 orientation = GTK_ORIENTATION_VERTICAL;
1349                 }
1350         else
1351                 {
1352                 orientation = GTK_ORIENTATION_HORIZONTAL;
1353                 }
1354
1355         if (sd->handle_prelit)
1356                 {
1357                 state = GTK_STATE_PRELIGHT;
1358                 }
1359         else
1360                 {
1361                 state = gtk_widget_get_state(widget);
1362                 }
1363
1364         gdk_region_get_clipbox(event->region, &clip);
1365
1366         gtk_paint_handle(gtk_widget_get_style(widget), gtk_widget_get_window(widget), state,
1367                          GTK_SHADOW_NONE, &clip, widget, "paned",
1368                          0, 0,
1369                          allocation.width, allocation.height,
1370                          orientation);
1371 #endif
1372
1373         return TRUE;
1374 }
1375
1376 static void sizer_realize_cb(GtkWidget *widget, gpointer data)
1377 {
1378         SizerData *sd = data;
1379         GdkCursorType n;
1380
1381         n = 0;
1382         if (sd->position & SIZER_POS_TOP || sd->position & SIZER_POS_BOTTOM)
1383                 {
1384                 n = GDK_SB_V_DOUBLE_ARROW;
1385                 }
1386         if (sd->position & SIZER_POS_LEFT || sd->position & SIZER_POS_RIGHT)
1387                 {
1388                 n = (n != 0) ? GDK_FLEUR : GDK_SB_H_DOUBLE_ARROW;
1389                 }
1390
1391         if (n != 0 && gtk_widget_get_window(widget))
1392                 {
1393                 GdkCursor *cursor;
1394                 cursor = gdk_cursor_new(n);
1395                 gdk_window_set_cursor(gtk_widget_get_window(widget), cursor);
1396                 gdk_cursor_unref(cursor);
1397                 }
1398 }
1399
1400 static void sizer_destroy_cb(GtkWidget *widget, gpointer data)
1401 {
1402         SizerData *sd = data;
1403
1404         g_free(sd);
1405 }
1406
1407 GtkWidget *sizer_new(GtkWidget *parent, GtkWidget *bounding_widget,
1408                      SizerPositionType position)
1409 {
1410         SizerData *sd;
1411         gint handle_size;
1412
1413         sd = g_new0(SizerData, 1);
1414
1415         sd->sizer = gtk_event_box_new();
1416         sd->parent = parent;
1417         sd->bounding_widget = bounding_widget;
1418         sd->position = position;
1419         sd->hsize_min = -1;
1420         sd->hsize_max = -1;
1421         sd->vsize_min = -1;
1422         sd->vsize_max = -1;
1423
1424         sd->in_drag = FALSE;
1425         sd->handle_prelit = FALSE;
1426
1427         g_signal_connect(G_OBJECT(sd->sizer), "destroy",
1428                          G_CALLBACK(sizer_destroy_cb), sd);
1429
1430         g_signal_connect(G_OBJECT(sd->sizer), "motion_notify_event",
1431                          G_CALLBACK(sizer_motion_cb), sd);
1432         g_signal_connect(G_OBJECT(sd->sizer), "button_press_event",
1433                          G_CALLBACK(sizer_press_cb), sd);
1434         g_signal_connect(G_OBJECT(sd->sizer), "button_release_event",
1435                          G_CALLBACK(sizer_release_cb), sd);
1436
1437         g_signal_connect(G_OBJECT(sd->sizer), "enter_notify_event",
1438                          G_CALLBACK(sizer_enter_cb), sd);
1439         g_signal_connect(G_OBJECT(sd->sizer), "leave_notify_event",
1440                          G_CALLBACK(sizer_leave_cb), sd);
1441
1442         gtk_widget_set_events(sd->sizer, GDK_POINTER_MOTION_MASK |
1443                                          GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
1444                                          GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
1445
1446         g_signal_connect(sd->sizer, "realize",
1447                          G_CALLBACK(sizer_realize_cb), sd);
1448         g_signal_connect(sd->sizer, "expose_event",
1449                          G_CALLBACK(sizer_expose_cb), sd);
1450
1451         handle_size = sizer_default_handle_size();
1452
1453         gtk_widget_set_size_request(sd->sizer, handle_size, handle_size);
1454 #if 0
1455         /* use this if you add a shadow border to the handle */
1456         gtk_widget_set_size_request(sd->sizer, handle_size + sd->sizer->style->xthickness * 2,
1457                                                handle_size + sd->sizer->style->ythickness * 2);
1458 #endif
1459
1460         g_object_set_data(G_OBJECT(sd->sizer), SIZER_DATA_KEY,sd);
1461
1462         return sd->sizer;
1463 }
1464
1465 void sizer_set_limits(GtkWidget *sizer,
1466                       gint hsize_min, gint hsize_max,
1467                       gint vsize_min, gint vsize_max)
1468 {
1469         SizerData *sd;
1470
1471         sd = g_object_get_data(G_OBJECT(sizer), SIZER_DATA_KEY);
1472         if (!sd) return;
1473
1474         sd->hsize_min = hsize_min;
1475         sd->hsize_max = hsize_max;
1476         sd->vsize_min = vsize_min;
1477         sd->vsize_max = vsize_max;
1478 }
1479
1480
1481 /*
1482  *-----------------------------------------------------------------------------
1483  * storing data in a history list with key,data pairs
1484  *-----------------------------------------------------------------------------
1485  */
1486
1487 #define PREF_LIST_MARKER_INT "[INT]:"
1488 #define PREF_LIST_MARKER_DOUBLE "[DOUBLE]:"
1489 #define PREF_LIST_MARKER_STRING "[STRING]:"
1490
1491 static GList *pref_list_find(const gchar *group, const gchar *token)
1492 {
1493         GList *work;
1494         gint l;
1495
1496         l = strlen(token);
1497
1498         work = history_list_get_by_key(group);
1499         while (work)
1500                 {
1501                 const gchar *text = work->data;
1502
1503                 if (strncmp(text, token, l) == 0) return work;
1504
1505                 work = work->next;
1506                 }
1507
1508         return NULL;
1509 }
1510
1511 static gboolean pref_list_get(const gchar *group, const gchar *key, const gchar *marker, const gchar **result)
1512 {
1513         gchar *token;
1514         GList *work;
1515         gboolean ret;
1516
1517         if (!group || !key || !marker)
1518                 {
1519                 *result = NULL;
1520                 return FALSE;
1521                 }
1522
1523         token = g_strconcat(key, marker, NULL);
1524
1525         work = pref_list_find(group, token);
1526         if (work)
1527                 {
1528                 *result = (const gchar *)work->data + strlen(token);
1529                 if (strlen(*result) == 0) *result = NULL;
1530                 ret = TRUE;
1531                 }
1532         else
1533                 {
1534                 *result = NULL;
1535                 ret = FALSE;
1536                 }
1537
1538         g_free(token);
1539
1540         return ret;
1541 }
1542
1543 static void pref_list_set(const gchar *group, const gchar *key, const gchar *marker, const gchar *text)
1544 {
1545         gchar *token;
1546         gchar *path;
1547         GList *work;
1548
1549         if (!group || !key || !marker) return;
1550
1551         token = g_strconcat(key, marker, NULL);
1552         path = g_strconcat(token, text, NULL);
1553
1554         work = pref_list_find(group, token);
1555         if (work)
1556                 {
1557                 gchar *old_path = work->data;
1558
1559                 if (text)
1560                         {
1561                         work->data = path;
1562                         path = NULL;
1563
1564                         g_free(old_path);
1565                         }
1566                 else
1567                         {
1568                         history_list_item_remove(group, old_path);
1569                         }
1570                 }
1571         else if (text)
1572                 {
1573                 history_list_add_to_key(group, path, 0);
1574                 }
1575
1576         g_free(path);
1577         g_free(token);
1578 }
1579
1580 void pref_list_int_set(const gchar *group, const gchar *key, gint value)
1581 {
1582         gchar *text;
1583
1584         text = g_strdup_printf("%d", value);
1585         pref_list_set(group, key, PREF_LIST_MARKER_INT, text);
1586         g_free(text);
1587 }
1588
1589 gboolean pref_list_int_get(const gchar *group, const gchar *key, gint *result)
1590 {
1591         const gchar *text;
1592
1593         if (!group || !key)
1594                 {
1595                 *result = 0;
1596                 return FALSE;
1597                 }
1598
1599         if (pref_list_get(group, key, PREF_LIST_MARKER_INT, &text) && text)
1600                 {
1601                 *result = (gint)strtol(text, NULL, 10);
1602                 return TRUE;
1603                 }
1604
1605         *result = 0;
1606         return FALSE;
1607 }
1608
1609 void pref_list_double_set(const gchar *group, const gchar *key, gdouble value)
1610 {
1611         gchar text[G_ASCII_DTOSTR_BUF_SIZE];
1612
1613         g_ascii_dtostr(text, sizeof(text), value);
1614         pref_list_set(group, key, PREF_LIST_MARKER_DOUBLE, text);
1615 }
1616
1617 gboolean pref_list_double_get(const gchar *group, const gchar *key, gdouble *result)
1618 {
1619         const gchar *text;
1620
1621         if (!group || !key)
1622                 {
1623                 *result = 0;
1624                 return FALSE;
1625                 }
1626
1627         if (pref_list_get(group, key, PREF_LIST_MARKER_DOUBLE, &text) && text)
1628                 {
1629                 *result = g_ascii_strtod(text, NULL);
1630                 return TRUE;
1631                 }
1632
1633         *result = 0;
1634         return FALSE;
1635 }
1636
1637 void pref_list_string_set(const gchar *group, const gchar *key, const gchar *value)
1638 {
1639         pref_list_set(group, key, PREF_LIST_MARKER_STRING, value);
1640 }
1641
1642 gboolean pref_list_string_get(const gchar *group, const gchar *key, const gchar **result)
1643 {
1644         return pref_list_get(group, key, PREF_LIST_MARKER_STRING, result);
1645 }
1646
1647
1648 void pref_color_button_set_cb(GtkWidget *widget, gpointer data)
1649 {
1650         GdkColor *color = data;
1651
1652         gtk_color_button_get_color(GTK_COLOR_BUTTON(widget), color);
1653 }
1654
1655 GtkWidget *pref_color_button_new(GtkWidget *parent_box,
1656                                  const gchar *title, const GdkColor *color,
1657                                  GCallback func, gpointer data)
1658 {
1659         GtkWidget *button;
1660
1661         if (color)
1662                 {
1663                 button = gtk_color_button_new_with_color(color);
1664                 }
1665         else
1666                 {
1667                 button = gtk_color_button_new();
1668                 }
1669
1670         if (func) g_signal_connect(G_OBJECT(button), "color-set", func, data);
1671
1672         if (title)
1673                 {
1674                 GtkWidget *label;
1675                 GtkWidget *hbox;
1676
1677                 gtk_color_button_set_title(GTK_COLOR_BUTTON(button), title);
1678                 label = gtk_label_new(title);
1679
1680                 hbox = gtk_hbox_new(TRUE, 0);
1681                 gtk_box_pack_start(GTK_BOX(parent_box), hbox, TRUE, TRUE, 0);
1682
1683                 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
1684                 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
1685
1686                 gtk_widget_show_all(hbox);
1687                 }
1688         else
1689                 {
1690                 gtk_widget_show(button);
1691                 }
1692
1693         return button;
1694 }
1695
1696 /*
1697  *-----------------------------------------------------------------------------
1698  * text widget
1699  *-----------------------------------------------------------------------------
1700  */
1701
1702 gchar *text_widget_text_pull(GtkWidget *text_widget)
1703 {
1704         if (GTK_IS_TEXT_VIEW(text_widget))
1705                 {
1706                 GtkTextBuffer *buffer;
1707                 GtkTextIter start, end;
1708
1709                 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_widget));
1710                 gtk_text_buffer_get_bounds(buffer, &start, &end);
1711
1712                 return gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1713                 }
1714         else if (GTK_IS_ENTRY(text_widget))
1715                 {
1716                 return g_strdup(gtk_entry_get_text(GTK_ENTRY(text_widget)));
1717                 }
1718         else
1719                 {
1720                 return NULL;
1721                 }
1722
1723 }
1724
1725 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */