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