clang-tidy: bugprone-macro-parentheses
[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         GtkWidget *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         gboolean *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, y;
828         gint w, h;
829         gint xr, yr;
830         GdkWindow *window;
831
832         xr = static_cast<gint>(event->x_root);
833         yr = static_cast<gint>(event->y_root);
834
835         window = gtk_widget_get_window(ds->window);
836         gdk_window_get_origin(window, &x, &y);
837         w = gdk_window_get_width(window);
838         h = gdk_window_get_height(window);
839
840         if (xr < x || yr < y || xr > x + w || yr > y + h)
841                 {
842                 g_signal_connect(G_OBJECT(ds->window), "button_release_event",
843                                  G_CALLBACK(date_selection_popup_release_cb), ds);
844                 return TRUE;
845                 }
846
847         return FALSE;
848 }
849
850 static void date_selection_popup_sync(DateSelection *ds)
851 {
852         guint day;
853         guint month;
854         guint year;
855
856 #if HAVE_GTK4
857         GDateTime *date_selected;
858
859         date_selected = gtk_calendar_get_date(GTK_CALENDAR(ds->calendar));
860         g_date_time_get_ymd(date_selected, static_cast<guint>(&year), static_cast<guint>(&month), static_cast<guint>(&day));
861
862         g_date_time_unref(date_selected);
863 #else
864         gtk_calendar_get_date(GTK_CALENDAR(ds->calendar), &year, &month, &day);
865         /* month is range 0 to 11 */
866         month = month + 1;
867 #endif
868         date_selection_set(ds->box, day, month, year);
869 }
870
871 static gboolean date_selection_popup_keypress_cb(GtkWidget *, GdkEventKey *event, gpointer data)
872 {
873         auto ds = static_cast<DateSelection *>(data);
874
875         switch (event->keyval)
876                 {
877                 case GDK_KEY_Return:
878                 case GDK_KEY_KP_Enter:
879                 case GDK_KEY_Tab:
880                 case GDK_KEY_ISO_Left_Tab:
881                         date_selection_popup_sync(ds);
882                         date_selection_popup_hide(ds);
883                         break;
884                 case GDK_KEY_Escape:
885                         date_selection_popup_hide(ds);
886                         break;
887                 default:
888                         break;
889                 }
890
891         return FALSE;
892 }
893
894 static void date_selection_day_cb(GtkWidget *, gpointer data)
895 {
896         auto ds = static_cast<DateSelection *>(data);
897
898         date_selection_popup_sync(ds);
899 }
900
901 static void date_selection_doubleclick_cb(GtkWidget *, gpointer data)
902 {
903         auto ds = static_cast<DateSelection *>(data);
904
905         date_selection_popup_hide(ds);
906 }
907
908 static void date_selection_popup(DateSelection *ds)
909 {
910         GDateTime *date;
911         gint wx, wy;
912         gint x, y;
913         GtkAllocation button_allocation;
914         GtkAllocation window_allocation;
915
916         if (ds->window) return;
917
918         ds->window = gtk_window_new(GTK_WINDOW_POPUP);
919         gtk_window_set_resizable(GTK_WINDOW(ds->window), FALSE);
920         g_signal_connect(G_OBJECT(ds->window), "button_press_event",
921                          G_CALLBACK(date_selection_popup_press_cb), ds);
922         g_signal_connect(G_OBJECT(ds->window), "key_press_event",
923                          G_CALLBACK(date_selection_popup_keypress_cb), ds);
924
925         ds->calendar = gtk_calendar_new();
926         gq_gtk_container_add(GTK_WIDGET(ds->window), ds->calendar);
927         gtk_widget_show(ds->calendar);
928
929         date = date_selection_get(ds->box);
930 #ifdef HAVE_GTK4
931         gtk_calendar_select_day(GTK_CALENDAR(ds->calendar), date);
932 #else
933         gtk_calendar_select_month(GTK_CALENDAR(ds->calendar), g_date_time_get_month(date), g_date_time_get_year(date));
934         gtk_calendar_select_day(GTK_CALENDAR(ds->calendar), g_date_time_get_day_of_month(date));
935 #endif
936         g_date_time_unref(date);
937
938         g_signal_connect(G_OBJECT(ds->calendar), "day_selected",
939                          G_CALLBACK(date_selection_day_cb), ds);
940         g_signal_connect(G_OBJECT(ds->calendar), "day_selected_double_click",
941                         G_CALLBACK(date_selection_doubleclick_cb), ds);
942
943         gtk_widget_realize(ds->window);
944
945         gdk_window_get_origin(gtk_widget_get_window(ds->button), &wx, &wy);
946
947         gtk_widget_get_allocation(ds->button, &button_allocation);
948         gtk_widget_get_allocation(ds->window, &window_allocation);
949
950         x = wx + button_allocation.x + button_allocation.width - window_allocation.width;
951         y = wy + button_allocation.y + button_allocation.height;
952
953         if (y + window_allocation.height > gdk_screen_height())
954                 {
955                 y = wy + button_allocation.y - window_allocation.height;
956                 }
957         if (x < 0) x = 0;
958         if (y < 0) y = 0;
959
960         gq_gtk_window_move(GTK_WINDOW(ds->window), x, y);
961         gtk_widget_show(ds->window);
962
963         gtk_widget_grab_focus(ds->calendar);
964         gdk_pointer_grab(gtk_widget_get_window(ds->window), TRUE,
965                          static_cast<GdkEventMask>(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK),
966                          nullptr, nullptr, GDK_CURRENT_TIME);
967         gdk_keyboard_grab(gtk_widget_get_window(ds->window), TRUE, GDK_CURRENT_TIME);
968         gtk_grab_add(ds->window);
969
970         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ds->button), TRUE);
971 }
972
973 static void date_selection_button_cb(GtkWidget *, gpointer data)
974 {
975         auto ds = static_cast<DateSelection *>(data);
976
977         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ds->button)) == (!ds->window))
978                 {
979                 date_selection_popup(ds);
980                 }
981 }
982
983 static void button_size_allocate_cb(GtkWidget *button, GtkAllocation *allocation, gpointer data)
984 {
985         auto spin = static_cast<GtkWidget *>(data);
986         GtkRequisition spin_requisition;
987         gtk_widget_get_requisition(spin, &spin_requisition);
988
989         if (allocation->height > spin_requisition.height)
990                 {
991                 GtkAllocation button_allocation;
992                 GtkAllocation spin_allocation;
993
994                 gtk_widget_get_allocation(button, &button_allocation);
995                 gtk_widget_get_allocation(spin, &spin_allocation);
996                 button_allocation.height = spin_requisition.height;
997                 button_allocation.y = spin_allocation.y +
998                         (spin_allocation.height - spin_requisition.height) / 2;
999                 gtk_widget_size_allocate(button, &button_allocation);
1000                 }
1001 }
1002
1003 static void spin_increase(GtkWidget *spin, gint value)
1004 {
1005         GtkRequisition req;
1006
1007         gtk_widget_size_request(spin, &req);
1008         gtk_widget_set_size_request(spin, req.width + value, -1);
1009 }
1010
1011 static void date_selection_destroy_cb(GtkWidget *, gpointer data)
1012 {
1013         auto ds = static_cast<DateSelection *>(data);
1014
1015         date_selection_popup_hide(ds);
1016
1017         g_free(ds);
1018 }
1019
1020 GtkWidget *date_selection_new()
1021 {
1022         DateSelection *ds;
1023         GtkWidget *icon;
1024
1025         ds = g_new0(DateSelection, 1);
1026         gchar *date_format;
1027         gint i;
1028
1029         ds->box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
1030         g_signal_connect(G_OBJECT(ds->box), "destroy",
1031                          G_CALLBACK(date_selection_destroy_cb), ds);
1032
1033         date_format = nl_langinfo(D_FMT);
1034
1035         if (strlen(date_format) == 8)
1036                 {
1037                 for (i=1; i<8; i=i+3)
1038                         {
1039                         switch (date_format[i])
1040                                 {
1041                                 case 'd':
1042                                         ds->spin_d = pref_spin_new(ds->box, nullptr, nullptr, 1, 31, 1, 0, 1, nullptr, nullptr);
1043                                         break;
1044                                 case 'm':
1045                                         ds->spin_m = pref_spin_new(ds->box, nullptr, nullptr, 1, 12, 1, 0, 1, nullptr, nullptr);
1046                                         break;
1047                                 case 'y': case 'Y':
1048                                         ds->spin_y = pref_spin_new(ds->box, nullptr, nullptr, 1900, 9999, 1, 0, 1900, nullptr, nullptr);
1049                                         break;
1050                                 default:
1051                                         log_printf("Warning: Date locale %s is unknown", date_format);
1052                                         break;
1053                                 }
1054                         }
1055                 }
1056         else
1057                 {
1058                 ds->spin_m = pref_spin_new(ds->box, nullptr, nullptr, 1, 12, 1, 0, 1, nullptr, nullptr);
1059                 ds->spin_d = pref_spin_new(ds->box, nullptr, nullptr, 1, 31, 1, 0, 1, nullptr, nullptr);
1060                 ds->spin_y = pref_spin_new(ds->box, nullptr, nullptr, 1900, 9999, 1, 0, 1900, nullptr, nullptr);
1061                 }
1062
1063         spin_increase(ds->spin_y, 5);
1064
1065         ds->button = gtk_toggle_button_new();
1066         g_signal_connect(G_OBJECT(ds->button), "size_allocate",
1067                          G_CALLBACK(button_size_allocate_cb), ds->spin_y);
1068
1069         icon = gtk_image_new_from_icon_name(GQ_ICON_PAN_DOWN, GTK_ICON_SIZE_BUTTON);
1070         gq_gtk_container_add(GTK_WIDGET(ds->button), icon);
1071         gtk_widget_show(icon);
1072
1073         gq_gtk_box_pack_start(GTK_BOX(ds->box), ds->button, FALSE, FALSE, 0);
1074         g_signal_connect(G_OBJECT(ds->button), "clicked",
1075                          G_CALLBACK(date_selection_button_cb), ds);
1076         gtk_widget_show(ds->button);
1077
1078         g_object_set_data(G_OBJECT(ds->box), DATE_SELECION_KEY, ds);
1079
1080         return ds->box;
1081 }
1082
1083 void date_selection_set(GtkWidget *widget, gint day, gint month, gint year)
1084 {
1085         DateSelection *ds;
1086
1087         ds = static_cast<DateSelection *>(g_object_get_data(G_OBJECT(widget), DATE_SELECION_KEY));
1088         if (!ds) return;
1089
1090         gtk_spin_button_set_value(GTK_SPIN_BUTTON(ds->spin_d), static_cast<gdouble>(day));
1091         gtk_spin_button_set_value(GTK_SPIN_BUTTON(ds->spin_m), static_cast<gdouble>(month));
1092         gtk_spin_button_set_value(GTK_SPIN_BUTTON(ds->spin_y), static_cast<gdouble>(year));
1093 }
1094
1095 /**
1096  * @brief Returns date structure set to value of spin buttons
1097  * @param widget #DateSelection
1098  * @returns
1099  *
1100  * Free returned structure with g_date_time_unref();
1101  */
1102 GDateTime *date_selection_get(GtkWidget *widget)
1103 {
1104         DateSelection *ds;
1105         gint day;
1106         gint month;
1107         gint year;
1108         GDateTime *date;
1109
1110         ds = static_cast<DateSelection *>(g_object_get_data(G_OBJECT(widget), DATE_SELECION_KEY));
1111         if (!ds)
1112                 {
1113                 return nullptr;
1114                 }
1115
1116         day = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ds->spin_d));
1117         month = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ds->spin_m));
1118         year = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ds->spin_y));
1119
1120         date = g_date_time_new_local(year, month, day, 0, 0, 0);
1121
1122         return date;
1123 }
1124
1125 void date_selection_time_set(GtkWidget *widget, time_t t)
1126 {
1127         struct tm *lt;
1128
1129         lt = localtime(&t);
1130         if (!lt) return;
1131
1132         date_selection_set(widget, lt->tm_mday, lt->tm_mon + 1, lt->tm_year + 1900);
1133 }
1134
1135 #pragma GCC diagnostic push
1136 #pragma GCC diagnostic ignored "-Wunused-function"
1137 time_t date_selection_time_get_unused(GtkWidget *widget)
1138 {
1139         struct tm lt;
1140         gint day = 0;
1141         gint month = 0;
1142         gint year = 0;
1143
1144         date_selection_get(widget);
1145
1146         lt.tm_sec = 0;
1147         lt.tm_min = 0;
1148         lt.tm_hour = 0;
1149         lt.tm_mday = day;
1150         lt.tm_mon = month - 1;
1151         lt.tm_year = year - 1900;
1152         lt.tm_isdst = 0;
1153
1154         return mktime(&lt);
1155 }
1156 #pragma GCC diagnostic pop
1157
1158 /*
1159  *-----------------------------------------------------------------------------
1160  * storing data in a history list with key,data pairs
1161  *-----------------------------------------------------------------------------
1162  */
1163
1164 #define PREF_LIST_MARKER_INT "[INT]:"
1165 #define PREF_LIST_MARKER_DOUBLE "[DOUBLE]:"
1166 #define PREF_LIST_MARKER_STRING "[STRING]:"
1167
1168 static GList *pref_list_find(const gchar *group, const gchar *token)
1169 {
1170         GList *work;
1171         gint l;
1172
1173         l = strlen(token);
1174
1175         work = history_list_get_by_key(group);
1176         while (work)
1177                 {
1178                 auto text = static_cast<const gchar *>(work->data);
1179
1180                 if (strncmp(text, token, l) == 0) return work;
1181
1182                 work = work->next;
1183                 }
1184
1185         return nullptr;
1186 }
1187
1188 static gboolean pref_list_get(const gchar *group, const gchar *key, const gchar *marker, const gchar **result)
1189 {
1190         gchar *token;
1191         GList *work;
1192         gboolean ret;
1193
1194         if (!group || !key || !marker)
1195                 {
1196                 *result = nullptr;
1197                 return FALSE;
1198                 }
1199
1200         token = g_strconcat(key, marker, NULL);
1201
1202         work = pref_list_find(group, token);
1203         if (work)
1204                 {
1205                 *result = static_cast<const gchar *>(work->data) + strlen(token);
1206                 if (strlen(*result) == 0) *result = nullptr;
1207                 ret = TRUE;
1208                 }
1209         else
1210                 {
1211                 *result = nullptr;
1212                 ret = FALSE;
1213                 }
1214
1215         g_free(token);
1216
1217         return ret;
1218 }
1219
1220 static void pref_list_set(const gchar *group, const gchar *key, const gchar *marker, const gchar *text)
1221 {
1222         gchar *token;
1223         gchar *path;
1224         GList *work;
1225
1226         if (!group || !key || !marker) return;
1227
1228         token = g_strconcat(key, marker, NULL);
1229         path = g_strconcat(token, text, NULL);
1230
1231         work = pref_list_find(group, token);
1232         if (work)
1233                 {
1234                 auto old_path = static_cast<gchar *>(work->data);
1235
1236                 if (text)
1237                         {
1238                         work->data = path;
1239                         path = nullptr;
1240
1241                         g_free(old_path);
1242                         }
1243                 else
1244                         {
1245                         history_list_item_remove(group, old_path);
1246                         }
1247                 }
1248         else if (text)
1249                 {
1250                 history_list_add_to_key(group, path, 0);
1251                 }
1252
1253         g_free(path);
1254         g_free(token);
1255 }
1256
1257 void pref_list_int_set(const gchar *group, const gchar *key, gint value)
1258 {
1259         gchar *text;
1260
1261         text = g_strdup_printf("%d", value);
1262         pref_list_set(group, key, PREF_LIST_MARKER_INT, text);
1263         g_free(text);
1264 }
1265
1266 gboolean pref_list_int_get(const gchar *group, const gchar *key, gint *result)
1267 {
1268         const gchar *text;
1269
1270         if (!group || !key)
1271                 {
1272                 *result = 0;
1273                 return FALSE;
1274                 }
1275
1276         if (pref_list_get(group, key, PREF_LIST_MARKER_INT, &text) && text)
1277                 {
1278                 *result = static_cast<gint>(strtol(text, nullptr, 10));
1279                 return TRUE;
1280                 }
1281
1282         *result = 0;
1283         return FALSE;
1284 }
1285
1286 #pragma GCC diagnostic push
1287 #pragma GCC diagnostic ignored "-Wunused-function"
1288 void pref_list_double_set_unused(const gchar *group, const gchar *key, gdouble value)
1289 {
1290         gchar text[G_ASCII_DTOSTR_BUF_SIZE];
1291
1292         g_ascii_dtostr(text, sizeof(text), value);
1293         pref_list_set(group, key, PREF_LIST_MARKER_DOUBLE, text);
1294 }
1295
1296 gboolean pref_list_double_get_unused(const gchar *group, const gchar *key, gdouble *result)
1297 {
1298         const gchar *text;
1299
1300         if (!group || !key)
1301                 {
1302                 *result = 0;
1303                 return FALSE;
1304                 }
1305
1306         if (pref_list_get(group, key, PREF_LIST_MARKER_DOUBLE, &text) && text)
1307                 {
1308                 *result = g_ascii_strtod(text, nullptr);
1309                 return TRUE;
1310                 }
1311
1312         *result = 0;
1313         return FALSE;
1314 }
1315
1316 void pref_list_string_set_unused(const gchar *group, const gchar *key, const gchar *value)
1317 {
1318         pref_list_set(group, key, PREF_LIST_MARKER_STRING, value);
1319 }
1320
1321 gboolean pref_list_string_get_unused(const gchar *group, const gchar *key, const gchar **result)
1322 {
1323         return pref_list_get(group, key, PREF_LIST_MARKER_STRING, result);
1324 }
1325 #pragma GCC diagnostic pop
1326
1327 void pref_color_button_set_cb(GtkWidget *widget, gpointer data)
1328 {
1329         auto color = static_cast<GdkRGBA *>(data);
1330
1331         gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(widget), color);
1332 }
1333
1334 GtkWidget *pref_color_button_new(GtkWidget *parent_box, const gchar *title, GdkRGBA *color, GCallback func, gpointer data)
1335 {
1336         GtkWidget *button;
1337
1338         if (color)
1339                 {
1340                 button = gtk_color_button_new_with_rgba(color);
1341                 }
1342         else
1343                 {
1344                 button = gtk_color_button_new();
1345                 }
1346
1347         if (func) g_signal_connect(G_OBJECT(button), "color-set", func, data);
1348
1349         if (title)
1350                 {
1351                 GtkWidget *label;
1352                 GtkWidget *hbox;
1353
1354                 gtk_color_button_set_title(GTK_COLOR_BUTTON(button), title);
1355                 label = gtk_label_new(title);
1356
1357                 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
1358                 gq_gtk_box_pack_start(GTK_BOX(parent_box), hbox, TRUE, TRUE, 0);
1359
1360                 gq_gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
1361                 gq_gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
1362
1363                 gq_gtk_widget_show_all(hbox);
1364                 }
1365         else
1366                 {
1367                 gtk_widget_show(button);
1368                 }
1369
1370         return button;
1371 }
1372
1373 /*
1374  *-----------------------------------------------------------------------------
1375  * text widget
1376  *-----------------------------------------------------------------------------
1377  */
1378
1379 gchar *text_widget_text_pull(GtkWidget *text_widget)
1380 {
1381         if (GTK_IS_TEXT_VIEW(text_widget))
1382                 {
1383                 GtkTextBuffer *buffer;
1384                 GtkTextIter start, end;
1385
1386                 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_widget));
1387                 gtk_text_buffer_get_bounds(buffer, &start, &end);
1388
1389                 return gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1390                 }
1391         else if (GTK_IS_ENTRY(text_widget))
1392                 {
1393                 return g_strdup(gq_gtk_entry_get_text(GTK_ENTRY(text_widget)));
1394                 }
1395         else
1396                 {
1397                 return nullptr;
1398                 }
1399
1400 }
1401
1402 gchar *text_widget_text_pull_selected(GtkWidget *text_widget)
1403 {
1404         if (GTK_IS_TEXT_VIEW(text_widget))
1405                 {
1406                 GtkTextBuffer *buffer;
1407                 GtkTextIter start, end;
1408
1409                 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_widget));
1410                 gtk_text_buffer_get_bounds(buffer, &start, &end);
1411
1412                 if (gtk_text_buffer_get_selection_bounds(buffer, &start, &end))
1413                         {
1414                         gtk_text_iter_set_line_offset(&start, 0);
1415                         gtk_text_iter_forward_to_line_end(&end);
1416                         }
1417
1418                 return gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1419                 }
1420         else if (GTK_IS_ENTRY(text_widget))
1421                 {
1422                 return g_strdup(gq_gtk_entry_get_text(GTK_ENTRY(text_widget)));
1423                 }
1424         else
1425                 {
1426                 return nullptr;
1427                 }
1428 }
1429
1430 static gint simple_sort_cb(gconstpointer a, gconstpointer b)
1431 {
1432         const ActionItem *a_action;
1433         const ActionItem *b_action;
1434
1435         a_action = static_cast<const ActionItem *>(a);
1436         b_action = static_cast<const ActionItem *>(b);
1437
1438         return g_strcmp0(a_action->name, b_action->name);
1439 }
1440
1441 void free_action_items_cb(gpointer data)
1442 {
1443         ActionItem *action_item;
1444
1445         action_item = static_cast<ActionItem *>(data);
1446         g_free((gchar *)action_item->icon_name);
1447         g_free((gchar *)action_item->name);
1448         g_free((gchar *)action_item->label);
1449         g_free(action_item);
1450 }
1451
1452 void action_items_free(GList *list)
1453 {
1454         g_list_free_full(list, free_action_items_cb);
1455 }
1456
1457 /**
1458  * @brief Get a list of menu actions
1459  * @param
1460  * @returns GList ActionItem
1461  *
1462  * Free returned list with action_items_free(list)
1463  *
1464  * The list generated is used in the --remote --action-list command and
1465  * programmable mouse buttons 8 and 9.
1466  */
1467 GList* get_action_items()
1468 {
1469         ActionItem *action_item;
1470         const gchar *accel_path;
1471         gboolean duplicate;
1472         gchar *action_name;
1473         gchar *label;
1474         gchar *tooltip;
1475         GList *actions;
1476         GList *groups;
1477         GList *list_duplicates = nullptr;
1478         GList *list_unique = nullptr;
1479         GList *work1;
1480         GList *work2;
1481         GtkAction *action;
1482         LayoutWindow *lw = nullptr;
1483
1484         if (!layout_valid(&lw))
1485                 {
1486                 return nullptr;
1487                 }
1488
1489         groups = gtk_ui_manager_get_action_groups(lw->ui_manager);
1490         while (groups)
1491                 {
1492                 actions = gtk_action_group_list_actions(GTK_ACTION_GROUP(groups->data));
1493                 while (actions)
1494                         {
1495                         action = GTK_ACTION(actions->data);
1496                         accel_path = gtk_action_get_accel_path(action);
1497
1498                         if (accel_path && gtk_accel_map_lookup_entry(accel_path, nullptr))
1499                                 {
1500                                 g_object_get(action, "tooltip", &tooltip, "label", &label, NULL);
1501
1502                                 action_name = g_path_get_basename(accel_path);
1503
1504                                 /* Menu actions are irrelevant */
1505                                 if (g_strstr_len(action_name, -1, "Menu") == nullptr)
1506                                         {
1507                                         action_item = g_new0(ActionItem, 1);
1508
1509                                         /* .desktop items need the program name, Geeqie menu items need the tooltip */
1510                                         if (g_strstr_len(action_name, -1, ".desktop") == nullptr)
1511                                                 {
1512
1513                                                 /* Tooltips with newlines affect output format */
1514                                                 if (tooltip && (g_strstr_len(tooltip, -1, "\n") == nullptr) )
1515                                                         {
1516                                                         action_item->label = g_strdup(tooltip);
1517                                                         }
1518                                                 else
1519                                                         {
1520                                                         action_item->label = g_strdup(label);
1521                                                         }
1522                                                 }
1523                                         else
1524                                                 {
1525                                                 action_item->label = g_strdup(label);
1526                                                 }
1527
1528                                         action_item->name = action_name;
1529                                         action_item->icon_name = g_strdup(gtk_action_get_stock_id(action));
1530
1531                                         list_duplicates = g_list_prepend(list_duplicates, action_item);
1532                                         }
1533                                 }
1534                         actions = actions->next;
1535                         }
1536
1537                 groups = groups->next;
1538                 }
1539
1540         /* Use the shortest name i.e. ignore -Alt versions. Sort makes the shortest first in the list */
1541         list_duplicates = g_list_sort(list_duplicates, simple_sort_cb);
1542
1543         /* Ignore duplicate entries */
1544         work1 = list_duplicates;
1545         while (work1)
1546                 {
1547                 duplicate = FALSE;
1548                 work2 = list_unique;
1549                 /* The first entry must be unique, list_unique is null so control bypasses the while */
1550                 while (work2)
1551                         {
1552                         if (g_strcmp0(static_cast<ActionItem *>(work2->data)->label, static_cast<ActionItem *>(work1->data)->label) == 0)
1553                                 {
1554                                 duplicate = TRUE;
1555                                 break;
1556                                 }
1557                         work2 = work2->next;
1558                         }
1559
1560                 if (!duplicate)
1561                         {
1562                         action_item = g_new0(ActionItem, 1);
1563                         action_item->name = g_strdup(static_cast<ActionItem *>(work1->data)->name);
1564                         action_item->label = g_strdup(static_cast<ActionItem *>(work1->data)->label);
1565                         action_item->icon_name = g_strdup(static_cast<ActionItem *>(work1->data)->icon_name);
1566                         list_unique = g_list_append(list_unique, action_item);
1567                         }
1568                 work1 = work1->next;
1569                 }
1570
1571         g_list_free_full(list_duplicates, free_action_items_cb);
1572
1573         return list_unique;
1574 }
1575
1576 gboolean defined_mouse_buttons(GtkWidget *, GdkEventButton *event, gpointer data)
1577 {
1578         auto lw = static_cast<LayoutWindow *>(data);
1579         GtkAction *action;
1580         gboolean ret = FALSE;
1581
1582         switch (event->button)
1583                 {
1584                 case MOUSE_BUTTON_8:
1585                         if (options->mouse_button_8)
1586                                 {
1587                                 if (g_strstr_len(options->mouse_button_8, -1, ".desktop") != nullptr)
1588                                         {
1589                                         file_util_start_editor_from_filelist(options->mouse_button_8, layout_selection_list(lw), layout_get_path(lw), lw->window);
1590                                         ret = TRUE;
1591                                         }
1592                                 else
1593                                         {
1594                                         action = gtk_action_group_get_action(lw->action_group, options->mouse_button_8);
1595                                         if (action)
1596                                                 {
1597                                                 gtk_action_activate(action);
1598                                                 }
1599                                         ret = TRUE;
1600                                         }
1601                                 }
1602                         break;
1603                 case MOUSE_BUTTON_9:
1604                         if (options->mouse_button_9)
1605                                 {
1606                                 if (g_strstr_len(options->mouse_button_9, -1, ".desktop") != nullptr)
1607                                         {
1608                                         file_util_start_editor_from_filelist(options->mouse_button_9, layout_selection_list(lw), layout_get_path(lw), lw->window);
1609                                         }
1610                                 else
1611                                         {
1612                                         action = gtk_action_group_get_action(lw->action_group, options->mouse_button_9);
1613                                         ret = TRUE;
1614                                         if (action)
1615                                                 {
1616                                                 gtk_action_activate(action);
1617                                                 }
1618                                         ret = TRUE;
1619                                         }
1620                                 }
1621                         break;
1622                 default:
1623                         break;
1624                 }
1625
1626         return ret;
1627 }
1628
1629 GdkPixbuf *gq_gtk_icon_theme_load_icon_copy(GtkIconTheme *icon_theme, const gchar *icon_name, gint size, GtkIconLookupFlags flags)
1630 {
1631         GError *error = nullptr;
1632         GdkPixbuf *icon = gtk_icon_theme_load_icon(icon_theme, icon_name, size, flags, &error);
1633         if (error) return nullptr;
1634
1635         GdkPixbuf *pixbuf = gdk_pixbuf_copy(icon);
1636         g_object_unref(icon);
1637         return pixbuf;
1638 }
1639
1640 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */