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