f388d581f6b35c2475be1521bc7c727f070dc850
[geeqie.git] / src / ui-misc.cc
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 <cstdlib>
23 #include <cstring>
24
25 #include "main.h"
26 #include "ui-misc.h"
27
28 #include "history-list.h"
29
30 #include <langinfo.h>
31
32 /*
33  *-----------------------------------------------------------------------------
34  * widget and layout utilities
35  *-----------------------------------------------------------------------------
36  */
37
38 GtkWidget *pref_box_new(GtkWidget *parent_box, gboolean fill,
39                         GtkOrientation orientation, gboolean padding)
40 {
41         GtkWidget *box;
42
43         if (orientation == GTK_ORIENTATION_HORIZONTAL)
44                 {
45                 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, padding);
46                 }
47         else
48                 {
49                 box = gtk_box_new(GTK_ORIENTATION_VERTICAL, padding);
50                 }
51
52         gtk_box_pack_start(GTK_BOX(parent_box), box, fill, fill, 0);
53         gtk_widget_show(box);
54
55         return box;
56 }
57
58 GtkWidget *pref_group_new(GtkWidget *parent_box, gboolean fill,
59                           const gchar *text, GtkOrientation orientation)
60 {
61         GtkWidget *box;
62         GtkWidget *vbox;
63         GtkWidget *hbox;
64         GtkWidget *label;
65
66         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
67
68         /* add additional spacing if necessary */
69         if (GTK_IS_VBOX(parent_box))
70                 {
71                 GList *list = gtk_container_get_children(GTK_CONTAINER(parent_box));
72                 if (list)
73                         {
74                         pref_spacer(vbox, PREF_PAD_GROUP - PREF_PAD_GAP);
75                         }
76                 g_list_free(list);
77                 }
78
79         gtk_box_pack_start(GTK_BOX(parent_box), vbox, fill, fill, 0);
80         gtk_widget_show(vbox);
81
82         label = gtk_label_new(text);
83         gtk_label_set_xalign(GTK_LABEL(label), 0.0);
84         gtk_label_set_yalign(GTK_LABEL(label), 0.5);
85         pref_label_bold(label, TRUE, FALSE);
86
87         gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
88         gtk_widget_show(label);
89
90         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PREF_PAD_INDENT);
91         gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
92         gtk_widget_show(hbox);
93
94         /* indent using empty box */
95         pref_spacer(hbox, 0);
96
97         if (orientation == GTK_ORIENTATION_HORIZONTAL)
98                 {
99                 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
100                 }
101         else
102                 {
103                 box = gtk_box_new(GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
104                 }
105         gtk_box_pack_start(GTK_BOX(hbox), box, TRUE, TRUE, 0);
106         gtk_widget_show(box);
107
108         g_object_set_data(G_OBJECT(box), "pref_group", vbox);
109
110         return box;
111 }
112
113 GtkWidget *pref_group_parent(GtkWidget *child)
114 {
115         GtkWidget *parent;
116
117         parent = child;
118         while (parent)
119                 {
120                 GtkWidget *group;
121
122                 group = static_cast<GtkWidget *>(g_object_get_data(G_OBJECT(parent), "pref_group"));
123                 if (group && GTK_IS_WIDGET(group)) return group;
124
125                 parent = gtk_widget_get_parent(parent);
126                 }
127
128         return child;
129 }
130
131 GtkWidget *pref_frame_new(GtkWidget *parent_box, gboolean fill,
132                           const gchar *text,
133                           GtkOrientation orientation, gboolean padding)
134 {
135         GtkWidget *box;
136         GtkWidget *frame = nullptr;
137
138         frame = gtk_frame_new(text);
139         gtk_box_pack_start(GTK_BOX(parent_box), frame, fill, fill, 0);
140         gtk_widget_show(frame);
141
142         if (orientation == GTK_ORIENTATION_HORIZONTAL)
143                 {
144                 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, padding);
145                 }
146         else
147                 {
148                 box = gtk_box_new(GTK_ORIENTATION_VERTICAL, padding);
149                 }
150         gtk_container_add(GTK_CONTAINER(frame), box);
151         gtk_container_set_border_width(GTK_CONTAINER(box), PREF_PAD_BORDER);
152         gtk_widget_show(box);
153
154         return box;
155 }
156
157 GtkWidget *pref_spacer(GtkWidget *parent_box, gboolean padding)
158 {
159         GtkWidget *spacer;
160
161         spacer = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
162         gtk_box_pack_start(GTK_BOX(parent_box), spacer, FALSE, FALSE, padding / 2);
163         gtk_widget_show(spacer);
164
165         return spacer;
166 }
167
168 GtkWidget *pref_line(GtkWidget *parent_box, gboolean padding)
169 {
170         GtkWidget *spacer;
171
172         if (GTK_IS_HBOX(parent_box))
173                 {
174                 spacer = gtk_vseparator_new();
175                 }
176         else
177                 {
178                 spacer = gtk_hseparator_new();
179                 }
180
181         gtk_box_pack_start(GTK_BOX(parent_box), spacer, FALSE, FALSE, padding / 2);
182         gtk_widget_show(spacer);
183
184         return spacer;
185 }
186
187 GtkWidget *pref_label_new(GtkWidget *parent_box, const gchar *text)
188 {
189         GtkWidget *label;
190
191         label = gtk_label_new(text);
192         gtk_box_pack_start(GTK_BOX(parent_box), label, FALSE, FALSE, 0);
193         gtk_widget_show(label);
194
195         return label;
196 }
197
198 GtkWidget *pref_label_new_mnemonic(GtkWidget *parent_box, const gchar *text, GtkWidget *widget)
199 {
200         GtkWidget *label;
201
202         label = gtk_label_new_with_mnemonic(text);
203         gtk_label_set_mnemonic_widget(GTK_LABEL(label), widget);
204         gtk_box_pack_start(GTK_BOX(parent_box), label, FALSE, FALSE, 0);
205         gtk_widget_show(label);
206
207         return label;
208 }
209
210 void pref_label_bold(GtkWidget *label, gboolean bold, gboolean increase_size)
211 {
212         PangoAttrList *pal;
213         PangoAttribute *pa;
214
215         if (!bold && !increase_size) return;
216
217         pal = pango_attr_list_new();
218
219         if (bold)
220                 {
221                 pa = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
222                 pa->start_index = 0;
223                 pa->end_index = G_MAXINT;
224                 pango_attr_list_insert(pal, pa);
225                 }
226
227         if (increase_size)
228                 {
229                 pa = pango_attr_scale_new(PANGO_SCALE_LARGE);
230                 pa->start_index = 0;
231                 pa->end_index = G_MAXINT;
232                 pango_attr_list_insert(pal, pa);
233                 }
234
235         gtk_label_set_attributes(GTK_LABEL(label), pal);
236         pango_attr_list_unref(pal);
237 }
238
239 GtkWidget *pref_button_new(GtkWidget *parent_box, const gchar *stock_id,
240                            const gchar *text, gboolean hide_stock_text,
241                            GCallback func, gpointer data)
242 {
243         GtkWidget *button;
244
245         if (stock_id && !text && !hide_stock_text)
246                 {
247                 button = gtk_button_new_from_stock(stock_id);
248                 }
249         else
250                 {
251                 GtkWidget *image = nullptr;
252                 GtkWidget *label = nullptr;
253
254                 button = gtk_button_new();
255
256                 if (stock_id)
257                         {
258                         image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_BUTTON);
259                         }
260
261                 if (text)
262                         {
263                         label = gtk_label_new_with_mnemonic(text);
264                         gtk_label_set_xalign(GTK_LABEL(label), 0.5);
265                         gtk_label_set_yalign(GTK_LABEL(label), 0.5);
266                         gtk_label_set_mnemonic_widget(GTK_LABEL(label), button);
267                         }
268
269                 if (image && label)
270                         {
271                         GtkWidget *align;
272                         GtkWidget *hbox;
273
274                         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PREF_PAD_BUTTON_ICON_GAP);
275
276                         align = gtk_alignment_new(0.5, 0.5, 0.0, 0.0);
277                         gtk_container_add(GTK_CONTAINER(button), align);
278                         gtk_widget_show(align);
279
280                         gtk_container_add(GTK_CONTAINER(align), hbox);
281                         gtk_widget_show(hbox);
282
283                         gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
284                         gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0);
285                         }
286                 else
287                         {
288                         if (image)
289                                 {
290                                 gtk_container_add(GTK_CONTAINER(button), image);
291                                 }
292                         else if (label)
293                                 {
294                                 gtk_container_add(GTK_CONTAINER(button), label);
295                                 }
296                         }
297
298                 if (image) gtk_widget_show(image);
299                 if (label) gtk_widget_show(label);
300                 }
301
302         if (func) g_signal_connect(G_OBJECT(button), "clicked", func, data);
303
304         if (parent_box)
305                 {
306                 gtk_box_pack_start(GTK_BOX(parent_box), button, FALSE, FALSE, 0);
307                 gtk_widget_show(button);
308                 }
309
310         return button;
311 }
312
313 static GtkWidget *real_pref_checkbox_new(GtkWidget *parent_box, const gchar *text, gboolean mnemonic_text,
314                                          gboolean active, GCallback func, gpointer data)
315 {
316         GtkWidget *button;
317
318         if (mnemonic_text)
319                 {
320                 button = gtk_check_button_new_with_mnemonic(text);
321                 }
322         else
323                 {
324                 button = gtk_check_button_new_with_label(text);
325                 }
326         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), active);
327         if (func) g_signal_connect(G_OBJECT(button), "clicked", func, data);
328
329         gtk_box_pack_start(GTK_BOX(parent_box), button, FALSE, FALSE, 0);
330         gtk_widget_show(button);
331
332         return button;
333 }
334
335 GtkWidget *pref_checkbox_new(GtkWidget *parent_box, const gchar *text, gboolean active,
336                              GCallback func, gpointer data)
337 {
338         return real_pref_checkbox_new(parent_box, text, FALSE, active, func, data);
339 }
340
341 #pragma GCC diagnostic push
342 #pragma GCC diagnostic ignored "-Wunused-function"
343 GtkWidget *pref_checkbox_new_mnemonic_unused(GtkWidget *parent_box, const gchar *text, gboolean active,
344                                       GCallback func, gpointer data)
345 {
346         return real_pref_checkbox_new(parent_box, text, TRUE, active, func, data);
347 }
348 #pragma GCC diagnostic pop
349
350 static void pref_checkbox_int_cb(GtkWidget *widget, gpointer data)
351 {
352         auto result = static_cast<gboolean *>(data);
353
354         *result = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
355 }
356
357 GtkWidget *pref_checkbox_new_int(GtkWidget *parent_box, const gchar *text, gboolean active,
358                                  gboolean *result)
359 {
360         GtkWidget *button;
361
362         button = pref_checkbox_new(parent_box, text, active,
363                                    G_CALLBACK(pref_checkbox_int_cb), result);
364         *result = active;
365
366         return button;
367 }
368
369 static void pref_checkbox_link_sensitivity_cb(GtkWidget *button, gpointer data)
370 {
371         auto widget = static_cast<GtkWidget *>(data);
372
373         gtk_widget_set_sensitive(widget, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)));
374 }
375
376 void pref_checkbox_link_sensitivity(GtkWidget *button, GtkWidget *widget)
377 {
378         g_signal_connect(G_OBJECT(button), "toggled",
379                          G_CALLBACK(pref_checkbox_link_sensitivity_cb), widget);
380
381         pref_checkbox_link_sensitivity_cb(button, widget);
382 }
383
384 #pragma GCC diagnostic push
385 #pragma GCC diagnostic ignored "-Wunused-function"
386 static void pref_checkbox_link_sensitivity_swap_cb_unused(GtkWidget *button, gpointer data)
387 {
388         GtkWidget *widget = static_cast<GtkWidget *>(data);
389
390         gtk_widget_set_sensitive(widget, !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)));
391 }
392
393 void pref_checkbox_link_sensitivity_swap_unused(GtkWidget *button, GtkWidget *widget)
394 {
395         g_signal_connect(G_OBJECT(button), "toggled",
396                          G_CALLBACK(pref_checkbox_link_sensitivity_swap_cb_unused), widget);
397
398         pref_checkbox_link_sensitivity_swap_cb_unused(button, widget);
399 }
400 #pragma GCC diagnostic pop
401
402 static GtkWidget *real_pref_radiobutton_new(GtkWidget *parent_box, GtkWidget *sibling,
403                                             const gchar *text, gboolean mnemonic_text, gboolean active,
404                                             GCallback func, gpointer data)
405 {
406         GtkWidget *button;
407         GSList *group;
408
409         if (sibling)
410                 {
411                 group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(sibling));
412                 }
413         else
414                 {
415                 group = nullptr;
416                 }
417
418         if (mnemonic_text)
419                 {
420                 button = gtk_radio_button_new_with_mnemonic(group, text);
421                 }
422         else
423                 {
424                 button = gtk_radio_button_new_with_label(group, text);
425                 }
426
427         if (active) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), active);
428         if (func) g_signal_connect(G_OBJECT(button), "clicked", func, data);
429
430         gtk_box_pack_start(GTK_BOX(parent_box), button, FALSE, FALSE, 0);
431         gtk_widget_show(button);
432
433         return button;
434 }
435
436 GtkWidget *pref_radiobutton_new(GtkWidget *parent_box, GtkWidget *sibling,
437                                 const gchar *text, gboolean active,
438                                 GCallback func, gpointer data)
439 {
440         return real_pref_radiobutton_new(parent_box, sibling, text, FALSE, active, func, data);
441 }
442
443 #pragma GCC diagnostic push
444 #pragma GCC diagnostic ignored "-Wunused-function"
445 GtkWidget *pref_radiobutton_new_mnemonic_unused(GtkWidget *parent_box, GtkWidget *sibling,
446                                          const gchar *text, gboolean active,
447                                          GCallback func, gpointer data)
448 {
449         return real_pref_radiobutton_new(parent_box, sibling, text, TRUE, active, func, data);
450 }
451
452 #define PREF_RADIO_VALUE_KEY "pref_radio_value"
453
454 static void pref_radiobutton_int_cb_unused(GtkWidget *widget, gpointer data)
455 {
456         gboolean *result = static_cast<gboolean *>(data);
457
458         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
459                 {
460                 *result = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), PREF_RADIO_VALUE_KEY));
461                 }
462 }
463
464 GtkWidget *pref_radiobutton_new_int_unused(GtkWidget *parent_box, GtkWidget *sibling,
465                                     const gchar *text, gboolean active,
466                                     gboolean *result, gboolean value,
467                                     GCallback UNUSED(func), gpointer UNUSED(data))
468 {
469         GtkWidget *button;
470
471         button = pref_radiobutton_new(parent_box, sibling, text, active,
472                                       G_CALLBACK(pref_radiobutton_int_cb_unused), result);
473         g_object_set_data(G_OBJECT(button), PREF_RADIO_VALUE_KEY, GINT_TO_POINTER(value));
474         if (active) *result = value;
475
476         return button;
477 }
478 #pragma GCC diagnostic pop
479
480 static GtkWidget *real_pref_spin_new(GtkWidget *parent_box, const gchar *text, const gchar *suffix,
481                                      gboolean mnemonic_text,
482                                      gdouble min, gdouble max, gdouble step, gint digits,
483                                      gdouble value,
484                                      GCallback func, gpointer data)
485 {
486         GtkWidget *spin;
487         GtkWidget *box;
488         GtkWidget *label;
489
490         box = pref_box_new(parent_box, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
491
492         spin = gtk_spin_button_new_with_range(min, max, step);
493         gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), digits);
494         gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), value);
495
496         if (func)
497                 {
498                 g_signal_connect(G_OBJECT(spin), "value_changed", G_CALLBACK(func), data);
499                 }
500
501         if (text)
502                 {
503                 if (mnemonic_text)
504                         {
505                         label = pref_label_new_mnemonic(box, text, spin);
506                         }
507                 else
508                         {
509                         label = pref_label_new(box, text);
510                         }
511                 pref_link_sensitivity(label, spin);
512                 }
513
514         gtk_box_pack_start(GTK_BOX(box), spin, FALSE, FALSE, 0);
515         gtk_widget_show(spin);
516
517         /* perhaps this should only be PREF_PAD_GAP distance from spinbutton ? */
518         if (suffix)
519                 {
520                 label =  pref_label_new(box, suffix);
521                 pref_link_sensitivity(label, spin);
522                 }
523
524         return spin;
525 }
526
527 GtkWidget *pref_spin_new(GtkWidget *parent_box, const gchar *text, const gchar *suffix,
528                          gdouble min, gdouble max, gdouble step, gint digits,
529                          gdouble value,
530                          GCallback func, gpointer data)
531 {
532         return real_pref_spin_new(parent_box, text, suffix, FALSE,
533                                   min, max, step, digits, value, func, data);
534 }
535
536 #pragma GCC diagnostic push
537 #pragma GCC diagnostic ignored "-Wunused-function"
538 GtkWidget *pref_spin_new_mnemonic_unused(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 #pragma GCC diagnostic pop
547
548 static void pref_spin_int_cb(GtkWidget *widget, gpointer data)
549 {
550         auto var = static_cast<gint *>(data);
551         *var = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget));
552 }
553
554 GtkWidget *pref_spin_new_int(GtkWidget *parent_box, const gchar *text, const gchar *suffix,
555                              gint min, gint max, gint step,
556                              gint value, gint *value_var)
557 {
558         *value_var = value;
559         return pref_spin_new(parent_box, text, suffix,
560                              static_cast<gdouble>(min), static_cast<gdouble>(max), static_cast<gdouble>(step), 0,
561                              value,
562                              G_CALLBACK(pref_spin_int_cb), value_var);
563 }
564
565 static void pref_link_sensitivity_cb(GtkWidget *watch, GtkStateType UNUSED(prev_state), gpointer data)
566 {
567         auto widget = static_cast<GtkWidget *>(data);
568
569         gtk_widget_set_sensitive(widget, gtk_widget_is_sensitive(watch));
570 }
571
572 void pref_link_sensitivity(GtkWidget *widget, GtkWidget *watch)
573 {
574         g_signal_connect(G_OBJECT(watch), "state_changed",
575                          G_CALLBACK(pref_link_sensitivity_cb), widget);
576 }
577
578 void pref_signal_block_data(GtkWidget *widget, gpointer data)
579 {
580         g_signal_handlers_block_matched(widget, G_SIGNAL_MATCH_DATA,
581                                         0, 0, nullptr, nullptr, data);
582 }
583
584 void pref_signal_unblock_data(GtkWidget *widget, gpointer data)
585 {
586         g_signal_handlers_unblock_matched(widget, G_SIGNAL_MATCH_DATA,
587                                           0, 0, nullptr, nullptr, data);
588 }
589
590 GtkWidget *pref_table_new(GtkWidget *parent_box, gint columns, gint rows,
591                           gboolean homogeneous, gboolean fill)
592 {
593         GtkWidget *table;
594
595         table = gtk_table_new(rows, columns, homogeneous);
596         gtk_table_set_row_spacings(GTK_TABLE(table), PREF_PAD_GAP);
597         gtk_table_set_col_spacings(GTK_TABLE(table), PREF_PAD_SPACE);
598
599         if (parent_box)
600                 {
601                 gtk_box_pack_start(GTK_BOX(parent_box), table, fill, fill, 0);
602                 gtk_widget_show(table);
603                 }
604
605         return table;
606 }
607
608 GtkWidget *pref_table_box(GtkWidget *table, gint column, gint row,
609                           GtkOrientation orientation, const gchar *text)
610 {
611         GtkWidget *box;
612         GtkWidget *shell;
613
614         if (text)
615                 {
616                 shell = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
617                 box = pref_group_new(shell, TRUE, text, orientation);
618                 }
619         else
620                 {
621                 if (orientation == GTK_ORIENTATION_HORIZONTAL)
622                         {
623                         box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
624                         }
625                 else
626                         {
627                         box = gtk_box_new(GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
628                         }
629                 shell = box;
630                 }
631
632         gtk_table_attach(GTK_TABLE(table), shell, column, column + 1, row, row + 1,
633                          static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL), static_cast<GtkAttachOptions>(0), 0, 0);
634
635         gtk_widget_show(shell);
636
637         return box;
638 }
639
640 GtkWidget *pref_table_label(GtkWidget *table, gint column, gint row,
641                             const gchar *text, gfloat alignment)
642 {
643         GtkWidget *label;
644         GtkWidget *align;
645
646         align = gtk_alignment_new(alignment, 0.50, 0.0, 0.0);
647         gtk_table_attach(GTK_TABLE(table), align, column, column + 1, row, row + 1,
648                          GTK_FILL, static_cast<GtkAttachOptions>(0), 0, 0);
649         gtk_widget_show(align);
650         label = gtk_label_new(text);
651         gtk_container_add(GTK_CONTAINER(align), label);
652         gtk_widget_show(label);
653
654         return label;
655 }
656
657 GtkWidget *pref_table_button(GtkWidget *table, gint column, gint row,
658                              const gchar *stock_id, const gchar *text, gboolean hide_stock_text,
659                              GCallback func, gpointer data)
660 {
661         GtkWidget *button;
662
663         button = pref_button_new(nullptr, stock_id, text, hide_stock_text, func, data);
664         gtk_table_attach(GTK_TABLE(table), button, column, column + 1, row, row + 1,
665                          GTK_FILL, static_cast<GtkAttachOptions>(0), 0, 0);
666         gtk_widget_show(button);
667
668         return button;
669 }
670
671 GtkWidget *pref_table_spin(GtkWidget *table, gint column, gint row,
672                            const gchar *text, const gchar *suffix,
673                            gdouble min, gdouble max, gdouble step, gint digits,
674                            gdouble value,
675                            GCallback func, gpointer data)
676 {
677         GtkWidget *spin;
678         GtkWidget *box;
679         GtkWidget *label;
680
681         spin = gtk_spin_button_new_with_range(min, max, step);
682         gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), digits);
683         gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), value);
684         if (func)
685                 {
686                 g_signal_connect(G_OBJECT(spin), "value_changed", G_CALLBACK(func), data);
687                 }
688
689         if (text)
690                 {
691                 label = pref_table_label(table, column, row, text, 1.0);
692                 pref_link_sensitivity(label, spin);
693                 column++;
694                 }
695
696         if (suffix)
697                 {
698                 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
699                 gtk_box_pack_start(GTK_BOX(box), spin, FALSE, FALSE, 0);
700                 gtk_widget_show(spin);
701
702                 label = pref_label_new(box, suffix);
703                 pref_link_sensitivity(label, spin);
704                 }
705         else
706                 {
707                 box = spin;
708                 }
709
710         gtk_table_attach(GTK_TABLE(table), box, column, column + 1, row, row + 1,
711                          static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL), static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL), 0, 0);
712         gtk_widget_show(box);
713
714         return spin;
715 }
716
717 GtkWidget *pref_table_spin_new_int(GtkWidget *table, gint column, gint row,
718                                    const gchar *text, const gchar *suffix,
719                                    gint min, gint max, gint step,
720                                    gint value, gint *value_var)
721 {
722         *value_var = value;
723         return pref_table_spin(table, column, row,
724                                text, suffix,
725                                static_cast<gdouble>(min), static_cast<gdouble>(max), static_cast<gdouble>(step), 0,
726                                value,
727                                G_CALLBACK(pref_spin_int_cb), value_var);
728 }
729
730
731 GtkWidget *pref_toolbar_new(GtkWidget *parent_box, GtkToolbarStyle style)
732 {
733         GtkWidget *tbar;
734
735         tbar = gtk_toolbar_new();
736         gtk_toolbar_set_style(GTK_TOOLBAR(tbar), style);
737
738         if (parent_box)
739                 {
740                 gtk_box_pack_start(GTK_BOX(parent_box), tbar, FALSE, FALSE, 0);
741                 gtk_widget_show(tbar);
742                 }
743         return tbar;
744 }
745
746 GtkWidget *pref_toolbar_button(GtkWidget *toolbar,
747                                const gchar *stock_id, const gchar *label, gboolean toggle,
748                                const gchar *description,
749                                GCallback func, gpointer data)
750 {
751         GtkWidget *item;
752
753         if (toggle)
754                 {
755                 if (stock_id)
756                         {
757                         item = GTK_WIDGET(gtk_toggle_tool_button_new_from_stock(stock_id));
758                         }
759                 else
760                         {
761                         item = GTK_WIDGET(gtk_toggle_tool_button_new());
762                         }
763                 }
764         else
765                 {
766                 if (stock_id)
767                         {
768                         item = GTK_WIDGET(gtk_tool_button_new_from_stock(stock_id));
769                         }
770                 else
771                         {
772                         item = GTK_WIDGET(gtk_tool_button_new(nullptr, nullptr));
773                         }
774                 }
775         gtk_tool_button_set_use_underline(GTK_TOOL_BUTTON(item), TRUE);
776
777         if (label) gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), label);
778
779         if (func) g_signal_connect(item, "clicked", func, data);
780         gtk_container_add(GTK_CONTAINER(toolbar), item);
781         gtk_widget_show(item);
782
783         if (description)
784                 {
785                 gtk_widget_set_tooltip_text(item, description);
786                 }
787
788         return item;
789 }
790
791 #pragma GCC diagnostic push
792 #pragma GCC diagnostic ignored "-Wunused-function"
793 void pref_toolbar_button_set_icon_unused(GtkWidget *button, GtkWidget *widget, const gchar *stock_id)
794 {
795         if (widget)
796                 {
797                 gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(button), widget);
798                 }
799         else if (stock_id)
800                 {
801                 gtk_tool_button_set_stock_id(GTK_TOOL_BUTTON(button), stock_id);
802                 }
803 }
804
805 GtkWidget *pref_toolbar_spacer_unused(GtkWidget *toolbar)
806 {
807         GtkWidget *item;
808
809         item = GTK_WIDGET(gtk_separator_tool_item_new());
810         gtk_container_add(GTK_CONTAINER(toolbar), item);
811         gtk_widget_show(item);
812
813         return item;
814 }
815 #pragma GCC diagnostic pop
816
817
818 /*
819  *-----------------------------------------------------------------------------
820  * date selection entry
821  *-----------------------------------------------------------------------------
822  */
823
824 #define DATE_SELECION_KEY "date_selection_data"
825
826
827 struct DateSelection
828 {
829         GtkWidget *box;
830
831         GtkWidget *spin_d;
832         GtkWidget *spin_m;
833         GtkWidget *spin_y;
834
835         GtkWidget *button;
836
837         GtkWidget *window;
838         GtkWidget *calendar;
839 };
840
841
842 static void date_selection_popup_hide(DateSelection *ds)
843 {
844         if (!ds->window) return;
845
846         if (gtk_widget_has_grab(ds->window))
847                 {
848                 gtk_grab_remove(ds->window);
849                 gdk_keyboard_ungrab(GDK_CURRENT_TIME);
850                 gdk_pointer_ungrab(GDK_CURRENT_TIME);
851                 }
852
853         gtk_widget_hide(ds->window);
854
855         gtk_widget_destroy(ds->window);
856         ds->window = nullptr;
857         ds->calendar = nullptr;
858
859         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ds->button), FALSE);
860 }
861
862 static gboolean date_selection_popup_release_cb(GtkWidget *UNUSED(widget), GdkEventButton *UNUSED(event), gpointer data)
863 {
864         auto ds = static_cast<DateSelection *>(data);
865
866         date_selection_popup_hide(ds);
867         return TRUE;
868 }
869
870 static gboolean date_selection_popup_press_cb(GtkWidget *UNUSED(widget), GdkEventButton *event, gpointer data)
871 {
872         auto ds = static_cast<DateSelection *>(data);
873         gint x, y;
874         gint w, h;
875         gint xr, yr;
876         GdkWindow *window;
877
878         xr = static_cast<gint>(event->x_root);
879         yr = static_cast<gint>(event->y_root);
880
881         window = gtk_widget_get_window(ds->window);
882         gdk_window_get_origin(window, &x, &y);
883         w = gdk_window_get_width(window);
884         h = gdk_window_get_height(window);
885
886         if (xr < x || yr < y || xr > x + w || yr > y + h)
887                 {
888                 g_signal_connect(G_OBJECT(ds->window), "button_release_event",
889                                  G_CALLBACK(date_selection_popup_release_cb), ds);
890                 return TRUE;
891                 }
892
893         return FALSE;
894 }
895
896 static void date_selection_popup_sync(DateSelection *ds)
897 {
898         guint day, month, year;
899
900         gtk_calendar_get_date(GTK_CALENDAR(ds->calendar), &year, &month, &day);
901         date_selection_set(ds->box, day, month + 1, year);
902 }
903
904 static gboolean date_selection_popup_keypress_cb(GtkWidget *UNUSED(widget), GdkEventKey *event, gpointer data)
905 {
906         auto ds = static_cast<DateSelection *>(data);
907
908         switch (event->keyval)
909                 {
910                 case GDK_KEY_Return:
911                 case GDK_KEY_KP_Enter:
912                 case GDK_KEY_Tab:
913                 case GDK_KEY_ISO_Left_Tab:
914                         date_selection_popup_sync(ds);
915                         date_selection_popup_hide(ds);
916                         break;
917                 case GDK_KEY_Escape:
918                         date_selection_popup_hide(ds);
919                         break;
920                 default:
921                         break;
922                 }
923
924         return FALSE;
925 }
926
927 static void date_selection_day_cb(GtkWidget *UNUSED(widget), gpointer data)
928 {
929         auto ds = static_cast<DateSelection *>(data);
930
931         date_selection_popup_sync(ds);
932 }
933
934 static void date_selection_doubleclick_cb(GtkWidget *UNUSED(widget), gpointer data)
935 {
936         auto ds = static_cast<DateSelection *>(data);
937
938         date_selection_popup_hide(ds);
939 }
940
941 static void date_selection_popup(DateSelection *ds)
942 {
943         gint x, y;
944         gint wx, wy;
945         gint day, month, year;
946         GtkAllocation button_allocation;
947         GtkAllocation window_allocation;
948
949         if (ds->window) return;
950
951         ds->window = gtk_window_new(GTK_WINDOW_POPUP);
952         gtk_window_set_resizable(GTK_WINDOW(ds->window), FALSE);
953         g_signal_connect(G_OBJECT(ds->window), "button_press_event",
954                          G_CALLBACK(date_selection_popup_press_cb), ds);
955         g_signal_connect(G_OBJECT(ds->window), "key_press_event",
956                          G_CALLBACK(date_selection_popup_keypress_cb), ds);
957
958         ds->calendar = gtk_calendar_new();
959         gtk_container_add(GTK_CONTAINER(ds->window), ds->calendar);
960         gtk_widget_show(ds->calendar);
961
962         date_selection_get(ds->box, &day, &month, &year);
963         gtk_calendar_select_month(GTK_CALENDAR(ds->calendar), month - 1, year);
964         gtk_calendar_select_day(GTK_CALENDAR(ds->calendar), day);
965
966         g_signal_connect(G_OBJECT(ds->calendar), "day_selected",
967                          G_CALLBACK(date_selection_day_cb), ds);
968         g_signal_connect(G_OBJECT(ds->calendar), "day_selected_double_click",
969                         G_CALLBACK(date_selection_doubleclick_cb), ds);
970
971         gtk_widget_realize(ds->window);
972
973         gdk_window_get_origin(gtk_widget_get_window(ds->button), &wx, &wy);
974
975         gtk_widget_get_allocation(ds->button, &button_allocation);
976         gtk_widget_get_allocation(ds->window, &window_allocation);
977
978         x = wx + button_allocation.x + button_allocation.width - window_allocation.width;
979         y = wy + button_allocation.y + button_allocation.height;
980
981         if (y + window_allocation.height > gdk_screen_height())
982                 {
983                 y = wy + button_allocation.y - window_allocation.height;
984                 }
985         if (x < 0) x = 0;
986         if (y < 0) y = 0;
987
988         gtk_window_move(GTK_WINDOW(ds->window), x, y);
989         gtk_widget_show(ds->window);
990
991         gtk_widget_grab_focus(ds->calendar);
992         gdk_pointer_grab(gtk_widget_get_window(ds->window), TRUE,
993                          static_cast<GdkEventMask>(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK),
994                          nullptr, nullptr, GDK_CURRENT_TIME);
995         gdk_keyboard_grab(gtk_widget_get_window(ds->window), TRUE, GDK_CURRENT_TIME);
996         gtk_grab_add(ds->window);
997
998         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ds->button), TRUE);
999 }
1000
1001 static void date_selection_button_cb(GtkWidget *UNUSED(widget), gpointer data)
1002 {
1003         auto ds = static_cast<DateSelection *>(data);
1004
1005         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ds->button)) == (!ds->window))
1006                 {
1007                 date_selection_popup(ds);
1008                 }
1009 }
1010
1011 static void button_size_allocate_cb(GtkWidget *button, GtkAllocation *allocation, gpointer data)
1012 {
1013         auto spin = static_cast<GtkWidget *>(data);
1014         GtkRequisition spin_requisition;
1015         gtk_widget_get_requisition(spin, &spin_requisition);
1016
1017         if (allocation->height > spin_requisition.height)
1018                 {
1019                 GtkAllocation button_allocation;
1020                 GtkAllocation spin_allocation;
1021
1022                 gtk_widget_get_allocation(button, &button_allocation);
1023                 gtk_widget_get_allocation(spin, &spin_allocation);
1024                 button_allocation.height = spin_requisition.height;
1025                 button_allocation.y = spin_allocation.y +
1026                         (spin_allocation.height - spin_requisition.height) / 2;
1027                 gtk_widget_size_allocate(button, &button_allocation);
1028                 }
1029 }
1030
1031 static void spin_increase(GtkWidget *spin, gint value)
1032 {
1033         GtkRequisition req;
1034
1035         gtk_widget_size_request(spin, &req);
1036         gtk_widget_set_size_request(spin, req.width + value, -1);
1037 }
1038
1039 static void date_selection_destroy_cb(GtkWidget *UNUSED(widget), gpointer data)
1040 {
1041         auto ds = static_cast<DateSelection *>(data);
1042
1043         date_selection_popup_hide(ds);
1044
1045         g_free(ds);
1046 }
1047
1048 GtkWidget *date_selection_new()
1049 {
1050         DateSelection *ds;
1051         GtkWidget *arrow;
1052
1053         ds = g_new0(DateSelection, 1);
1054         gchar *date_format;
1055         gint i;
1056
1057         ds->box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
1058         g_signal_connect(G_OBJECT(ds->box), "destroy",
1059                          G_CALLBACK(date_selection_destroy_cb), ds);
1060
1061         date_format = nl_langinfo(D_FMT);
1062
1063         if (strlen(date_format) == 8)
1064                 {
1065                 for (i=1; i<8; i=i+3)
1066                         {
1067                         switch (date_format[i])
1068                                 {
1069                                 case 'd':
1070                                         ds->spin_d = pref_spin_new(ds->box, nullptr, nullptr, 1, 31, 1, 0, 1, nullptr, nullptr);
1071                                         break;
1072                                 case 'm':
1073                                         ds->spin_m = pref_spin_new(ds->box, nullptr, nullptr, 1, 12, 1, 0, 1, nullptr, nullptr);
1074                                         break;
1075                                 case 'y': case 'Y':
1076                                         ds->spin_y = pref_spin_new(ds->box, nullptr, nullptr, 1900, 9999, 1, 0, 1900, nullptr, nullptr);
1077                                         break;
1078                                 default:
1079                                         log_printf("Warning: Date locale %s is unknown", date_format);
1080                                         break;
1081                                 }
1082                         }
1083                 }
1084         else
1085                 {
1086                 ds->spin_m = pref_spin_new(ds->box, nullptr, nullptr, 1, 12, 1, 0, 1, nullptr, nullptr);
1087                 ds->spin_d = pref_spin_new(ds->box, nullptr, nullptr, 1, 31, 1, 0, 1, nullptr, nullptr);
1088                 ds->spin_y = pref_spin_new(ds->box, nullptr, nullptr, 1900, 9999, 1, 0, 1900, nullptr, nullptr);
1089                 }
1090
1091         spin_increase(ds->spin_y, 5);
1092
1093         ds->button = gtk_toggle_button_new();
1094         g_signal_connect(G_OBJECT(ds->button), "size_allocate",
1095                          G_CALLBACK(button_size_allocate_cb), ds->spin_y);
1096
1097         arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
1098         gtk_container_add(GTK_CONTAINER(ds->button), arrow);
1099         gtk_widget_show(arrow);
1100
1101         gtk_box_pack_start(GTK_BOX(ds->box), ds->button, FALSE, FALSE, 0);
1102         g_signal_connect(G_OBJECT(ds->button), "clicked",
1103                          G_CALLBACK(date_selection_button_cb), ds);
1104         gtk_widget_show(ds->button);
1105
1106         g_object_set_data(G_OBJECT(ds->box), DATE_SELECION_KEY, ds);
1107
1108         return ds->box;
1109 }
1110
1111 void date_selection_set(GtkWidget *widget, gint day, gint month, gint year)
1112 {
1113         DateSelection *ds;
1114
1115         ds = static_cast<DateSelection *>(g_object_get_data(G_OBJECT(widget), DATE_SELECION_KEY));
1116         if (!ds) return;
1117
1118         gtk_spin_button_set_value(GTK_SPIN_BUTTON(ds->spin_d), static_cast<gdouble>(day));
1119         gtk_spin_button_set_value(GTK_SPIN_BUTTON(ds->spin_m), static_cast<gdouble>(month));
1120         gtk_spin_button_set_value(GTK_SPIN_BUTTON(ds->spin_y), static_cast<gdouble>(year));
1121 }
1122
1123
1124 void date_selection_get(GtkWidget *widget, gint *day, gint *month, gint *year)
1125 {
1126         DateSelection *ds;
1127
1128         ds = static_cast<DateSelection *>(g_object_get_data(G_OBJECT(widget), DATE_SELECION_KEY));
1129         if (!ds) return;
1130
1131         if (day) *day = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ds->spin_d));
1132         if (month) *month = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ds->spin_m));
1133         if (year) *year = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ds->spin_y));
1134 }
1135
1136 void date_selection_time_set(GtkWidget *widget, time_t t)
1137 {
1138         struct tm *lt;
1139
1140         lt = localtime(&t);
1141         if (!lt) return;
1142
1143         date_selection_set(widget, lt->tm_mday, lt->tm_mon + 1, lt->tm_year + 1900);
1144 }
1145
1146 #pragma GCC diagnostic push
1147 #pragma GCC diagnostic ignored "-Wunused-function"
1148 time_t date_selection_time_get_unused(GtkWidget *widget)
1149 {
1150         struct tm lt;
1151         gint day = 0;
1152         gint month = 0;
1153         gint year = 0;
1154
1155         date_selection_get(widget, &day, &month ,&year);
1156
1157         lt.tm_sec = 0;
1158         lt.tm_min = 0;
1159         lt.tm_hour = 0;
1160         lt.tm_mday = day;
1161         lt.tm_mon = month - 1;
1162         lt.tm_year = year - 1900;
1163         lt.tm_isdst = 0;
1164
1165         return mktime(&lt);
1166 }
1167 #pragma GCC diagnostic pop
1168
1169 /*
1170  *-----------------------------------------------------------------------------
1171  * storing data in a history list with key,data pairs
1172  *-----------------------------------------------------------------------------
1173  */
1174
1175 #define PREF_LIST_MARKER_INT "[INT]:"
1176 #define PREF_LIST_MARKER_DOUBLE "[DOUBLE]:"
1177 #define PREF_LIST_MARKER_STRING "[STRING]:"
1178
1179 static GList *pref_list_find(const gchar *group, const gchar *token)
1180 {
1181         GList *work;
1182         gint l;
1183
1184         l = strlen(token);
1185
1186         work = history_list_get_by_key(group);
1187         while (work)
1188                 {
1189                 auto text = static_cast<const gchar *>(work->data);
1190
1191                 if (strncmp(text, token, l) == 0) return work;
1192
1193                 work = work->next;
1194                 }
1195
1196         return nullptr;
1197 }
1198
1199 static gboolean pref_list_get(const gchar *group, const gchar *key, const gchar *marker, const gchar **result)
1200 {
1201         gchar *token;
1202         GList *work;
1203         gboolean ret;
1204
1205         if (!group || !key || !marker)
1206                 {
1207                 *result = nullptr;
1208                 return FALSE;
1209                 }
1210
1211         token = g_strconcat(key, marker, NULL);
1212
1213         work = pref_list_find(group, token);
1214         if (work)
1215                 {
1216                 *result = static_cast<const gchar *>(work->data) + strlen(token);
1217                 if (strlen(*result) == 0) *result = nullptr;
1218                 ret = TRUE;
1219                 }
1220         else
1221                 {
1222                 *result = nullptr;
1223                 ret = FALSE;
1224                 }
1225
1226         g_free(token);
1227
1228         return ret;
1229 }
1230
1231 static void pref_list_set(const gchar *group, const gchar *key, const gchar *marker, const gchar *text)
1232 {
1233         gchar *token;
1234         gchar *path;
1235         GList *work;
1236
1237         if (!group || !key || !marker) return;
1238
1239         token = g_strconcat(key, marker, NULL);
1240         path = g_strconcat(token, text, NULL);
1241
1242         work = pref_list_find(group, token);
1243         if (work)
1244                 {
1245                 auto old_path = static_cast<gchar *>(work->data);
1246
1247                 if (text)
1248                         {
1249                         work->data = path;
1250                         path = nullptr;
1251
1252                         g_free(old_path);
1253                         }
1254                 else
1255                         {
1256                         history_list_item_remove(group, old_path);
1257                         }
1258                 }
1259         else if (text)
1260                 {
1261                 history_list_add_to_key(group, path, 0);
1262                 }
1263
1264         g_free(path);
1265         g_free(token);
1266 }
1267
1268 void pref_list_int_set(const gchar *group, const gchar *key, gint value)
1269 {
1270         gchar *text;
1271
1272         text = g_strdup_printf("%d", value);
1273         pref_list_set(group, key, PREF_LIST_MARKER_INT, text);
1274         g_free(text);
1275 }
1276
1277 gboolean pref_list_int_get(const gchar *group, const gchar *key, gint *result)
1278 {
1279         const gchar *text;
1280
1281         if (!group || !key)
1282                 {
1283                 *result = 0;
1284                 return FALSE;
1285                 }
1286
1287         if (pref_list_get(group, key, PREF_LIST_MARKER_INT, &text) && text)
1288                 {
1289                 *result = static_cast<gint>(strtol(text, nullptr, 10));
1290                 return TRUE;
1291                 }
1292
1293         *result = 0;
1294         return FALSE;
1295 }
1296
1297 #pragma GCC diagnostic push
1298 #pragma GCC diagnostic ignored "-Wunused-function"
1299 void pref_list_double_set_unused(const gchar *group, const gchar *key, gdouble value)
1300 {
1301         gchar text[G_ASCII_DTOSTR_BUF_SIZE];
1302
1303         g_ascii_dtostr(text, sizeof(text), value);
1304         pref_list_set(group, key, PREF_LIST_MARKER_DOUBLE, text);
1305 }
1306
1307 gboolean pref_list_double_get_unused(const gchar *group, const gchar *key, gdouble *result)
1308 {
1309         const gchar *text;
1310
1311         if (!group || !key)
1312                 {
1313                 *result = 0;
1314                 return FALSE;
1315                 }
1316
1317         if (pref_list_get(group, key, PREF_LIST_MARKER_DOUBLE, &text) && text)
1318                 {
1319                 *result = g_ascii_strtod(text, NULL);
1320                 return TRUE;
1321                 }
1322
1323         *result = 0;
1324         return FALSE;
1325 }
1326
1327 void pref_list_string_set_unused(const gchar *group, const gchar *key, const gchar *value)
1328 {
1329         pref_list_set(group, key, PREF_LIST_MARKER_STRING, value);
1330 }
1331
1332 gboolean pref_list_string_get_unused(const gchar *group, const gchar *key, const gchar **result)
1333 {
1334         return pref_list_get(group, key, PREF_LIST_MARKER_STRING, result);
1335 }
1336 #pragma GCC diagnostic pop
1337
1338 void pref_color_button_set_cb(GtkWidget *widget, gpointer data)
1339 {
1340         auto color = static_cast<GdkColor *>(data);
1341
1342         gtk_color_button_get_color(GTK_COLOR_BUTTON(widget), color);
1343 }
1344
1345 GtkWidget *pref_color_button_new(GtkWidget *parent_box,
1346                                  const gchar *title, const GdkColor *color,
1347                                  GCallback func, gpointer data)
1348 {
1349         GtkWidget *button;
1350
1351         if (color)
1352                 {
1353                 button = gtk_color_button_new_with_color(color);
1354                 }
1355         else
1356                 {
1357                 button = gtk_color_button_new();
1358                 }
1359
1360         if (func) g_signal_connect(G_OBJECT(button), "color-set", func, data);
1361
1362         if (title)
1363                 {
1364                 GtkWidget *label;
1365                 GtkWidget *hbox;
1366
1367                 gtk_color_button_set_title(GTK_COLOR_BUTTON(button), title);
1368                 label = gtk_label_new(title);
1369
1370                 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
1371                 gtk_box_pack_start(GTK_BOX(parent_box), hbox, TRUE, TRUE, 0);
1372
1373                 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
1374                 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
1375
1376                 gtk_widget_show_all(hbox);
1377                 }
1378         else
1379                 {
1380                 gtk_widget_show(button);
1381                 }
1382
1383         return button;
1384 }
1385
1386 /*
1387  *-----------------------------------------------------------------------------
1388  * text widget
1389  *-----------------------------------------------------------------------------
1390  */
1391
1392 gchar *text_widget_text_pull(GtkWidget *text_widget)
1393 {
1394         if (GTK_IS_TEXT_VIEW(text_widget))
1395                 {
1396                 GtkTextBuffer *buffer;
1397                 GtkTextIter start, end;
1398
1399                 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_widget));
1400                 gtk_text_buffer_get_bounds(buffer, &start, &end);
1401
1402                 return gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1403                 }
1404         else if (GTK_IS_ENTRY(text_widget))
1405                 {
1406                 return g_strdup(gtk_entry_get_text(GTK_ENTRY(text_widget)));
1407                 }
1408         else
1409                 {
1410                 return nullptr;
1411                 }
1412
1413 }
1414
1415 gchar *text_widget_text_pull_selected(GtkWidget *text_widget)
1416 {
1417         if (GTK_IS_TEXT_VIEW(text_widget))
1418                 {
1419                 GtkTextBuffer *buffer;
1420                 GtkTextIter start, end;
1421
1422                 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_widget));
1423                 gtk_text_buffer_get_bounds(buffer, &start, &end);
1424
1425                 if (gtk_text_buffer_get_selection_bounds(buffer, &start, &end))
1426                         {
1427                         gtk_text_iter_set_line_offset(&start, 0);
1428                         gtk_text_iter_forward_to_line_end(&end);
1429                         }
1430
1431                 return gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1432                 }
1433         else if (GTK_IS_ENTRY(text_widget))
1434                 {
1435                 return g_strdup(gtk_entry_get_text(GTK_ENTRY(text_widget)));
1436                 }
1437         else
1438                 {
1439                 return nullptr;
1440                 }
1441 }
1442
1443 gboolean defined_mouse_buttons(GtkWidget *UNUSED(widget), GdkEventButton *event, gpointer data)
1444 {
1445         auto lw = static_cast<LayoutWindow *>(data);
1446         GtkAction *action;
1447         gboolean ret = FALSE;
1448
1449         switch (event->button)
1450                 {
1451                 case MOUSE_BUTTON_8:
1452                         if (options->mouse_button_8)
1453                                 {
1454                                 action = gtk_action_group_get_action(lw->action_group, options->mouse_button_8);
1455                                 if (action)
1456                                         {
1457                                         gtk_action_activate(action);
1458                                         }
1459                                 ret = TRUE;
1460                                 }
1461                                 break;
1462                 case MOUSE_BUTTON_9:
1463                         if (options->mouse_button_9)
1464                                 {
1465                                 action = gtk_action_group_get_action(lw->action_group, options->mouse_button_9);
1466                                 if (action)
1467                                         {
1468                                         gtk_action_activate(action);
1469                                         }
1470                                 ret = TRUE;
1471                                 }
1472                         break;
1473                 default:
1474                         break;
1475                 }
1476
1477         return ret;
1478 }
1479
1480 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */