Cleanup main.h header
[geeqie.git] / src / ui-bookmark.cc
1 /*
2  * Copyright (C) 2004 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "ui-bookmark.h"
23
24 #include <cstring>
25 #include <memory>
26
27 #include <config.h>
28
29 #include "compat.h"
30 #include "debug.h"
31 #include "history-list.h"
32 #include "intl.h"
33 #include "main-defines.h"
34 #include "misc.h"
35 #include "pixbuf-util.h"
36 #include "typedefs.h"
37 #include "ui-fileops.h"
38 #include "ui-menu.h"
39 #include "ui-misc.h"
40 #include "ui-tabcomp.h"
41 #include "ui-utildlg.h"
42 #include "uri-utils.h"
43
44 /*
45  *-----------------------------------------------------------------------------
46  * bookmarks
47  *-----------------------------------------------------------------------------
48  */
49
50 #define BOOKMARK_DATA_KEY "bookmarkdata"
51 #define MARKER_PATH "[path]"
52 #define MARKER_ICON "[icon]"
53
54 struct BookButtonData;
55
56 struct BookMarkData
57 {
58         GtkWidget *widget;
59         GtkWidget *box;
60         const gchar *key;
61
62         void (*select_func)(const gchar *path, gpointer data);
63         gpointer select_data;
64
65         gboolean no_defaults;
66         gboolean editable;
67         gboolean only_directories;
68
69         BookButtonData *active_button;
70 };
71
72 struct BookButtonData
73 {
74         GtkWidget *button;
75         GtkWidget *image;
76         GtkWidget *label;
77
78         gchar *key;
79         gchar *name;
80         gchar *path;
81         gchar *icon;
82         gchar *parent;
83 };
84
85 struct BookPropData
86 {
87         GtkWidget *name_entry;
88         GtkWidget *path_entry;
89         GtkWidget *icon_entry;
90
91         BookButtonData *bb;
92 };
93
94 enum {
95         TARGET_URI_LIST,
96         TARGET_X_URL,
97         TARGET_TEXT_PLAIN
98 };
99
100 static GtkTargetEntry bookmark_drop_types[] = {
101         { const_cast<gchar *>("text/uri-list"), 0, TARGET_URI_LIST },
102         { const_cast<gchar *>("x-url/http"),    0, TARGET_X_URL },
103         { const_cast<gchar *>("_NETSCAPE_URL"), 0, TARGET_X_URL }
104 };
105 enum {
106         bookmark_drop_types_n = 3
107 };
108
109 static GtkTargetEntry bookmark_drag_types[] = {
110         { const_cast<gchar *>("text/uri-list"), 0, TARGET_URI_LIST },
111         { const_cast<gchar *>("text/plain"),    0, TARGET_TEXT_PLAIN }
112 };
113 enum {
114         bookmark_drag_types_n = 2
115 };
116
117
118 static GList *bookmark_widget_list = nullptr;
119 static GList *bookmark_default_list = nullptr;
120
121
122 static void bookmark_populate_all(const gchar *key);
123
124
125 static BookButtonData *bookmark_from_string(const gchar *text)
126 {
127         BookButtonData *b;
128         const gchar *path_ptr;
129         const gchar *icon_ptr;
130
131         b = g_new0(BookButtonData, 1);
132
133         if (!text)
134                 {
135                 b->name = g_strdup(_("New Bookmark"));
136                 b->path = g_strdup(homedir());
137                 b->key = nullptr;
138                 return b;
139                 }
140
141         b->key = g_strdup(text);
142
143         path_ptr = strstr(text, MARKER_PATH);
144         icon_ptr = strstr(text, MARKER_ICON);
145
146         if (path_ptr && icon_ptr && icon_ptr < path_ptr)
147                 {
148                 log_printf("warning, bookmark icon must be after path\n");
149                 return nullptr;
150                 }
151
152         if (path_ptr)
153                 {
154                 gint l;
155
156                 l = path_ptr - text;
157                 b->name = g_strndup(text, l);
158                 path_ptr += strlen(MARKER_PATH);
159                 if (icon_ptr)
160                         {
161                         l = icon_ptr - path_ptr;
162                         b->path = g_strndup(path_ptr, l);
163                         }
164                 else
165                         {
166                         b->path = g_strdup(path_ptr);
167                         }
168                 }
169         else
170                 {
171                 b->name = g_strdup(text);
172                 b->path = g_strdup("");
173                 }
174
175         if (icon_ptr)
176                 {
177                 icon_ptr += strlen(MARKER_ICON);
178                 b->icon = g_strdup(icon_ptr);
179                 }
180
181         return b;
182 }
183
184 static void bookmark_free(BookButtonData *b)
185 {
186         if (!b) return;
187
188         g_free(b->name);
189         g_free(b->path);
190         g_free(b->icon);
191         g_free(b->key);
192         g_free(b->parent);
193         g_free(b);
194 }
195
196 static gchar *bookmark_string(const gchar *name, const gchar *path, const gchar *icon)
197 {
198         if (!name) name = _("New Bookmark");
199
200         if (icon)
201                 {
202                 return g_strdup_printf("%s" MARKER_PATH "%s" MARKER_ICON "%s", name, path, icon);
203                 }
204
205         return g_strdup_printf("%s" MARKER_PATH "%s", name, path);
206 }
207
208 static void bookmark_select_cb(GtkWidget *button, gpointer data)
209 {
210         auto bm = static_cast<BookMarkData *>(data);
211         BookButtonData *b;
212
213         b = static_cast<BookButtonData *>(g_object_get_data(G_OBJECT(button), "bookbuttondata"));
214         if (!b) return;
215
216         if (bm->select_func) bm->select_func(b->path, bm->select_data);
217 }
218
219 static void bookmark_edit_destroy_cb(GtkWidget *, gpointer data)
220 {
221         auto p = static_cast<BookPropData *>(data);
222
223         bookmark_free(p->bb);
224         g_free(p);
225 }
226
227 static void bookmark_edit_cancel_cb(GenericDialog *, gpointer)
228 {
229 }
230
231 static void bookmark_edit_ok_cb(GenericDialog *, gpointer data)
232 {
233         auto p = static_cast<BookPropData *>(data);
234         const gchar *name;
235         gchar *path;
236         const gchar *icon;
237         gchar *new_string;
238
239         name = gq_gtk_entry_get_text(GTK_ENTRY(p->name_entry));
240         path = remove_trailing_slash(gq_gtk_entry_get_text(GTK_ENTRY(p->path_entry)));
241         icon = gq_gtk_entry_get_text(GTK_ENTRY(p->icon_entry));
242
243         new_string = bookmark_string(name, path, icon);
244
245         if (p->bb->key)
246                 {
247                 history_list_item_change(p->bb->parent, p->bb->key, new_string);
248                 }
249         else
250                 {
251                 history_list_add_to_key(p->bb->parent, new_string, 0);
252                 }
253
254         if (path && strlen(path) > 0) tab_completion_append_to_history(p->path_entry, path);
255         if (icon && strlen(icon) > 0) tab_completion_append_to_history(p->icon_entry, icon);
256
257         g_free(path);
258         g_free(new_string);
259
260         bookmark_populate_all(p->bb->parent);
261 }
262
263 /* simply pass NULL for text to turn this into a 'new bookmark' dialog */
264
265 static void bookmark_edit(const gchar *key, const gchar *text, GtkWidget *parent)
266 {
267         BookPropData *p;
268         GenericDialog *gd;
269         GtkWidget *table;
270         GtkWidget *label;
271         const gchar *icon;
272
273         if (!key) key = "bookmarks";
274
275         p = g_new0(BookPropData, 1);
276
277         p->bb = bookmark_from_string(text);
278         p->bb->parent = g_strdup(key);
279
280         gd = generic_dialog_new(_("Edit Bookmark"), "bookmark_edit",
281                                 parent, TRUE,
282                                 bookmark_edit_cancel_cb, p);
283         g_signal_connect(G_OBJECT(gd->dialog), "destroy",
284                          G_CALLBACK(bookmark_edit_destroy_cb), p);
285
286         generic_dialog_add_message(gd, nullptr, _("Edit Bookmark"), nullptr, FALSE);
287
288         generic_dialog_add_button(gd, GQ_ICON_OK, "OK",
289                                   bookmark_edit_ok_cb, TRUE);
290
291         table = pref_table_new(gd->vbox, 3, 2, FALSE, TRUE);
292         pref_table_label(table, 0, 0, _("Name:"), GTK_ALIGN_END);
293
294         p->name_entry = gtk_entry_new();
295         gtk_widget_set_size_request(p->name_entry, 300, -1);
296         if (p->bb->name) gq_gtk_entry_set_text(GTK_ENTRY(p->name_entry), p->bb->name);
297         gq_gtk_grid_attach_default(GTK_GRID(table), p->name_entry, 1, 2, 0, 1);
298         generic_dialog_attach_default(gd, p->name_entry);
299         gtk_widget_show(p->name_entry);
300
301         pref_table_label(table, 0, 1, _("Path:"), GTK_ALIGN_END);
302
303         label = tab_completion_new_with_history(&p->path_entry, p->bb->path,
304                                                 "bookmark_path", -1, nullptr, nullptr);
305         tab_completion_add_select_button(p->path_entry, nullptr, TRUE);
306         gq_gtk_grid_attach_default(GTK_GRID(table), label, 1, 2, 1, 2);
307         generic_dialog_attach_default(gd, p->path_entry);
308         gtk_widget_show(label);
309
310         pref_table_label(table, 0, 2, _("Icon:"), GTK_ALIGN_END);
311
312         icon = p->bb->icon;
313         if (!icon) icon = "";
314         label = tab_completion_new_with_history(&p->icon_entry, icon,
315                                                 "bookmark_icons", -1, nullptr, nullptr);
316         tab_completion_add_select_button(p->icon_entry, _("Select icon"), FALSE);
317         gq_gtk_grid_attach_default(GTK_GRID(table), label, 1, 2, 2, 3);
318         generic_dialog_attach_default(gd, p->icon_entry);
319         gtk_widget_show(label);
320
321         gtk_widget_show(gd->dialog);
322 }
323
324 static void bookmark_move(BookMarkData *bm, GtkWidget *button, gint direction)
325 {
326         BookButtonData *b;
327         gint p;
328         GList *list;
329         const gchar *key_holder;
330
331         if (!bm->editable) return;
332
333         b = static_cast<BookButtonData *>(g_object_get_data(G_OBJECT(button), "bookbuttondata"));
334         if (!b) return;
335
336         list = gtk_container_get_children(GTK_CONTAINER(bm->box));
337         p = g_list_index(list, button);
338         g_list_free(list);
339
340         if (p < 0 || p + direction < 0) return;
341
342         key_holder = bm->key;
343         bm->key = "_TEMPHOLDER";
344         history_list_item_move(key_holder, b->key, -direction);
345         bookmark_populate_all(key_holder);
346         bm->key = key_holder;
347
348         gtk_box_reorder_child(GTK_BOX(bm->box), button, p + direction);
349 }
350
351 static void bookmark_menu_prop_cb(GtkWidget *widget, gpointer data)
352 {
353         auto bm = static_cast<BookMarkData *>(data);
354
355         if (!bm->active_button) return;
356
357         bookmark_edit(bm->key, bm->active_button->key, widget);
358 }
359
360 static void bookmark_menu_move(BookMarkData *bm, gint direction)
361 {
362         if (!bm->active_button) return;
363
364         bookmark_move(bm, bm->active_button->button, direction);
365 }
366
367 static void bookmark_menu_up_cb(GtkWidget *, gpointer data)
368 {
369         bookmark_menu_move(static_cast<BookMarkData *>(data), -1);
370 }
371
372 static void bookmark_menu_down_cb(GtkWidget *, gpointer data)
373 {
374         bookmark_menu_move(static_cast<BookMarkData *>(data), 1);
375 }
376
377 static void bookmark_menu_remove_cb(GtkWidget *, gpointer data)
378 {
379         auto bm = static_cast<BookMarkData *>(data);
380
381         if (!bm->active_button) return;
382
383         history_list_item_remove(bm->key, bm->active_button->key);
384         bookmark_populate_all(bm->key);
385 }
386
387 static void bookmark_menu_popup(BookMarkData *bm, GtkWidget *button, gint, guint32, gboolean local)
388 {
389         GtkWidget *menu;
390         BookButtonData *b;
391
392         b = static_cast<BookButtonData *>(g_object_get_data(G_OBJECT(button), "bookbuttondata"));
393         if (!b) return;
394
395         bm->active_button = b;
396
397         menu = popup_menu_short_lived();
398         menu_item_add_icon_sensitive(menu, _("_Properties..."), PIXBUF_INLINE_ICON_PROPERTIES, bm->editable,
399                       G_CALLBACK(bookmark_menu_prop_cb), bm);
400         menu_item_add_icon_sensitive(menu, _("Move _up"), GQ_ICON_GO_UP, bm->editable,
401                       G_CALLBACK(bookmark_menu_up_cb), bm);
402         menu_item_add_icon_sensitive(menu, _("Move _down"), GQ_ICON_GO_DOWN, bm->editable,
403                       G_CALLBACK(bookmark_menu_down_cb), bm);
404         menu_item_add_icon_sensitive(menu, _("_Remove"), GQ_ICON_REMOVE, bm->editable,
405                       G_CALLBACK(bookmark_menu_remove_cb), bm);
406
407         if (local)
408                 {
409                 gtk_menu_popup_at_widget(GTK_MENU(menu), button, GDK_GRAVITY_NORTH_EAST, GDK_GRAVITY_CENTER, nullptr);
410                 }
411         else
412                 {
413                 gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr);
414                 }
415 }
416
417 static gboolean bookmark_press_cb(GtkWidget *button, GdkEventButton *event, gpointer data)
418 {
419         auto bm = static_cast<BookMarkData *>(data);
420
421         if (event->button != MOUSE_BUTTON_RIGHT) return FALSE;
422
423         bookmark_menu_popup(bm, button, event->button, event->time, FALSE);
424
425         return TRUE;
426 }
427
428 static gboolean bookmark_keypress_cb(GtkWidget *button, GdkEventKey *event, gpointer data)
429 {
430         auto bm = static_cast<BookMarkData *>(data);
431
432         switch (event->keyval)
433                 {
434                 case GDK_KEY_F10:
435                         if (!(event->state & GDK_CONTROL_MASK)) return FALSE;
436                         /* fall through */
437                 case GDK_KEY_Menu:
438                         bookmark_menu_popup(bm, button, 0, event->time, TRUE);
439                         return TRUE;
440                         break;
441                 case GDK_KEY_Up:
442                         if (event->state & GDK_SHIFT_MASK)
443                                 {
444                                 bookmark_move(bm, button, -1);
445                                 return TRUE;
446                                 }
447                         break;
448                 case GDK_KEY_Down:
449                         if (event->state & GDK_SHIFT_MASK)
450                                 {
451                                 bookmark_move(bm, button, 1);
452                                 return TRUE;
453                                 }
454                         break;
455                 }
456
457         return FALSE;
458 }
459
460 static void bookmark_drag_set_data(GtkWidget *button,
461                                    GdkDragContext *context, GtkSelectionData *selection_data,
462                                    guint, guint, gpointer data)
463 {
464         auto bm = static_cast<BookMarkData *>(data);
465         BookButtonData *b;
466         GList *list = nullptr;
467
468         return;
469         if (gdk_drag_context_get_dest_window(context) == gtk_widget_get_window(bm->widget)) return;
470
471         b = static_cast<BookButtonData *>(g_object_get_data(G_OBJECT(button), "bookbuttondata"));
472         if (!b) return;
473
474         list = g_list_append(list, b->path);
475
476         gchar **uris = uris_from_pathlist(list);
477         gboolean ret = gtk_selection_data_set_uris(selection_data, uris);
478         if (!ret)
479                 {
480                 char *str = g_strjoinv("\r\n", uris);
481                 ret = gtk_selection_data_set_text(selection_data, str, -1);
482                 g_free(str);
483                 }
484
485         g_strfreev(uris);
486         g_list_free(list);
487 }
488
489 static void bookmark_drag_begin(GtkWidget *button, GdkDragContext *context, gpointer)
490 {
491         GdkPixbuf *pixbuf;
492         GdkModifierType mask;
493         gint x;
494         gint y;
495         GtkAllocation allocation;
496         GdkSeat *seat;
497         GdkDevice *device;
498
499         gtk_widget_get_allocation(button, &allocation);
500
501         pixbuf = gdk_pixbuf_get_from_window(gtk_widget_get_window(button),
502                                             allocation.x, allocation.y,
503                                             allocation.width, allocation.height);
504         seat = gdk_display_get_default_seat(gdk_window_get_display(gtk_widget_get_window(button)));
505         device = gdk_seat_get_pointer(seat);
506         gdk_window_get_device_position(gtk_widget_get_window(button), device, &x, &y, &mask);
507
508         gtk_drag_set_icon_pixbuf(context, pixbuf,
509                                  x - allocation.x, y - allocation.y);
510         g_object_unref(pixbuf);
511 }
512
513 static gboolean bookmark_path_tooltip_cb(GtkWidget *button, gpointer)
514 {
515         BookButtonData *b;
516
517         b = static_cast<BookButtonData *>(g_object_get_data(G_OBJECT(button), "bookbuttondata"));
518         gtk_widget_set_tooltip_text(GTK_WIDGET(button), b->path);
519
520         return FALSE;
521 }
522
523 static void bookmark_populate(BookMarkData *bm)
524 {
525         GtkBox *box;
526         GList *work;
527         GList *children;
528
529         box = GTK_BOX(bm->box);
530         children = gtk_container_get_children(GTK_CONTAINER(box));
531         work = children;
532         while (work)
533                 {
534                 GtkWidget *widget = GTK_WIDGET(work->data);
535                 work = work->next;
536                 gq_gtk_widget_destroy(widget);
537                 }
538
539         if (!bm->no_defaults && !history_list_get_by_key(bm->key))
540                 {
541                 gchar *buf;
542                 gchar *path;
543
544                 if (!bookmark_default_list)
545                         {
546                         buf = bookmark_string(_("Home"), homedir(), nullptr);
547                         history_list_add_to_key(bm->key, buf, 0);
548                         g_free(buf);
549
550                         if (g_strcmp0(bm->key, "shortcuts") != 0)
551                                 {
552                                 buf = bookmark_string(".", g_strdup(history_list_find_last_path_by_key("path_list")), nullptr);
553                                 history_list_add_to_key(bm->key, buf, 0);
554                                 g_free(buf);
555                                 }
556
557                         path = g_build_filename(homedir(), "Desktop", NULL);
558                         if (isname(path))
559                                 {
560                                 buf = bookmark_string(_("Desktop"), path, nullptr);
561                                 history_list_add_to_key(bm->key, buf, 0);
562                                 g_free(buf);
563                                 }
564                         g_free(path);
565                         }
566
567                 work = bookmark_default_list;
568                 while (work && work->next)
569                         {
570                         gchar *name;
571
572                         name = static_cast<gchar *>(work->data);
573                         work = work->next;
574                         path = static_cast<gchar *>(work->data);
575                         work = work->next;
576
577                         if (strcmp(name, ".") == 0)
578                                 {
579                                 if (g_strcmp0(bm->key, "shortcuts") != 0)
580                                         {
581                                         buf = bookmark_string(name, g_strdup(history_list_find_last_path_by_key("path_list")), nullptr);
582                                         }
583                                 else
584                                         {
585                                         continue;
586                                         }
587                                 }
588                         else
589                                 {
590                                 buf = bookmark_string(name, path, nullptr);
591                                 }
592                         history_list_add_to_key(bm->key, buf, 0);
593                         g_free(buf);
594                         }
595                 }
596
597         work = history_list_get_by_key(bm->key);
598         work = g_list_last(work);
599         while (work)
600                 {
601                 BookButtonData *b;
602
603                 b = bookmark_from_string(static_cast<const gchar *>(work->data));
604                 if (b)
605                         {
606                         if (strcmp(b->name, ".") == 0)
607                                 {
608                                 gchar *buf;
609
610                                 b->path = g_strdup(history_list_find_last_path_by_key("path_list"));
611                                 buf = bookmark_string(".", b->path, b->icon);
612                                 history_list_item_change("bookmarks", b->key, buf);
613                                 b->key = g_strdup(buf);
614                                 g_free(buf);
615                                 }
616                         GtkWidget *box;
617
618                         b->button = gtk_button_new();
619                         gtk_button_set_relief(GTK_BUTTON(b->button), GTK_RELIEF_NONE);
620                         gq_gtk_box_pack_start(GTK_BOX(bm->box), b->button, FALSE, FALSE, 0);
621                         gtk_widget_show(b->button);
622
623                         g_object_set_data_full(G_OBJECT(b->button), "bookbuttondata",
624                                                b, reinterpret_cast<GDestroyNotify>(bookmark_free));
625
626                         box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PREF_PAD_BUTTON_GAP);
627                         gq_gtk_container_add(GTK_WIDGET(b->button), box);
628                         gtk_widget_show(box);
629
630                         if (b->icon)
631                                 {
632                                 GdkPixbuf *pixbuf = nullptr;
633                                 gchar *iconl;
634
635                                 iconl = path_from_utf8(b->icon);
636                                 pixbuf = gdk_pixbuf_new_from_file(iconl, nullptr);
637
638                                 if (isfile(b->icon))
639                                         {
640                                         pixbuf = gdk_pixbuf_new_from_file(iconl, nullptr);
641                                         }
642                                 else
643                                         {
644                                         gint w;
645                                         gint h;
646
647                                         w = h = 16;
648                                         gtk_icon_size_lookup(GTK_ICON_SIZE_BUTTON, &w, &h);
649
650                                         pixbuf = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), b->icon, w, GTK_ICON_LOOKUP_NO_SVG, nullptr);
651                                         }
652
653                                 g_free(iconl);
654                                 if (pixbuf)
655                                         {
656                                         GdkPixbuf *scaled;
657                                         gint w;
658                                         gint h;
659
660                                         w = h = 16;
661                                         gtk_icon_size_lookup(GTK_ICON_SIZE_BUTTON, &w, &h);
662
663                                         scaled = gdk_pixbuf_scale_simple(pixbuf, w, h,
664                                                                          GDK_INTERP_BILINEAR);
665                                         b->image = gtk_image_new_from_pixbuf(scaled);
666                                         g_object_unref(scaled);
667                                         g_object_unref(pixbuf);
668                                         }
669                                 else
670                                         {
671                                         b->image = gtk_image_new_from_icon_name(GQ_ICON_DIRECTORY, GTK_ICON_SIZE_BUTTON);
672                                         }
673                                 }
674                         else
675                                 {
676                                 b->image = gtk_image_new_from_icon_name(GQ_ICON_DIRECTORY, GTK_ICON_SIZE_BUTTON);
677                                 }
678                         gq_gtk_box_pack_start(GTK_BOX(box), b->image, FALSE, FALSE, 0);
679                         gtk_widget_show(b->image);
680
681                         b->label = gtk_label_new(b->name);
682                         gq_gtk_box_pack_start(GTK_BOX(box), b->label, FALSE, FALSE, 0);
683                         gtk_widget_show(b->label);
684
685                         g_signal_connect(G_OBJECT(b->button), "clicked",
686                                          G_CALLBACK(bookmark_select_cb), bm);
687                         g_signal_connect(G_OBJECT(b->button), "button_press_event",
688                                          G_CALLBACK(bookmark_press_cb), bm);
689                         g_signal_connect(G_OBJECT(b->button), "key_press_event",
690                                          G_CALLBACK(bookmark_keypress_cb), bm);
691
692                         gtk_drag_source_set(b->button, GDK_BUTTON1_MASK,
693                                             bookmark_drag_types, bookmark_drag_types_n,
694                                             static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
695                         g_signal_connect(G_OBJECT(b->button), "drag_data_get",
696                                          G_CALLBACK(bookmark_drag_set_data), bm);
697                         g_signal_connect(G_OBJECT(b->button), "drag_begin",
698                                          G_CALLBACK(bookmark_drag_begin), bm);
699
700                         gtk_widget_set_has_tooltip(GTK_WIDGET(b->button), TRUE);
701                         g_signal_connect(G_OBJECT(b->button), "query_tooltip", G_CALLBACK(bookmark_path_tooltip_cb), bm);
702                         }
703
704                 work = work->prev;
705                 }
706 }
707
708 static void bookmark_populate_all(const gchar *key)
709 {
710         GList *work;
711
712         if (!key) return;
713
714         work = bookmark_widget_list;
715         while (work)
716                 {
717                 BookMarkData *bm;
718
719                 bm = static_cast<BookMarkData *>(work->data);
720                 work = work->next;
721
722                 if (strcmp(bm->key, key) == 0)
723                         {
724                         bookmark_populate(bm);
725                         }
726                 }
727 }
728
729 static void bookmark_dnd_get_data(GtkWidget *, GdkDragContext *,
730                                   gint, gint,
731                                   GtkSelectionData *selection_data, guint,
732                                   guint, gpointer data)
733 {
734         auto bm = static_cast<BookMarkData *>(data);
735         GList *list = nullptr;
736         GList *errors = nullptr;
737         GList *work;
738         gchar *real_path;
739         gchar **uris;
740
741         if (!bm->editable) return;
742
743         uris = gtk_selection_data_get_uris(selection_data);
744         if (uris)
745                 {
746                 list = uri_pathlist_from_uris(uris, &errors);
747                 if(errors)
748                         {
749                         warning_dialog_dnd_uri_error(errors);
750                         g_list_free_full(errors, g_free);
751                         }
752                 g_strfreev(uris);
753
754                 work = list;
755                 while (work)
756                         {
757                         auto path = static_cast<gchar *>(work->data);
758                         gchar *buf;
759
760                         work = work->next;
761
762                         if (bm->only_directories && !isdir(path)) continue;
763
764                         real_path = realpath(path, nullptr);
765
766                         if (strstr(real_path, get_collections_dir()) && isfile(path))
767                                 {
768                                 buf = bookmark_string(filename_from_path(path), path, "gq-icon-collection");
769                                 }
770                         else if (isfile(path))
771                                 {
772                                 buf = bookmark_string(filename_from_path(path), path, GQ_ICON_FILE);
773                                 }
774                         else
775                                 {
776                                 buf = bookmark_string(filename_from_path(path), path, nullptr);
777                                 }
778                         history_list_add_to_key(bm->key, buf, 0);
779                         g_free(buf);
780                         g_free(real_path);
781                         }
782
783                 g_list_free_full(list, g_free);
784
785                 bookmark_populate_all(bm->key);
786                 }
787 }
788
789 static void bookmark_list_destroy(GtkWidget *, gpointer data)
790 {
791         auto bm = static_cast<BookMarkData *>(data);
792
793         bookmark_widget_list = g_list_remove(bookmark_widget_list, bm);
794
795         g_free(const_cast<gchar *>(bm->key));
796         g_free(bm);
797 }
798
799 GtkWidget *bookmark_list_new(const gchar *key,
800                              void (*select_func)(const gchar *path, gpointer data), gpointer select_data)
801 {
802         GtkWidget *scrolled;
803         BookMarkData *bm;
804
805         if (!key) key = "bookmarks";
806
807         bm = g_new0(BookMarkData, 1);
808         bm->key = g_strdup(key);
809
810         bm->select_func = select_func;
811         bm->select_data = select_data;
812
813         bm->no_defaults = FALSE;
814         bm->editable = TRUE;
815         bm->only_directories = FALSE;
816
817         scrolled = gq_gtk_scrolled_window_new(nullptr, nullptr);
818
819         PangoLayout *layout;
820         gint width;
821         gint height;
822
823         layout = gtk_widget_create_pango_layout(GTK_WIDGET(scrolled), "reasonable width");
824         pango_layout_get_pixel_size(layout, &width, &height);
825         gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(scrolled), width);
826         g_object_unref(layout);
827
828         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
829
830         bm->box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
831         gq_gtk_container_add(GTK_WIDGET(scrolled), bm->box);
832         gtk_widget_show(bm->box);
833
834         bookmark_populate(bm);
835
836         g_signal_connect(G_OBJECT(bm->box), "destroy",
837                          G_CALLBACK(bookmark_list_destroy), bm);
838         g_object_set_data(G_OBJECT(bm->box), BOOKMARK_DATA_KEY, bm);
839         g_object_set_data(G_OBJECT(scrolled), BOOKMARK_DATA_KEY, bm);
840         bm->widget = scrolled;
841
842         gtk_drag_dest_set(scrolled,
843                           static_cast<GtkDestDefaults>(GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP),
844                           bookmark_drop_types, bookmark_drop_types_n,
845                           static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
846         g_signal_connect(G_OBJECT(scrolled), "drag_data_received",
847                          G_CALLBACK(bookmark_dnd_get_data), bm);
848
849         bookmark_widget_list = g_list_append(bookmark_widget_list, bm);
850
851         return scrolled;
852 }
853
854 void bookmark_list_set_key(GtkWidget *list, const gchar *key)
855 {
856         BookMarkData *bm;
857
858         if (!list || !key) return;
859
860         bm = static_cast<BookMarkData *>(g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY));
861         if (!bm) return;
862
863         if (bm->key && strcmp(bm->key, key) == 0) return;
864
865         g_free(const_cast<gchar *>(bm->key));
866         bm->key = g_strdup(key);
867
868         bookmark_populate(bm);
869 }
870
871 void bookmark_list_set_no_defaults(GtkWidget *list, gboolean no_defaults)
872 {
873         BookMarkData *bm;
874
875         bm = static_cast<BookMarkData *>(g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY));
876         if (!bm) return;
877
878         bm->no_defaults = no_defaults;
879 }
880
881 void bookmark_list_set_editable(GtkWidget *list, gboolean editable)
882 {
883         BookMarkData *bm;
884
885         bm = static_cast<BookMarkData *>(g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY));
886         if (!bm) return;
887
888         bm->editable = editable;
889 }
890
891 void bookmark_list_set_only_directories(GtkWidget *list, gboolean only_directories)
892 {
893         BookMarkData *bm;
894
895         bm = static_cast<BookMarkData *>(g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY));
896         if (!bm) return;
897
898         bm->only_directories = only_directories;
899 }
900
901 void bookmark_list_add(GtkWidget *list, const gchar *name, const gchar *path)
902 {
903         BookMarkData *bm;
904         gchar *real_path;
905
906         bm = static_cast<BookMarkData *>(g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY));
907         if (!bm) return;
908
909         std::unique_ptr<gchar, decltype(&g_free)> buf(bookmark_string(name, path, nullptr), g_free);
910         real_path = realpath(path, nullptr);
911
912         if (strstr(real_path, get_collections_dir()) && isfile(path))
913                 {
914                 buf.reset(bookmark_string(name, path, "gq-icon-collection"));
915                 }
916         else
917                 {
918                 if (isfile(path))
919                         {
920                         buf.reset(bookmark_string(name, path, GQ_ICON_FILE));
921                         }
922                 else
923                         {
924                         buf.reset(bookmark_string(name, path, nullptr));
925                         }
926                 }
927
928         history_list_add_to_key(bm->key, buf.get(), 0);
929         g_free(real_path);
930
931         bookmark_populate_all(bm->key);
932 }
933
934 void bookmark_add_default(const gchar *name, const gchar *path)
935 {
936         if (!name || !path) return;
937         bookmark_default_list = g_list_append(bookmark_default_list, g_strdup(name));
938         bookmark_default_list = g_list_append(bookmark_default_list, g_strdup(path));
939 }
940
941 /*
942  *-----------------------------------------------------------------------------
943  * combo with history key
944  *-----------------------------------------------------------------------------
945  */
946
947 struct HistoryComboData
948 {
949         GtkWidget *combo;
950         GtkWidget *entry;
951         gchar *history_key;
952         gint history_levels;
953 };
954
955 static void history_combo_destroy(GtkWidget *, gpointer data)
956 {
957         auto hc = static_cast<HistoryComboData *>(data);
958
959         g_free(hc->history_key);
960         g_free(data);
961 }
962
963 /* if text is NULL, entry is set to the most recent item */
964 GtkWidget *history_combo_new(GtkWidget **entry, const gchar *text,
965                              const gchar *history_key, gint max_levels)
966 {
967         HistoryComboData *hc;
968         GList *work;
969         gint n = 0;
970
971         hc = g_new0(HistoryComboData, 1);
972         hc->history_key = g_strdup(history_key);
973         hc->history_levels = max_levels;
974
975         hc->combo = gtk_combo_box_text_new_with_entry();
976
977         hc->entry = gtk_bin_get_child(GTK_BIN(hc->combo));
978
979         g_object_set_data(G_OBJECT(hc->combo), "history_combo_data", hc);
980         g_object_set_data(G_OBJECT(hc->entry), "history_combo_data", hc);
981         g_signal_connect(G_OBJECT(hc->combo), "destroy",
982                          G_CALLBACK(history_combo_destroy), hc);
983
984         work = history_list_get_by_key(hc->history_key);
985         while (work)
986                 {
987                 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(hc->combo), static_cast<gchar *>(work->data));
988                 work = work->next;
989                 n++;
990                 }
991
992         if (text)
993                 {
994                 gq_gtk_entry_set_text(GTK_ENTRY(hc->entry), text);
995                 }
996         else if (n > 0)
997                 {
998                 gtk_combo_box_set_active(GTK_COMBO_BOX(hc->combo), 0);
999                 }
1000
1001         if (entry) *entry = hc->entry;
1002         return hc->combo;
1003 }
1004
1005 /* if text is NULL, current entry text is used
1006  * widget can be the combo or entry widget
1007  */
1008 void history_combo_append_history(GtkWidget *widget, const gchar *text)
1009 {
1010         HistoryComboData *hc;
1011         gchar *new_text;
1012
1013         hc = static_cast<HistoryComboData *>(g_object_get_data(G_OBJECT(widget), "history_combo_data"));
1014         if (!hc)
1015                 {
1016                 log_printf("widget is not a history combo\n");
1017                 return;
1018                 }
1019
1020         if (text)
1021                 {
1022                 new_text = g_strdup(text);
1023                 }
1024         else
1025                 {
1026                 new_text = g_strdup(gq_gtk_entry_get_text(GTK_ENTRY(hc->entry)));
1027                 }
1028
1029         if (new_text && strlen(new_text) > 0)
1030                 {
1031                 GtkTreeModel *store;
1032                 GList *work;
1033
1034                 history_list_add_to_key(hc->history_key, new_text, hc->history_levels);
1035
1036                 gtk_combo_box_set_active(GTK_COMBO_BOX(hc->combo), -1);
1037
1038                 store = gtk_combo_box_get_model(GTK_COMBO_BOX(hc->combo));
1039                 gtk_list_store_clear(GTK_LIST_STORE(store));
1040
1041                 work = history_list_get_by_key(hc->history_key);
1042                 while (work)
1043                         {
1044                         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(hc->combo), static_cast<gchar *>(work->data));
1045                         work = work->next;
1046                         }
1047                 }
1048
1049         g_free(new_text);
1050 }
1051 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */