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