8e394bb91c808e3e9ccd10715b98ee4a625ea8d2
[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;
488         gint y;
489         GtkAllocation allocation;
490         GdkSeat *seat;
491         GdkDevice *device;
492
493         gtk_widget_get_allocation(button, &allocation);
494
495         pixbuf = gdk_pixbuf_get_from_window(gtk_widget_get_window(button),
496                                             allocation.x, allocation.y,
497                                             allocation.width, allocation.height);
498         seat = gdk_display_get_default_seat(gdk_window_get_display(gtk_widget_get_window(button)));
499         device = gdk_seat_get_pointer(seat);
500         gdk_window_get_device_position(gtk_widget_get_window(button), device, &x, &y, &mask);
501
502         gtk_drag_set_icon_pixbuf(context, pixbuf,
503                                  x - allocation.x, y - allocation.y);
504         g_object_unref(pixbuf);
505 }
506
507 static gboolean bookmark_path_tooltip_cb(GtkWidget *button, gpointer)
508 {
509         BookButtonData *b;
510
511         b = static_cast<BookButtonData *>(g_object_get_data(G_OBJECT(button), "bookbuttondata"));
512         gtk_widget_set_tooltip_text(GTK_WIDGET(button), b->path);
513
514         return FALSE;
515 }
516
517 static void bookmark_populate(BookMarkData *bm)
518 {
519         GtkBox *box;
520         GList *work;
521         GList *children;
522
523         box = GTK_BOX(bm->box);
524         children = gtk_container_get_children(GTK_CONTAINER(box));
525         work = children;
526         while (work)
527                 {
528                 GtkWidget *widget = GTK_WIDGET(work->data);
529                 work = work->next;
530                 gq_gtk_widget_destroy(widget);
531                 }
532
533         if (!bm->no_defaults && !history_list_get_by_key(bm->key))
534                 {
535                 gchar *buf;
536                 gchar *path;
537
538                 if (!bookmark_default_list)
539                         {
540                         buf = bookmark_string(_("Home"), homedir(), nullptr);
541                         history_list_add_to_key(bm->key, buf, 0);
542                         g_free(buf);
543
544                         if (g_strcmp0(bm->key, "shortcuts") != 0)
545                                 {
546                                 buf = bookmark_string(".", g_strdup(history_list_find_last_path_by_key("path_list")), nullptr);
547                                 history_list_add_to_key(bm->key, buf, 0);
548                                 g_free(buf);
549                                 }
550
551                         path = g_build_filename(homedir(), "Desktop", NULL);
552                         if (isname(path))
553                                 {
554                                 buf = bookmark_string(_("Desktop"), path, nullptr);
555                                 history_list_add_to_key(bm->key, buf, 0);
556                                 g_free(buf);
557                                 }
558                         g_free(path);
559                         }
560
561                 work = bookmark_default_list;
562                 while (work && work->next)
563                         {
564                         gchar *name;
565
566                         name = static_cast<gchar *>(work->data);
567                         work = work->next;
568                         path = static_cast<gchar *>(work->data);
569                         work = work->next;
570
571                         if (strcmp(name, ".") == 0)
572                                 {
573                                 if (g_strcmp0(bm->key, "shortcuts") != 0)
574                                         {
575                                         buf = bookmark_string(name, g_strdup(history_list_find_last_path_by_key("path_list")), nullptr);
576                                         }
577                                 else
578                                         {
579                                         continue;
580                                         }
581                                 }
582                         else
583                                 {
584                                 buf = bookmark_string(name, path, nullptr);
585                                 }
586                         history_list_add_to_key(bm->key, buf, 0);
587                         g_free(buf);
588                         }
589                 }
590
591         work = history_list_get_by_key(bm->key);
592         work = g_list_last(work);
593         while (work)
594                 {
595                 BookButtonData *b;
596
597                 b = bookmark_from_string(static_cast<const gchar *>(work->data));
598                 if (b)
599                         {
600                         if (strcmp(b->name, ".") == 0)
601                                 {
602                                 gchar *buf;
603
604                                 b->path = g_strdup(history_list_find_last_path_by_key("path_list"));
605                                 buf = bookmark_string(".", b->path, b->icon);
606                                 history_list_item_change("bookmarks", b->key, buf);
607                                 b->key = g_strdup(buf);
608                                 g_free(buf);
609                                 }
610                         GtkWidget *box;
611
612                         b->button = gtk_button_new();
613                         gtk_button_set_relief(GTK_BUTTON(b->button), GTK_RELIEF_NONE);
614                         gq_gtk_box_pack_start(GTK_BOX(bm->box), b->button, FALSE, FALSE, 0);
615                         gtk_widget_show(b->button);
616
617                         g_object_set_data_full(G_OBJECT(b->button), "bookbuttondata",
618                                                b, reinterpret_cast<GDestroyNotify>(bookmark_free));
619
620                         box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PREF_PAD_BUTTON_GAP);
621                         gq_gtk_container_add(GTK_WIDGET(b->button), box);
622                         gtk_widget_show(box);
623
624                         if (b->icon)
625                                 {
626                                 GdkPixbuf *pixbuf = nullptr;
627                                 gchar *iconl;
628
629                                 iconl = path_from_utf8(b->icon);
630                                 pixbuf = gdk_pixbuf_new_from_file(iconl, nullptr);
631
632                                 if (isfile(b->icon))
633                                         {
634                                         pixbuf = gdk_pixbuf_new_from_file(iconl, nullptr);
635                                         }
636                                 else
637                                         {
638                                         gint w;
639                                         gint h;
640
641                                         w = h = 16;
642                                         gtk_icon_size_lookup(GTK_ICON_SIZE_BUTTON, &w, &h);
643
644                                         pixbuf = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), b->icon, w, GTK_ICON_LOOKUP_NO_SVG, nullptr);
645                                         }
646
647                                 g_free(iconl);
648                                 if (pixbuf)
649                                         {
650                                         GdkPixbuf *scaled;
651                                         gint w;
652                                         gint h;
653
654                                         w = h = 16;
655                                         gtk_icon_size_lookup(GTK_ICON_SIZE_BUTTON, &w, &h);
656
657                                         scaled = gdk_pixbuf_scale_simple(pixbuf, w, h,
658                                                                          GDK_INTERP_BILINEAR);
659                                         b->image = gtk_image_new_from_pixbuf(scaled);
660                                         g_object_unref(scaled);
661                                         g_object_unref(pixbuf);
662                                         }
663                                 else
664                                         {
665                                         b->image = gtk_image_new_from_icon_name(GQ_ICON_DIRECTORY, GTK_ICON_SIZE_BUTTON);
666                                         }
667                                 }
668                         else
669                                 {
670                                 b->image = gtk_image_new_from_icon_name(GQ_ICON_DIRECTORY, GTK_ICON_SIZE_BUTTON);
671                                 }
672                         gq_gtk_box_pack_start(GTK_BOX(box), b->image, FALSE, FALSE, 0);
673                         gtk_widget_show(b->image);
674
675                         b->label = gtk_label_new(b->name);
676                         gq_gtk_box_pack_start(GTK_BOX(box), b->label, FALSE, FALSE, 0);
677                         gtk_widget_show(b->label);
678
679                         g_signal_connect(G_OBJECT(b->button), "clicked",
680                                          G_CALLBACK(bookmark_select_cb), bm);
681                         g_signal_connect(G_OBJECT(b->button), "button_press_event",
682                                          G_CALLBACK(bookmark_press_cb), bm);
683                         g_signal_connect(G_OBJECT(b->button), "key_press_event",
684                                          G_CALLBACK(bookmark_keypress_cb), bm);
685
686                         gtk_drag_source_set(b->button, GDK_BUTTON1_MASK,
687                                             bookmark_drag_types, bookmark_drag_types_n,
688                                             static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
689                         g_signal_connect(G_OBJECT(b->button), "drag_data_get",
690                                          G_CALLBACK(bookmark_drag_set_data), bm);
691                         g_signal_connect(G_OBJECT(b->button), "drag_begin",
692                                          G_CALLBACK(bookmark_drag_begin), bm);
693
694                         gtk_widget_set_has_tooltip(GTK_WIDGET(b->button), TRUE);
695                         g_signal_connect(G_OBJECT(b->button), "query_tooltip", G_CALLBACK(bookmark_path_tooltip_cb), bm);
696                         }
697
698                 work = work->prev;
699                 }
700 }
701
702 static void bookmark_populate_all(const gchar *key)
703 {
704         GList *work;
705
706         if (!key) return;
707
708         work = bookmark_widget_list;
709         while (work)
710                 {
711                 BookMarkData *bm;
712
713                 bm = static_cast<BookMarkData *>(work->data);
714                 work = work->next;
715
716                 if (strcmp(bm->key, key) == 0)
717                         {
718                         bookmark_populate(bm);
719                         }
720                 }
721 }
722
723 static void bookmark_dnd_get_data(GtkWidget *, GdkDragContext *,
724                                   gint, gint,
725                                   GtkSelectionData *selection_data, guint,
726                                   guint, gpointer data)
727 {
728         auto bm = static_cast<BookMarkData *>(data);
729         GList *list = nullptr;
730         GList *errors = nullptr;
731         GList *work;
732         gchar *real_path;
733         gchar **uris;
734
735         if (!bm->editable) return;
736
737         uris = gtk_selection_data_get_uris(selection_data);
738         if (uris)
739                 {
740                 list = uri_pathlist_from_uris(uris, &errors);
741                 if(errors)
742                         {
743                         warning_dialog_dnd_uri_error(errors);
744                         g_list_free_full(errors, g_free);
745                         }
746                 g_strfreev(uris);
747
748                 work = list;
749                 while (work)
750                         {
751                         auto path = static_cast<gchar *>(work->data);
752                         gchar *buf;
753
754                         work = work->next;
755
756                         if (bm->only_directories && !isdir(path)) continue;
757
758                         real_path = realpath(path, nullptr);
759
760                         if (strstr(real_path, get_collections_dir()) && isfile(path))
761                                 {
762                                 buf = bookmark_string(filename_from_path(path), path, "gq-icon-collection");
763                                 }
764                         else if (isfile(path))
765                                 {
766                                 buf = bookmark_string(filename_from_path(path), path, GQ_ICON_FILE);
767                                 }
768                         else
769                                 {
770                                 buf = bookmark_string(filename_from_path(path), path, nullptr);
771                                 }
772                         history_list_add_to_key(bm->key, buf, 0);
773                         g_free(buf);
774                         g_free(real_path);
775                         }
776
777                 g_list_free_full(list, g_free);
778
779                 bookmark_populate_all(bm->key);
780                 }
781 }
782
783 static void bookmark_list_destroy(GtkWidget *, gpointer data)
784 {
785         auto bm = static_cast<BookMarkData *>(data);
786
787         bookmark_widget_list = g_list_remove(bookmark_widget_list, bm);
788
789         g_free(const_cast<gchar *>(bm->key));
790         g_free(bm);
791 }
792
793 GtkWidget *bookmark_list_new(const gchar *key,
794                              void (*select_func)(const gchar *path, gpointer data), gpointer select_data)
795 {
796         GtkWidget *scrolled;
797         BookMarkData *bm;
798
799         if (!key) key = "bookmarks";
800
801         bm = g_new0(BookMarkData, 1);
802         bm->key = g_strdup(key);
803
804         bm->select_func = select_func;
805         bm->select_data = select_data;
806
807         bm->no_defaults = FALSE;
808         bm->editable = TRUE;
809         bm->only_directories = FALSE;
810
811         scrolled = gq_gtk_scrolled_window_new(nullptr, nullptr);
812
813         PangoLayout *layout;
814         gint width;
815         gint height;
816
817         layout = gtk_widget_create_pango_layout(GTK_WIDGET(scrolled), "reasonable width");
818         pango_layout_get_pixel_size(layout, &width, &height);
819         gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(scrolled), width);
820         g_object_unref(layout);
821
822         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
823
824         bm->box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
825         gq_gtk_container_add(GTK_WIDGET(scrolled), bm->box);
826         gtk_widget_show(bm->box);
827
828         bookmark_populate(bm);
829
830         g_signal_connect(G_OBJECT(bm->box), "destroy",
831                          G_CALLBACK(bookmark_list_destroy), bm);
832         g_object_set_data(G_OBJECT(bm->box), BOOKMARK_DATA_KEY, bm);
833         g_object_set_data(G_OBJECT(scrolled), BOOKMARK_DATA_KEY, bm);
834         bm->widget = scrolled;
835
836         gtk_drag_dest_set(scrolled,
837                           static_cast<GtkDestDefaults>(GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP),
838                           bookmark_drop_types, bookmark_drop_types_n,
839                           static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
840         g_signal_connect(G_OBJECT(scrolled), "drag_data_received",
841                          G_CALLBACK(bookmark_dnd_get_data), bm);
842
843         bookmark_widget_list = g_list_append(bookmark_widget_list, bm);
844
845         return scrolled;
846 }
847
848 void bookmark_list_set_key(GtkWidget *list, const gchar *key)
849 {
850         BookMarkData *bm;
851
852         if (!list || !key) return;
853
854         bm = static_cast<BookMarkData *>(g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY));
855         if (!bm) return;
856
857         if (bm->key && strcmp(bm->key, key) == 0) return;
858
859         g_free(const_cast<gchar *>(bm->key));
860         bm->key = g_strdup(key);
861
862         bookmark_populate(bm);
863 }
864
865 void bookmark_list_set_no_defaults(GtkWidget *list, gboolean no_defaults)
866 {
867         BookMarkData *bm;
868
869         bm = static_cast<BookMarkData *>(g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY));
870         if (!bm) return;
871
872         bm->no_defaults = no_defaults;
873 }
874
875 void bookmark_list_set_editable(GtkWidget *list, gboolean editable)
876 {
877         BookMarkData *bm;
878
879         bm = static_cast<BookMarkData *>(g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY));
880         if (!bm) return;
881
882         bm->editable = editable;
883 }
884
885 void bookmark_list_set_only_directories(GtkWidget *list, gboolean only_directories)
886 {
887         BookMarkData *bm;
888
889         bm = static_cast<BookMarkData *>(g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY));
890         if (!bm) return;
891
892         bm->only_directories = only_directories;
893 }
894
895 void bookmark_list_add(GtkWidget *list, const gchar *name, const gchar *path)
896 {
897         BookMarkData *bm;
898         gchar *real_path;
899
900         bm = static_cast<BookMarkData *>(g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY));
901         if (!bm) return;
902
903         std::unique_ptr<gchar, decltype(&g_free)> buf(bookmark_string(name, path, nullptr), g_free);
904         real_path = realpath(path, nullptr);
905
906         if (strstr(real_path, get_collections_dir()) && isfile(path))
907                 {
908                 buf.reset(bookmark_string(name, path, "gq-icon-collection"));
909                 }
910         else
911                 {
912                 if (isfile(path))
913                         {
914                         buf.reset(bookmark_string(name, path, GQ_ICON_FILE));
915                         }
916                 else
917                         {
918                         buf.reset(bookmark_string(name, path, nullptr));
919                         }
920                 }
921
922         history_list_add_to_key(bm->key, buf.get(), 0);
923         g_free(real_path);
924
925         bookmark_populate_all(bm->key);
926 }
927
928 void bookmark_add_default(const gchar *name, const gchar *path)
929 {
930         if (!name || !path) return;
931         bookmark_default_list = g_list_append(bookmark_default_list, g_strdup(name));
932         bookmark_default_list = g_list_append(bookmark_default_list, g_strdup(path));
933 }
934
935 /*
936  *-----------------------------------------------------------------------------
937  * combo with history key
938  *-----------------------------------------------------------------------------
939  */
940
941 struct HistoryComboData
942 {
943         GtkWidget *combo;
944         GtkWidget *entry;
945         gchar *history_key;
946         gint history_levels;
947 };
948
949 static void history_combo_destroy(GtkWidget *, gpointer data)
950 {
951         auto hc = static_cast<HistoryComboData *>(data);
952
953         g_free(hc->history_key);
954         g_free(data);
955 }
956
957 /* if text is NULL, entry is set to the most recent item */
958 GtkWidget *history_combo_new(GtkWidget **entry, const gchar *text,
959                              const gchar *history_key, gint max_levels)
960 {
961         HistoryComboData *hc;
962         GList *work;
963         gint n = 0;
964
965         hc = g_new0(HistoryComboData, 1);
966         hc->history_key = g_strdup(history_key);
967         hc->history_levels = max_levels;
968
969         hc->combo = gtk_combo_box_text_new_with_entry();
970
971         hc->entry = gtk_bin_get_child(GTK_BIN(hc->combo));
972
973         g_object_set_data(G_OBJECT(hc->combo), "history_combo_data", hc);
974         g_object_set_data(G_OBJECT(hc->entry), "history_combo_data", hc);
975         g_signal_connect(G_OBJECT(hc->combo), "destroy",
976                          G_CALLBACK(history_combo_destroy), hc);
977
978         work = history_list_get_by_key(hc->history_key);
979         while (work)
980                 {
981                 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(hc->combo), static_cast<gchar *>(work->data));
982                 work = work->next;
983                 n++;
984                 }
985
986         if (text)
987                 {
988                 gq_gtk_entry_set_text(GTK_ENTRY(hc->entry), text);
989                 }
990         else if (n > 0)
991                 {
992                 gtk_combo_box_set_active(GTK_COMBO_BOX(hc->combo), 0);
993                 }
994
995         if (entry) *entry = hc->entry;
996         return hc->combo;
997 }
998
999 /* if text is NULL, current entry text is used
1000  * widget can be the combo or entry widget
1001  */
1002 void history_combo_append_history(GtkWidget *widget, const gchar *text)
1003 {
1004         HistoryComboData *hc;
1005         gchar *new_text;
1006
1007         hc = static_cast<HistoryComboData *>(g_object_get_data(G_OBJECT(widget), "history_combo_data"));
1008         if (!hc)
1009                 {
1010                 log_printf("widget is not a history combo\n");
1011                 return;
1012                 }
1013
1014         if (text)
1015                 {
1016                 new_text = g_strdup(text);
1017                 }
1018         else
1019                 {
1020                 new_text = g_strdup(gq_gtk_entry_get_text(GTK_ENTRY(hc->entry)));
1021                 }
1022
1023         if (new_text && strlen(new_text) > 0)
1024                 {
1025                 GtkTreeModel *store;
1026                 GList *work;
1027
1028                 history_list_add_to_key(hc->history_key, new_text, hc->history_levels);
1029
1030                 gtk_combo_box_set_active(GTK_COMBO_BOX(hc->combo), -1);
1031
1032                 store = gtk_combo_box_get_model(GTK_COMBO_BOX(hc->combo));
1033                 gtk_list_store_clear(GTK_LIST_STORE(store));
1034
1035                 work = history_list_get_by_key(hc->history_key);
1036                 while (work)
1037                         {
1038                         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(hc->combo), static_cast<gchar *>(work->data));
1039                         work = work->next;
1040                         }
1041                 }
1042
1043         g_free(new_text);
1044 }
1045 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */