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