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