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