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