Fix #1248: Crash when hiding file list
[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 "ui-misc.h"
23
24 #include <langinfo.h>
25
26 #include <cstdlib>
27 #include <cstring>
28
29 #include <pango/pango.h>
30
31 #include <config.h>
32
33 #include "compat.h"
34 #include "debug.h"
35 #include "history-list.h"
36 #include "layout.h"
37 #include "main-defines.h"
38 #include "misc.h"
39 #include "options.h"
40 #include "typedefs.h"
41 #include "utilops.h"
42
43 /*
44  *-----------------------------------------------------------------------------
45  * widget and layout utilities
46  *-----------------------------------------------------------------------------
47  */
48
49 GtkWidget *pref_box_new(GtkWidget *parent_box, gboolean fill,
50                         GtkOrientation orientation, gboolean padding)
51 {
52         GtkWidget *box;
53
54         if (orientation == GTK_ORIENTATION_HORIZONTAL)
55                 {
56                 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, padding);
57                 }
58         else
59                 {
60                 box = gtk_box_new(GTK_ORIENTATION_VERTICAL, padding);
61                 }
62
63         gq_gtk_box_pack_start(GTK_BOX(parent_box), box, fill, fill, 0);
64         gtk_widget_show(box);
65
66         return box;
67 }
68
69 GtkWidget *pref_group_new(GtkWidget *parent_box, gboolean fill,
70                           const gchar *text, GtkOrientation orientation)
71 {
72         GtkWidget *box;
73         GtkWidget *vbox;
74         GtkWidget *hbox;
75         GtkWidget *label;
76
77         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
78
79         /* add additional spacing if necessary */
80         if (GTK_IS_VBOX(parent_box))
81                 {
82                 GList *list = gtk_container_get_children(GTK_CONTAINER(parent_box));
83                 if (list)
84                         {
85                         pref_spacer(vbox, PREF_PAD_GROUP - PREF_PAD_GAP);
86                         }
87                 g_list_free(list);
88                 }
89
90         gq_gtk_box_pack_start(GTK_BOX(parent_box), vbox, fill, fill, 0);
91         gtk_widget_show(vbox);
92
93         label = gtk_label_new(text);
94         gtk_label_set_xalign(GTK_LABEL(label), 0.0);
95         gtk_label_set_yalign(GTK_LABEL(label), 0.5);
96         pref_label_bold(label, TRUE, FALSE);
97
98         gq_gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
99         gtk_widget_show(label);
100
101         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PREF_PAD_INDENT);
102         gq_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_box_new(GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
111                 }
112         else
113                 {
114                 box = gtk_box_new(GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
115                 }
116         gq_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 = static_cast<GtkWidget *>(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 = nullptr;
148
149         frame = gtk_frame_new(text);
150         gq_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_box_new(GTK_ORIENTATION_HORIZONTAL, padding);
156                 }
157         else
158                 {
159                 box = gtk_box_new(GTK_ORIENTATION_VERTICAL, padding);
160                 }
161         gq_gtk_container_add(GTK_WIDGET(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_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
173         gq_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         spacer = gtk_separator_new(GTK_IS_HBOX(parent_box) ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL);
184         gq_gtk_box_pack_start(GTK_BOX(parent_box), spacer, FALSE, FALSE, padding / 2);
185         gtk_widget_show(spacer);
186
187         return spacer;
188 }
189
190 GtkWidget *pref_label_new(GtkWidget *parent_box, const gchar *text)
191 {
192         GtkWidget *label;
193
194         label = gtk_label_new(text);
195         gq_gtk_box_pack_start(GTK_BOX(parent_box), label, FALSE, FALSE, 0);
196         gtk_widget_show(label);
197
198         return label;
199 }
200
201 GtkWidget *pref_label_new_mnemonic(GtkWidget *parent_box, const gchar *text, GtkWidget *widget)
202 {
203         GtkWidget *label;
204
205         label = gtk_label_new_with_mnemonic(text);
206         gtk_label_set_mnemonic_widget(GTK_LABEL(label), widget);
207         gq_gtk_box_pack_start(GTK_BOX(parent_box), label, FALSE, FALSE, 0);
208         gtk_widget_show(label);
209
210         return label;
211 }
212
213 void pref_label_bold(GtkWidget *label, gboolean bold, gboolean increase_size)
214 {
215         PangoAttrList *pal;
216         PangoAttribute *pa;
217
218         if (!bold && !increase_size) return;
219
220         pal = pango_attr_list_new();
221
222         if (bold)
223                 {
224                 pa = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
225                 pa->start_index = 0;
226                 pa->end_index = G_MAXINT;
227                 pango_attr_list_insert(pal, pa);
228                 }
229
230         if (increase_size)
231                 {
232                 pa = pango_attr_scale_new(PANGO_SCALE_LARGE);
233                 pa->start_index = 0;
234                 pa->end_index = G_MAXINT;
235                 pango_attr_list_insert(pal, pa);
236                 }
237
238         gtk_label_set_attributes(GTK_LABEL(label), pal);
239         pango_attr_list_unref(pal);
240 }
241
242 GtkWidget *pref_button_new(GtkWidget *parent_box, const gchar *icon_name,
243                            const gchar *text, GCallback func, gpointer data)
244 {
245         GtkWidget *button;
246
247         if (icon_name)
248                 {
249                 button = gtk_button_new_from_icon_name(icon_name, GTK_ICON_SIZE_BUTTON);
250                 }
251         else
252                 {
253                 button = gtk_button_new();
254                 }
255
256         if (text)
257                 {
258                 gtk_button_set_use_underline(GTK_BUTTON(button), TRUE);
259                 gtk_button_set_label(GTK_BUTTON(button), text);
260                 }
261
262         if (func) g_signal_connect(G_OBJECT(button), "clicked", func, data);
263
264         if (parent_box)
265                 {
266                 gq_gtk_box_pack_start(GTK_BOX(parent_box), button, FALSE, FALSE, 0);
267                 gtk_widget_show(button);
268                 }
269
270         return button;
271 }
272
273 static GtkWidget *real_pref_checkbox_new(GtkWidget *parent_box, const gchar *text, gboolean mnemonic_text,
274                                          gboolean active, GCallback func, gpointer data)
275 {
276         GtkWidget *button;
277
278         if (mnemonic_text)
279                 {
280                 button = gtk_check_button_new_with_mnemonic(text);
281                 }
282         else
283                 {
284                 button = gtk_check_button_new_with_label(text);
285                 }
286         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), active);
287         if (func) g_signal_connect(G_OBJECT(button), "clicked", func, data);
288
289         gq_gtk_box_pack_start(GTK_BOX(parent_box), button, FALSE, FALSE, 0);
290         gtk_widget_show(button);
291
292         return button;
293 }
294
295 GtkWidget *pref_checkbox_new(GtkWidget *parent_box, const gchar *text, gboolean active,
296                              GCallback func, gpointer data)
297 {
298         return real_pref_checkbox_new(parent_box, text, FALSE, active, func, data);
299 }
300
301 #pragma GCC diagnostic push
302 #pragma GCC diagnostic ignored "-Wunused-function"
303 GtkWidget *pref_checkbox_new_mnemonic_unused(GtkWidget *parent_box, const gchar *text, gboolean active,
304                                       GCallback func, gpointer data)
305 {
306         return real_pref_checkbox_new(parent_box, text, TRUE, active, func, data);
307 }
308 #pragma GCC diagnostic pop
309
310 static void pref_checkbox_int_cb(GtkWidget *widget, gpointer data)
311 {
312         auto result = static_cast<gboolean *>(data);
313
314         *result = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
315 }
316
317 GtkWidget *pref_checkbox_new_int(GtkWidget *parent_box, const gchar *text, gboolean active,
318                                  gboolean *result)
319 {
320         GtkWidget *button;
321
322         button = pref_checkbox_new(parent_box, text, active,
323                                    G_CALLBACK(pref_checkbox_int_cb), result);
324         *result = active;
325
326         return button;
327 }
328
329 static void pref_checkbox_link_sensitivity_cb(GtkWidget *button, gpointer data)
330 {
331         auto widget = static_cast<GtkWidget *>(data);
332
333         gtk_widget_set_sensitive(widget, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)));
334 }
335
336 void pref_checkbox_link_sensitivity(GtkWidget *button, GtkWidget *widget)
337 {
338         g_signal_connect(G_OBJECT(button), "toggled",
339                          G_CALLBACK(pref_checkbox_link_sensitivity_cb), widget);
340
341         pref_checkbox_link_sensitivity_cb(button, widget);
342 }
343
344 #pragma GCC diagnostic push
345 #pragma GCC diagnostic ignored "-Wunused-function"
346 static void pref_checkbox_link_sensitivity_swap_cb_unused(GtkWidget *button, gpointer data)
347 {
348         auto *widget = static_cast<GtkWidget *>(data);
349
350         gtk_widget_set_sensitive(widget, !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)));
351 }
352
353 void pref_checkbox_link_sensitivity_swap_unused(GtkWidget *button, GtkWidget *widget)
354 {
355         g_signal_connect(G_OBJECT(button), "toggled",
356                          G_CALLBACK(pref_checkbox_link_sensitivity_swap_cb_unused), widget);
357
358         pref_checkbox_link_sensitivity_swap_cb_unused(button, widget);
359 }
360 #pragma GCC diagnostic pop
361
362 static GtkWidget *real_pref_radiobutton_new(GtkWidget *parent_box, GtkWidget *sibling,
363                                             const gchar *text, gboolean mnemonic_text, gboolean active,
364                                             GCallback func, gpointer data)
365 {
366         GtkWidget *button;
367 #if HAVE_GTK4
368         GtkToggleButton *group;;
369 #else
370         GSList *group;
371 #endif
372
373         if (sibling)
374                 {
375 #if HAVE_GTK4
376                 group = sibling;
377 #else
378                 group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(sibling));
379 #endif
380                 }
381         else
382                 {
383                 group = nullptr;
384                 }
385
386         if (mnemonic_text)
387                 {
388 #if HAVE_GTK4
389                 button = gtk_toggle_button_new_with_mnemonic(text);
390                 gtk_toggle_button_set_group(button, group);
391 #else
392                 button = gtk_radio_button_new_with_mnemonic(group, text);
393 #endif
394                 }
395         else
396                 {
397 #if HAVE_GTK4
398                 button = gtk_toggle_button_new_with_label(text);
399                 gtk_toggle_button_set_group(button, group);
400 #else
401                 button = gtk_radio_button_new_with_label(group, text);
402 #endif
403                 }
404
405         if (active) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), active);
406         if (func) g_signal_connect(G_OBJECT(button), "clicked", func, data);
407
408         gq_gtk_box_pack_start(GTK_BOX(parent_box), button, FALSE, FALSE, 0);
409         gtk_widget_show(button);
410
411         return button;
412 }
413
414 GtkWidget *pref_radiobutton_new(GtkWidget *parent_box, GtkWidget *sibling,
415                                 const gchar *text, gboolean active,
416                                 GCallback func, gpointer data)
417 {
418         return real_pref_radiobutton_new(parent_box, sibling, text, FALSE, active, func, data);
419 }
420
421 #pragma GCC diagnostic push
422 #pragma GCC diagnostic ignored "-Wunused-function"
423 GtkWidget *pref_radiobutton_new_mnemonic_unused(GtkWidget *parent_box, GtkWidget *sibling,
424                                          const gchar *text, gboolean active,
425                                          GCallback func, gpointer data)
426 {
427         return real_pref_radiobutton_new(parent_box, sibling, text, TRUE, active, func, data);
428 }
429
430 #define PREF_RADIO_VALUE_KEY "pref_radio_value"
431
432 static void pref_radiobutton_int_cb_unused(GtkWidget *widget, gpointer data)
433 {
434         auto *result = static_cast<gboolean *>(data);
435
436         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
437                 {
438                 *result = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), PREF_RADIO_VALUE_KEY));
439                 }
440 }
441
442 GtkWidget *pref_radiobutton_new_int_unused(GtkWidget *parent_box, GtkWidget *sibling,
443                                     const gchar *text, gboolean active,
444                                     gboolean *result, gboolean value,
445                                     GCallback, gpointer)
446 {
447         GtkWidget *button;
448
449         button = pref_radiobutton_new(parent_box, sibling, text, active,
450                                       G_CALLBACK(pref_radiobutton_int_cb_unused), result);
451         g_object_set_data(G_OBJECT(button), PREF_RADIO_VALUE_KEY, GINT_TO_POINTER(value));
452         if (active) *result = value;
453
454         return button;
455 }
456 #pragma GCC diagnostic pop
457
458 static GtkWidget *real_pref_spin_new(GtkWidget *parent_box, const gchar *text, const gchar *suffix,
459                                      gboolean mnemonic_text,
460                                      gdouble min, gdouble max, gdouble step, gint digits,
461                                      gdouble value,
462                                      GCallback func, gpointer data)
463 {
464         GtkWidget *spin;
465         GtkWidget *box;
466         GtkWidget *label;
467
468         box = pref_box_new(parent_box, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
469
470         spin = gtk_spin_button_new_with_range(min, max, step);
471         gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), digits);
472         gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), value);
473
474         if (func)
475                 {
476                 g_signal_connect(G_OBJECT(spin), "value_changed", G_CALLBACK(func), data);
477                 }
478
479         if (text)
480                 {
481                 if (mnemonic_text)
482                         {
483                         label = pref_label_new_mnemonic(box, text, spin);
484                         }
485                 else
486                         {
487                         label = pref_label_new(box, text);
488                         }
489                 pref_link_sensitivity(label, spin);
490                 }
491
492         gq_gtk_box_pack_start(GTK_BOX(box), spin, FALSE, FALSE, 0);
493         gtk_widget_show(spin);
494
495         /* perhaps this should only be PREF_PAD_GAP distance from spinbutton ? */
496         if (suffix)
497                 {
498                 label =  pref_label_new(box, suffix);
499                 pref_link_sensitivity(label, spin);
500                 }
501
502         return spin;
503 }
504
505 GtkWidget *pref_spin_new(GtkWidget *parent_box, const gchar *text, const gchar *suffix,
506                          gdouble min, gdouble max, gdouble step, gint digits,
507                          gdouble value,
508                          GCallback func, gpointer data)
509 {
510         return real_pref_spin_new(parent_box, text, suffix, FALSE,
511                                   min, max, step, digits, value, func, data);
512 }
513
514 #pragma GCC diagnostic push
515 #pragma GCC diagnostic ignored "-Wunused-function"
516 GtkWidget *pref_spin_new_mnemonic_unused(GtkWidget *parent_box, const gchar *text, const gchar *suffix,
517                                   gdouble min, gdouble max, gdouble step, gint digits,
518                                   gdouble value,
519                                   GCallback func, gpointer data)
520 {
521         return real_pref_spin_new(parent_box, text, suffix, TRUE,
522                                   min, max, step, digits, value, func, data);
523 }
524 #pragma GCC diagnostic pop
525
526 static void pref_spin_int_cb(GtkWidget *widget, gpointer data)
527 {
528         auto var = static_cast<gint *>(data);
529         *var = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget));
530 }
531
532 GtkWidget *pref_spin_new_int(GtkWidget *parent_box, const gchar *text, const gchar *suffix,
533                              gint min, gint max, gint step,
534                              gint value, gint *value_var)
535 {
536         *value_var = value;
537         return pref_spin_new(parent_box, text, suffix,
538                              static_cast<gdouble>(min), static_cast<gdouble>(max), static_cast<gdouble>(step), 0,
539                              value,
540                              G_CALLBACK(pref_spin_int_cb), value_var);
541 }
542
543 static void pref_link_sensitivity_cb(GtkWidget *watch, GtkStateType, gpointer data)
544 {
545         auto widget = static_cast<GtkWidget *>(data);
546
547         gtk_widget_set_sensitive(widget, gtk_widget_is_sensitive(watch));
548 }
549
550 void pref_link_sensitivity(GtkWidget *widget, GtkWidget *watch)
551 {
552         g_signal_connect(G_OBJECT(watch), "state_changed",
553                          G_CALLBACK(pref_link_sensitivity_cb), widget);
554 }
555
556 void pref_signal_block_data(GtkWidget *widget, gpointer data)
557 {
558         g_signal_handlers_block_matched(widget, G_SIGNAL_MATCH_DATA,
559                                         0, 0, nullptr, nullptr, data);
560 }
561
562 void pref_signal_unblock_data(GtkWidget *widget, gpointer data)
563 {
564         g_signal_handlers_unblock_matched(widget, G_SIGNAL_MATCH_DATA,
565                                           0, 0, nullptr, nullptr, data);
566 }
567
568 GtkWidget *pref_table_new(GtkWidget *parent_box, gint, gint, gboolean, gboolean fill)
569 {
570         GtkWidget *table;
571
572         table = gtk_grid_new();
573         gtk_grid_set_row_spacing(GTK_GRID(table), PREF_PAD_GAP);
574         gtk_grid_set_column_spacing(GTK_GRID(table), PREF_PAD_SPACE);
575
576         if (parent_box)
577                 {
578                 gq_gtk_box_pack_start(GTK_BOX(parent_box), table, fill, fill, 0);
579                 gtk_widget_show(table);
580                 }
581
582         return table;
583 }
584
585 GtkWidget *pref_table_box(GtkWidget *table, gint column, gint row,
586                           GtkOrientation orientation, const gchar *text)
587 {
588         GtkWidget *box;
589         GtkWidget *shell;
590
591         if (text)
592                 {
593                 shell = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
594                 box = pref_group_new(shell, TRUE, text, orientation);
595                 }
596         else
597                 {
598                 if (orientation == GTK_ORIENTATION_HORIZONTAL)
599                         {
600                         box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
601                         }
602                 else
603                         {
604                         box = gtk_box_new(GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
605                         }
606                 shell = box;
607                 }
608
609         gq_gtk_grid_attach(GTK_GRID(table), shell, column, column + 1, row, row + 1, static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL), static_cast<GtkAttachOptions>(0), 0, 0);
610
611         gtk_widget_show(shell);
612
613         return box;
614 }
615
616 GtkWidget *pref_table_label(GtkWidget *table, gint column, gint row,
617                             const gchar *text, GtkAlign alignment)
618 {
619         GtkWidget *label;
620
621         label = gtk_label_new(text);
622         gtk_widget_set_halign(label, alignment);
623         gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
624         gq_gtk_grid_attach(GTK_GRID(table), label, column, column + 1, row, row + 1,  GTK_FILL, static_cast<GtkAttachOptions>(0), 0, 0);
625         gtk_widget_show(label);
626
627         return label;
628 }
629
630 GtkWidget *pref_table_button(GtkWidget *table, gint column, gint row,
631                              const gchar *stock_id, const gchar *text,
632                              GCallback func, gpointer data)
633 {
634         GtkWidget *button;
635
636         button = pref_button_new(nullptr, stock_id, text, func, data);
637         gq_gtk_grid_attach(GTK_GRID(table), button, column, column + 1, row, row + 1,  GTK_FILL, static_cast<GtkAttachOptions>(0), 0, 0);
638         gtk_widget_show(button);
639
640         return button;
641 }
642
643 GtkWidget *pref_table_spin(GtkWidget *table, gint column, gint row,
644                            const gchar *text, const gchar *suffix,
645                            gdouble min, gdouble max, gdouble step, gint digits,
646                            gdouble value,
647                            GCallback func, gpointer data)
648 {
649         GtkWidget *spin;
650         GtkWidget *box;
651         GtkWidget *label;
652
653         spin = gtk_spin_button_new_with_range(min, max, step);
654         gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), digits);
655         gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), value);
656         if (func)
657                 {
658                 g_signal_connect(G_OBJECT(spin), "value_changed", G_CALLBACK(func), data);
659                 }
660
661         if (text)
662                 {
663                 label = pref_table_label(table, column, row, text, GTK_ALIGN_END);
664                 pref_link_sensitivity(label, spin);
665                 column++;
666                 }
667
668         if (suffix)
669                 {
670                 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
671                 gq_gtk_box_pack_start(GTK_BOX(box), spin, FALSE, FALSE, 0);
672                 gtk_widget_show(spin);
673
674                 label = pref_label_new(box, suffix);
675                 pref_link_sensitivity(label, spin);
676                 }
677         else
678                 {
679                 box = spin;
680                 }
681
682         gq_gtk_grid_attach(GTK_GRID(table), box, column, column + 1, row, row + 1, static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL), static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL), 0, 0);
683         gtk_widget_show(box);
684
685         return spin;
686 }
687
688 GtkWidget *pref_table_spin_new_int(GtkWidget *table, gint column, gint row,
689                                    const gchar *text, const gchar *suffix,
690                                    gint min, gint max, gint step,
691                                    gint value, gint *value_var)
692 {
693         *value_var = value;
694         return pref_table_spin(table, column, row,
695                                text, suffix,
696                                static_cast<gdouble>(min), static_cast<gdouble>(max), static_cast<gdouble>(step), 0,
697                                value,
698                                G_CALLBACK(pref_spin_int_cb), value_var);
699 }
700
701
702 GtkWidget *pref_toolbar_new(GtkWidget *parent_box)
703 {
704         GtkWidget *tbar;
705
706         tbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
707
708         if (parent_box)
709                 {
710                 gq_gtk_box_pack_start(GTK_BOX(parent_box), tbar, FALSE, FALSE, 0);
711                 gtk_widget_show(tbar);
712                 }
713         return tbar;
714 }
715
716 GtkWidget *pref_toolbar_button(GtkWidget *toolbar,
717                                const gchar *icon_name, const gchar *label, gboolean toggle,
718                                const gchar *description,
719                                GCallback func, gpointer data)
720 {
721         GtkWidget *item;
722
723         if (toggle) // TODO: TG seems no function uses toggle now
724                 {
725                 item = GTK_WIDGET(gtk_toggle_tool_button_new());
726                 if (icon_name) gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), icon_name);
727                 if (label) gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), label);
728                 }
729         else
730                 {
731                 GtkWidget *icon = nullptr;
732                 if (icon_name)
733                         {
734                         icon = gtk_image_new_from_icon_name(icon_name, GTK_ICON_SIZE_LARGE_TOOLBAR); // TODO: TG which size?
735                         gtk_widget_show(icon);
736                         }
737                 item = GTK_WIDGET(gtk_tool_button_new(icon, label));
738                 }
739         gtk_tool_button_set_use_underline(GTK_TOOL_BUTTON(item), TRUE);
740
741         if (func) g_signal_connect(item, "clicked", func, data);
742         gq_gtk_container_add(GTK_WIDGET(toolbar), item);
743         gtk_widget_show(item);
744
745         if (description)
746                 {
747                 gtk_widget_set_tooltip_text(item, description);
748                 }
749
750         return item;
751 }
752
753 #pragma GCC diagnostic push
754 #pragma GCC diagnostic ignored "-Wunused-function"
755 void pref_toolbar_button_set_icon_unused(GtkWidget *button, GtkWidget *widget, const gchar *stock_id)
756 {
757         if (widget)
758                 {
759                 gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(button), widget);
760                 }
761         else if (stock_id)
762                 {
763                 gtk_tool_button_set_stock_id(GTK_TOOL_BUTTON(button), stock_id);
764                 }
765 }
766
767 GtkWidget *pref_toolbar_spacer_unused(GtkWidget *toolbar)
768 {
769         GtkWidget *item;
770
771         item = GTK_WIDGET(gtk_separator_tool_item_new());
772         gq_gtk_container_add(GTK_WIDGET(toolbar), item);
773         gtk_widget_show(item);
774
775         return item;
776 }
777 #pragma GCC diagnostic pop
778
779
780 /*
781  *-----------------------------------------------------------------------------
782  * date selection entry
783  *-----------------------------------------------------------------------------
784  */
785
786 #define DATE_SELECION_KEY "date_selection_data"
787
788
789 struct DateSelection
790 {
791         GtkWidget *box;
792
793         GtkWidget *spin_d;
794         GtkWidget *spin_m;
795         GtkWidget *spin_y;
796
797         GtkWidget *button;
798
799         GtkWidget *window;
800         GtkWidget *calendar;
801 };
802
803
804 static void date_selection_popup_hide(DateSelection *ds)
805 {
806         if (!ds->window) return;
807
808         if (gtk_widget_has_grab(ds->window))
809                 {
810                 gtk_grab_remove(ds->window);
811                 gdk_keyboard_ungrab(GDK_CURRENT_TIME);
812                 gdk_pointer_ungrab(GDK_CURRENT_TIME);
813                 }
814
815         gtk_widget_hide(ds->window);
816
817         gq_gtk_widget_destroy(ds->window);
818         ds->window = nullptr;
819         ds->calendar = nullptr;
820
821         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ds->button), FALSE);
822 }
823
824 static gboolean date_selection_popup_release_cb(GtkWidget *, GdkEventButton *, gpointer data)
825 {
826         auto ds = static_cast<DateSelection *>(data);
827
828         date_selection_popup_hide(ds);
829         return TRUE;
830 }
831
832 static gboolean date_selection_popup_press_cb(GtkWidget *, GdkEventButton *event, gpointer data)
833 {
834         auto ds = static_cast<DateSelection *>(data);
835         gint x;
836         gint y;
837         gint w;
838         gint h;
839         gint xr;
840         gint yr;
841         GdkWindow *window;
842
843         xr = static_cast<gint>(event->x_root);
844         yr = static_cast<gint>(event->y_root);
845
846         window = gtk_widget_get_window(ds->window);
847         gdk_window_get_origin(window, &x, &y);
848         w = gdk_window_get_width(window);
849         h = gdk_window_get_height(window);
850
851         if (xr < x || yr < y || xr > x + w || yr > y + h)
852                 {
853                 g_signal_connect(G_OBJECT(ds->window), "button_release_event",
854                                  G_CALLBACK(date_selection_popup_release_cb), ds);
855                 return TRUE;
856                 }
857
858         return FALSE;
859 }
860
861 static void date_selection_popup_sync(DateSelection *ds)
862 {
863         guint day;
864         guint month;
865         guint year;
866
867 #if HAVE_GTK4
868         GDateTime *date_selected;
869
870         date_selected = gtk_calendar_get_date(GTK_CALENDAR(ds->calendar));
871         g_date_time_get_ymd(date_selected, static_cast<guint>(&year), static_cast<guint>(&month), static_cast<guint>(&day));
872
873         g_date_time_unref(date_selected);
874 #else
875         gtk_calendar_get_date(GTK_CALENDAR(ds->calendar), &year, &month, &day);
876         /* month is range 0 to 11 */
877         month = month + 1;
878 #endif
879         date_selection_set(ds->box, day, month, year);
880 }
881
882 static gboolean date_selection_popup_keypress_cb(GtkWidget *, GdkEventKey *event, gpointer data)
883 {
884         auto ds = static_cast<DateSelection *>(data);
885
886         switch (event->keyval)
887                 {
888                 case GDK_KEY_Return:
889                 case GDK_KEY_KP_Enter:
890                 case GDK_KEY_Tab:
891                 case GDK_KEY_ISO_Left_Tab:
892                         date_selection_popup_sync(ds);
893                         date_selection_popup_hide(ds);
894                         break;
895                 case GDK_KEY_Escape:
896                         date_selection_popup_hide(ds);
897                         break;
898                 default:
899                         break;
900                 }
901
902         return FALSE;
903 }
904
905 static void date_selection_day_cb(GtkWidget *, gpointer data)
906 {
907         auto ds = static_cast<DateSelection *>(data);
908
909         date_selection_popup_sync(ds);
910 }
911
912 static void date_selection_doubleclick_cb(GtkWidget *, gpointer data)
913 {
914         auto ds = static_cast<DateSelection *>(data);
915
916         date_selection_popup_hide(ds);
917 }
918
919 static void date_selection_popup(DateSelection *ds)
920 {
921         GDateTime *date;
922         gint wx;
923         gint wy;
924         gint x;
925         gint y;
926         GtkAllocation button_allocation;
927         GtkAllocation window_allocation;
928
929         if (ds->window) return;
930
931         ds->window = gtk_window_new(GTK_WINDOW_POPUP);
932         gtk_window_set_resizable(GTK_WINDOW(ds->window), FALSE);
933         g_signal_connect(G_OBJECT(ds->window), "button_press_event",
934                          G_CALLBACK(date_selection_popup_press_cb), ds);
935         g_signal_connect(G_OBJECT(ds->window), "key_press_event",
936                          G_CALLBACK(date_selection_popup_keypress_cb), ds);
937
938         ds->calendar = gtk_calendar_new();
939         gq_gtk_container_add(GTK_WIDGET(ds->window), ds->calendar);
940         gtk_widget_show(ds->calendar);
941
942         date = date_selection_get(ds->box);
943 #if HAVE_GTK4
944         gtk_calendar_select_day(GTK_CALENDAR(ds->calendar), date);
945 #else
946         gtk_calendar_select_month(GTK_CALENDAR(ds->calendar), g_date_time_get_month(date), g_date_time_get_year(date));
947         gtk_calendar_select_day(GTK_CALENDAR(ds->calendar), g_date_time_get_day_of_month(date));
948 #endif
949         g_date_time_unref(date);
950
951         g_signal_connect(G_OBJECT(ds->calendar), "day_selected",
952                          G_CALLBACK(date_selection_day_cb), ds);
953         g_signal_connect(G_OBJECT(ds->calendar), "day_selected_double_click",
954                         G_CALLBACK(date_selection_doubleclick_cb), ds);
955
956         gtk_widget_realize(ds->window);
957
958         gdk_window_get_origin(gtk_widget_get_window(ds->button), &wx, &wy);
959
960         gtk_widget_get_allocation(ds->button, &button_allocation);
961         gtk_widget_get_allocation(ds->window, &window_allocation);
962
963         x = wx + button_allocation.x + button_allocation.width - window_allocation.width;
964         y = wy + button_allocation.y + button_allocation.height;
965
966         if (y + window_allocation.height > gdk_screen_height())
967                 {
968                 y = wy + button_allocation.y - window_allocation.height;
969                 }
970         if (x < 0) x = 0;
971         if (y < 0) y = 0;
972
973         gq_gtk_window_move(GTK_WINDOW(ds->window), x, y);
974         gtk_widget_show(ds->window);
975
976         gtk_widget_grab_focus(ds->calendar);
977         gdk_pointer_grab(gtk_widget_get_window(ds->window), TRUE,
978                          static_cast<GdkEventMask>(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK),
979                          nullptr, nullptr, GDK_CURRENT_TIME);
980         gdk_keyboard_grab(gtk_widget_get_window(ds->window), TRUE, GDK_CURRENT_TIME);
981         gtk_grab_add(ds->window);
982
983         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ds->button), TRUE);
984 }
985
986 static void date_selection_button_cb(GtkWidget *, gpointer data)
987 {
988         auto ds = static_cast<DateSelection *>(data);
989
990         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ds->button)) == (!ds->window))
991                 {
992                 date_selection_popup(ds);
993                 }
994 }
995
996 static void button_size_allocate_cb(GtkWidget *button, GtkAllocation *allocation, gpointer data)
997 {
998         auto spin = static_cast<GtkWidget *>(data);
999         GtkRequisition spin_requisition;
1000         gtk_widget_get_requisition(spin, &spin_requisition);
1001
1002         if (allocation->height > spin_requisition.height)
1003                 {
1004                 GtkAllocation button_allocation;
1005                 GtkAllocation spin_allocation;
1006
1007                 gtk_widget_get_allocation(button, &button_allocation);
1008                 gtk_widget_get_allocation(spin, &spin_allocation);
1009                 button_allocation.height = spin_requisition.height;
1010                 button_allocation.y = spin_allocation.y +
1011                         (spin_allocation.height - spin_requisition.height) / 2;
1012                 gtk_widget_size_allocate(button, &button_allocation);
1013                 }
1014 }
1015
1016 static void spin_increase(GtkWidget *spin, gint value)
1017 {
1018         GtkRequisition req;
1019
1020         gtk_widget_size_request(spin, &req);
1021         gtk_widget_set_size_request(spin, req.width + value, -1);
1022 }
1023
1024 static void date_selection_destroy_cb(GtkWidget *, gpointer data)
1025 {
1026         auto ds = static_cast<DateSelection *>(data);
1027
1028         date_selection_popup_hide(ds);
1029
1030         g_free(ds);
1031 }
1032
1033 GtkWidget *date_selection_new()
1034 {
1035         DateSelection *ds;
1036         GtkWidget *icon;
1037
1038         ds = g_new0(DateSelection, 1);
1039         gchar *date_format;
1040         gint i;
1041
1042         ds->box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
1043         g_signal_connect(G_OBJECT(ds->box), "destroy",
1044                          G_CALLBACK(date_selection_destroy_cb), ds);
1045
1046         date_format = nl_langinfo(D_FMT);
1047
1048         if (strlen(date_format) == 8)
1049                 {
1050                 for (i=1; i<8; i=i+3)
1051                         {
1052                         switch (date_format[i])
1053                                 {
1054                                 case 'd':
1055                                         ds->spin_d = pref_spin_new(ds->box, nullptr, nullptr, 1, 31, 1, 0, 1, nullptr, nullptr);
1056                                         break;
1057                                 case 'm':
1058                                         ds->spin_m = pref_spin_new(ds->box, nullptr, nullptr, 1, 12, 1, 0, 1, nullptr, nullptr);
1059                                         break;
1060                                 case 'y': case 'Y':
1061                                         ds->spin_y = pref_spin_new(ds->box, nullptr, nullptr, 1900, 9999, 1, 0, 1900, nullptr, nullptr);
1062                                         break;
1063                                 default:
1064                                         log_printf("Warning: Date locale %s is unknown", date_format);
1065                                         break;
1066                                 }
1067                         }
1068                 }
1069         else
1070                 {
1071                 ds->spin_m = pref_spin_new(ds->box, nullptr, nullptr, 1, 12, 1, 0, 1, nullptr, nullptr);
1072                 ds->spin_d = pref_spin_new(ds->box, nullptr, nullptr, 1, 31, 1, 0, 1, nullptr, nullptr);
1073                 ds->spin_y = pref_spin_new(ds->box, nullptr, nullptr, 1900, 9999, 1, 0, 1900, nullptr, nullptr);
1074                 }
1075
1076         spin_increase(ds->spin_y, 5);
1077
1078         ds->button = gtk_toggle_button_new();
1079         g_signal_connect(G_OBJECT(ds->button), "size_allocate",
1080                          G_CALLBACK(button_size_allocate_cb), ds->spin_y);
1081
1082         icon = gtk_image_new_from_icon_name(GQ_ICON_PAN_DOWN, GTK_ICON_SIZE_BUTTON);
1083         gq_gtk_container_add(GTK_WIDGET(ds->button), icon);
1084         gtk_widget_show(icon);
1085
1086         gq_gtk_box_pack_start(GTK_BOX(ds->box), ds->button, FALSE, FALSE, 0);
1087         g_signal_connect(G_OBJECT(ds->button), "clicked",
1088                          G_CALLBACK(date_selection_button_cb), ds);
1089         gtk_widget_show(ds->button);
1090
1091         g_object_set_data(G_OBJECT(ds->box), DATE_SELECION_KEY, ds);
1092
1093         return ds->box;
1094 }
1095
1096 void date_selection_set(GtkWidget *widget, gint day, gint month, gint year)
1097 {
1098         DateSelection *ds;
1099
1100         ds = static_cast<DateSelection *>(g_object_get_data(G_OBJECT(widget), DATE_SELECION_KEY));
1101         if (!ds) return;
1102
1103         gtk_spin_button_set_value(GTK_SPIN_BUTTON(ds->spin_d), static_cast<gdouble>(day));
1104         gtk_spin_button_set_value(GTK_SPIN_BUTTON(ds->spin_m), static_cast<gdouble>(month));
1105         gtk_spin_button_set_value(GTK_SPIN_BUTTON(ds->spin_y), static_cast<gdouble>(year));
1106 }
1107
1108 /**
1109  * @brief Returns date structure set to value of spin buttons
1110  * @param widget #DateSelection
1111  * @returns
1112  *
1113  * Free returned structure with g_date_time_unref();
1114  */
1115 GDateTime *date_selection_get(GtkWidget *widget)
1116 {
1117         DateSelection *ds;
1118         gint day;
1119         gint month;
1120         gint year;
1121         GDateTime *date;
1122
1123         ds = static_cast<DateSelection *>(g_object_get_data(G_OBJECT(widget), DATE_SELECION_KEY));
1124         if (!ds)
1125                 {
1126                 return nullptr;
1127                 }
1128
1129         day = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ds->spin_d));
1130         month = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ds->spin_m));
1131         year = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ds->spin_y));
1132
1133         date = g_date_time_new_local(year, month, day, 0, 0, 0);
1134
1135         return date;
1136 }
1137
1138 void date_selection_time_set(GtkWidget *widget, time_t t)
1139 {
1140         struct tm *lt;
1141
1142         lt = localtime(&t);
1143         if (!lt) return;
1144
1145         date_selection_set(widget, lt->tm_mday, lt->tm_mon + 1, lt->tm_year + 1900);
1146 }
1147
1148 #pragma GCC diagnostic push
1149 #pragma GCC diagnostic ignored "-Wunused-function"
1150 time_t date_selection_time_get_unused(GtkWidget *widget)
1151 {
1152         struct tm lt;
1153         gint day = 0;
1154         gint month = 0;
1155         gint year = 0;
1156
1157         date_selection_get(widget);
1158
1159         lt.tm_sec = 0;
1160         lt.tm_min = 0;
1161         lt.tm_hour = 0;
1162         lt.tm_mday = day;
1163         lt.tm_mon = month - 1;
1164         lt.tm_year = year - 1900;
1165         lt.tm_isdst = 0;
1166
1167         return mktime(&lt);
1168 }
1169 #pragma GCC diagnostic pop
1170
1171 /*
1172  *-----------------------------------------------------------------------------
1173  * storing data in a history list with key,data pairs
1174  *-----------------------------------------------------------------------------
1175  */
1176
1177 #define PREF_LIST_MARKER_INT "[INT]:"
1178 #define PREF_LIST_MARKER_DOUBLE "[DOUBLE]:"
1179 #define PREF_LIST_MARKER_STRING "[STRING]:"
1180
1181 static GList *pref_list_find(const gchar *group, const gchar *token)
1182 {
1183         GList *work;
1184         gint l;
1185
1186         l = strlen(token);
1187
1188         work = history_list_get_by_key(group);
1189         while (work)
1190                 {
1191                 auto text = static_cast<const gchar *>(work->data);
1192
1193                 if (strncmp(text, token, l) == 0) return work;
1194
1195                 work = work->next;
1196                 }
1197
1198         return nullptr;
1199 }
1200
1201 static gboolean pref_list_get(const gchar *group, const gchar *key, const gchar *marker, const gchar **result)
1202 {
1203         gchar *token;
1204         GList *work;
1205         gboolean ret;
1206
1207         if (!group || !key || !marker)
1208                 {
1209                 *result = nullptr;
1210                 return FALSE;
1211                 }
1212
1213         token = g_strconcat(key, marker, NULL);
1214
1215         work = pref_list_find(group, token);
1216         if (work)
1217                 {
1218                 *result = static_cast<const gchar *>(work->data) + strlen(token);
1219                 if (strlen(*result) == 0) *result = nullptr;
1220                 ret = TRUE;
1221                 }
1222         else
1223                 {
1224                 *result = nullptr;
1225                 ret = FALSE;
1226                 }
1227
1228         g_free(token);
1229
1230         return ret;
1231 }
1232
1233 static void pref_list_set(const gchar *group, const gchar *key, const gchar *marker, const gchar *text)
1234 {
1235         gchar *token;
1236         gchar *path;
1237         GList *work;
1238
1239         if (!group || !key || !marker) return;
1240
1241         token = g_strconcat(key, marker, NULL);
1242         path = g_strconcat(token, text, NULL);
1243
1244         work = pref_list_find(group, token);
1245         if (work)
1246                 {
1247                 auto old_path = static_cast<gchar *>(work->data);
1248
1249                 if (text)
1250                         {
1251                         work->data = path;
1252                         path = nullptr;
1253
1254                         g_free(old_path);
1255                         }
1256                 else
1257                         {
1258                         history_list_item_remove(group, old_path);
1259                         }
1260                 }
1261         else if (text)
1262                 {
1263                 history_list_add_to_key(group, path, 0);
1264                 }
1265
1266         g_free(path);
1267         g_free(token);
1268 }
1269
1270 void pref_list_int_set(const gchar *group, const gchar *key, gint value)
1271 {
1272         gchar *text;
1273
1274         text = g_strdup_printf("%d", value);
1275         pref_list_set(group, key, PREF_LIST_MARKER_INT, text);
1276         g_free(text);
1277 }
1278
1279 gboolean pref_list_int_get(const gchar *group, const gchar *key, gint *result)
1280 {
1281         const gchar *text;
1282
1283         if (!group || !key)
1284                 {
1285                 *result = 0;
1286                 return FALSE;
1287                 }
1288
1289         if (pref_list_get(group, key, PREF_LIST_MARKER_INT, &text) && text)
1290                 {
1291                 *result = static_cast<gint>(strtol(text, nullptr, 10));
1292                 return TRUE;
1293                 }
1294
1295         *result = 0;
1296         return FALSE;
1297 }
1298
1299 #pragma GCC diagnostic push
1300 #pragma GCC diagnostic ignored "-Wunused-function"
1301 void pref_list_double_set_unused(const gchar *group, const gchar *key, gdouble value)
1302 {
1303         gchar text[G_ASCII_DTOSTR_BUF_SIZE];
1304
1305         g_ascii_dtostr(text, sizeof(text), value);
1306         pref_list_set(group, key, PREF_LIST_MARKER_DOUBLE, text);
1307 }
1308
1309 gboolean pref_list_double_get_unused(const gchar *group, const gchar *key, gdouble *result)
1310 {
1311         const gchar *text;
1312
1313         if (!group || !key)
1314                 {
1315                 *result = 0;
1316                 return FALSE;
1317                 }
1318
1319         if (pref_list_get(group, key, PREF_LIST_MARKER_DOUBLE, &text) && text)
1320                 {
1321                 *result = g_ascii_strtod(text, nullptr);
1322                 return TRUE;
1323                 }
1324
1325         *result = 0;
1326         return FALSE;
1327 }
1328
1329 void pref_list_string_set_unused(const gchar *group, const gchar *key, const gchar *value)
1330 {
1331         pref_list_set(group, key, PREF_LIST_MARKER_STRING, value);
1332 }
1333
1334 gboolean pref_list_string_get_unused(const gchar *group, const gchar *key, const gchar **result)
1335 {
1336         return pref_list_get(group, key, PREF_LIST_MARKER_STRING, result);
1337 }
1338 #pragma GCC diagnostic pop
1339
1340 void pref_color_button_set_cb(GtkWidget *widget, gpointer data)
1341 {
1342         auto color = static_cast<GdkRGBA *>(data);
1343
1344         gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(widget), color);
1345 }
1346
1347 GtkWidget *pref_color_button_new(GtkWidget *parent_box, const gchar *title, GdkRGBA *color, GCallback func, gpointer data)
1348 {
1349         GtkWidget *button;
1350
1351         if (color)
1352                 {
1353                 button = gtk_color_button_new_with_rgba(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                 gq_gtk_box_pack_start(GTK_BOX(parent_box), hbox, TRUE, TRUE, 0);
1372
1373                 gq_gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
1374                 gq_gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
1375
1376                 gq_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;
1398                 GtkTextIter end;
1399
1400                 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_widget));
1401                 gtk_text_buffer_get_bounds(buffer, &start, &end);
1402
1403                 return gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1404                 }
1405
1406         if (GTK_IS_ENTRY(text_widget))
1407                 {
1408                 return g_strdup(gq_gtk_entry_get_text(GTK_ENTRY(text_widget)));
1409                 }
1410
1411         return nullptr;
1412         
1413
1414 }
1415
1416 gchar *text_widget_text_pull_selected(GtkWidget *text_widget)
1417 {
1418         if (GTK_IS_TEXT_VIEW(text_widget))
1419                 {
1420                 GtkTextBuffer *buffer;
1421                 GtkTextIter start;
1422                 GtkTextIter end;
1423
1424                 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_widget));
1425                 gtk_text_buffer_get_bounds(buffer, &start, &end);
1426
1427                 if (gtk_text_buffer_get_selection_bounds(buffer, &start, &end))
1428                         {
1429                         gtk_text_iter_set_line_offset(&start, 0);
1430                         gtk_text_iter_forward_to_line_end(&end);
1431                         }
1432
1433                 return gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1434                 }
1435
1436         if (GTK_IS_ENTRY(text_widget))
1437                 {
1438                 return g_strdup(gq_gtk_entry_get_text(GTK_ENTRY(text_widget)));
1439                 }
1440
1441         return nullptr;
1442         
1443 }
1444
1445 static gint simple_sort_cb(gconstpointer a, gconstpointer b)
1446 {
1447         const ActionItem *a_action;
1448         const ActionItem *b_action;
1449
1450         a_action = static_cast<const ActionItem *>(a);
1451         b_action = static_cast<const ActionItem *>(b);
1452
1453         return g_strcmp0(a_action->name, b_action->name);
1454 }
1455
1456 void free_action_items_cb(gpointer data)
1457 {
1458         ActionItem *action_item;
1459
1460         action_item = static_cast<ActionItem *>(data);
1461         g_free((gchar *)action_item->icon_name);
1462         g_free((gchar *)action_item->name);
1463         g_free((gchar *)action_item->label);
1464         g_free(action_item);
1465 }
1466
1467 void action_items_free(GList *list)
1468 {
1469         g_list_free_full(list, free_action_items_cb);
1470 }
1471
1472 /**
1473  * @brief Get a list of menu actions
1474  * @param
1475  * @returns GList ActionItem
1476  *
1477  * Free returned list with action_items_free(list)
1478  *
1479  * The list generated is used in the --remote --action-list command and
1480  * programmable mouse buttons 8 and 9.
1481  */
1482 GList* get_action_items()
1483 {
1484         ActionItem *action_item;
1485         const gchar *accel_path;
1486         gboolean duplicate;
1487         gchar *action_name;
1488         gchar *label;
1489         gchar *tooltip;
1490         GList *actions;
1491         GList *groups;
1492         GList *list_duplicates = nullptr;
1493         GList *list_unique = nullptr;
1494         GList *work1;
1495         GList *work2;
1496         GtkAction *action;
1497         LayoutWindow *lw = nullptr;
1498
1499         if (!layout_valid(&lw))
1500                 {
1501                 return nullptr;
1502                 }
1503
1504         groups = gtk_ui_manager_get_action_groups(lw->ui_manager);
1505         while (groups)
1506                 {
1507                 actions = gtk_action_group_list_actions(GTK_ACTION_GROUP(groups->data));
1508                 while (actions)
1509                         {
1510                         action = GTK_ACTION(actions->data);
1511                         accel_path = gtk_action_get_accel_path(action);
1512
1513                         if (accel_path && gtk_accel_map_lookup_entry(accel_path, nullptr))
1514                                 {
1515                                 g_object_get(action, "tooltip", &tooltip, "label", &label, NULL);
1516
1517                                 action_name = g_path_get_basename(accel_path);
1518
1519                                 /* Menu actions are irrelevant */
1520                                 if (g_strstr_len(action_name, -1, "Menu") == nullptr)
1521                                         {
1522                                         action_item = g_new0(ActionItem, 1);
1523
1524                                         /* .desktop items need the program name, Geeqie menu items need the tooltip */
1525                                         if (g_strstr_len(action_name, -1, ".desktop") == nullptr)
1526                                                 {
1527
1528                                                 /* Tooltips with newlines affect output format */
1529                                                 if (tooltip && (g_strstr_len(tooltip, -1, "\n") == nullptr) )
1530                                                         {
1531                                                         action_item->label = g_strdup(tooltip);
1532                                                         }
1533                                                 else
1534                                                         {
1535                                                         action_item->label = g_strdup(label);
1536                                                         }
1537                                                 }
1538                                         else
1539                                                 {
1540                                                 action_item->label = g_strdup(label);
1541                                                 }
1542
1543                                         action_item->name = action_name;
1544                                         action_item->icon_name = g_strdup(gtk_action_get_stock_id(action));
1545
1546                                         list_duplicates = g_list_prepend(list_duplicates, action_item);
1547                                         }
1548                                 }
1549                         actions = actions->next;
1550                         }
1551
1552                 groups = groups->next;
1553                 }
1554
1555         /* Use the shortest name i.e. ignore -Alt versions. Sort makes the shortest first in the list */
1556         list_duplicates = g_list_sort(list_duplicates, simple_sort_cb);
1557
1558         /* Ignore duplicate entries */
1559         work1 = list_duplicates;
1560         while (work1)
1561                 {
1562                 duplicate = FALSE;
1563                 work2 = list_unique;
1564                 /* The first entry must be unique, list_unique is null so control bypasses the while */
1565                 while (work2)
1566                         {
1567                         if (g_strcmp0(static_cast<ActionItem *>(work2->data)->label, static_cast<ActionItem *>(work1->data)->label) == 0)
1568                                 {
1569                                 duplicate = TRUE;
1570                                 break;
1571                                 }
1572                         work2 = work2->next;
1573                         }
1574
1575                 if (!duplicate)
1576                         {
1577                         action_item = g_new0(ActionItem, 1);
1578                         action_item->name = g_strdup(static_cast<ActionItem *>(work1->data)->name);
1579                         action_item->label = g_strdup(static_cast<ActionItem *>(work1->data)->label);
1580                         action_item->icon_name = g_strdup(static_cast<ActionItem *>(work1->data)->icon_name);
1581                         list_unique = g_list_append(list_unique, action_item);
1582                         }
1583                 work1 = work1->next;
1584                 }
1585
1586         g_list_free_full(list_duplicates, free_action_items_cb);
1587
1588         return list_unique;
1589 }
1590
1591 gboolean defined_mouse_buttons(GtkWidget *, GdkEventButton *event, gpointer data)
1592 {
1593         auto lw = static_cast<LayoutWindow *>(data);
1594         GtkAction *action;
1595         gboolean ret = FALSE;
1596
1597         switch (event->button)
1598                 {
1599                 case MOUSE_BUTTON_8:
1600                         if (options->mouse_button_8)
1601                                 {
1602                                 if (g_strstr_len(options->mouse_button_8, -1, ".desktop") != nullptr)
1603                                         {
1604                                         file_util_start_editor_from_filelist(options->mouse_button_8, layout_selection_list(lw), layout_get_path(lw), lw->window);
1605                                         ret = TRUE;
1606                                         }
1607                                 else
1608                                         {
1609                                         action = gtk_action_group_get_action(lw->action_group, options->mouse_button_8);
1610                                         if (action)
1611                                                 {
1612                                                 gtk_action_activate(action);
1613                                                 }
1614                                         ret = TRUE;
1615                                         }
1616                                 }
1617                         break;
1618                 case MOUSE_BUTTON_9:
1619                         if (options->mouse_button_9)
1620                                 {
1621                                 if (g_strstr_len(options->mouse_button_9, -1, ".desktop") != nullptr)
1622                                         {
1623                                         file_util_start_editor_from_filelist(options->mouse_button_9, layout_selection_list(lw), layout_get_path(lw), lw->window);
1624                                         }
1625                                 else
1626                                         {
1627                                         action = gtk_action_group_get_action(lw->action_group, options->mouse_button_9);
1628                                         ret = TRUE;
1629                                         if (action)
1630                                                 {
1631                                                 gtk_action_activate(action);
1632                                                 }
1633                                         ret = TRUE;
1634                                         }
1635                                 }
1636                         break;
1637                 default:
1638                         break;
1639                 }
1640
1641         return ret;
1642 }
1643
1644 GdkPixbuf *gq_gtk_icon_theme_load_icon_copy(GtkIconTheme *icon_theme, const gchar *icon_name, gint size, GtkIconLookupFlags flags)
1645 {
1646         GError *error = nullptr;
1647         GdkPixbuf *icon = gtk_icon_theme_load_icon(icon_theme, icon_name, size, flags, &error);
1648         if (error) return nullptr;
1649
1650         GdkPixbuf *pixbuf = gdk_pixbuf_copy(icon);
1651         g_object_unref(icon);
1652         return pixbuf;
1653 }
1654
1655 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */