Optimize history_list_add_to_key() a bit.
[geeqie.git] / src / ui_bookmark.c
1 /*
2  * (SLIK) SimpLIstic sKin functions
3  * (C) 2004 John Ellis
4  * Copyright (C) 2008 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13 #ifdef HAVE_CONFIG_H
14 #  include "config.h"
15 #endif
16 #include "intl.h"
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21
22 #include <gtk/gtk.h>
23
24 #include <gdk/gdkkeysyms.h> /* for key values */
25
26 #include "main.h"
27 #include "filedata.h"
28
29 #include "secure_save.h"
30 #include "ui_bookmark.h"
31 #include "ui_fileops.h"
32 #include "ui_menu.h"
33 #include "ui_misc.h"
34 #include "ui_utildlg.h"
35 #include "ui_tabcomp.h"
36
37
38 /*
39  *-----------------------------------------------------------------------------
40  * history lists
41  *-----------------------------------------------------------------------------
42  */
43
44 #define HISTORY_DEFAULT_KEY_COUNT 16
45
46
47 typedef struct _HistoryData HistoryData;
48 struct _HistoryData
49 {
50         gchar *key;
51         GList *list;
52 };
53
54 static GList *history_list = NULL;
55
56
57 static gchar *quoted_from_text(const gchar *text)
58 {
59         const gchar *ptr;
60         gint c = 0;
61         gint l = strlen(text);
62
63         if (l == 0) return NULL;
64
65         while (c < l && text[c] !='"') c++;
66         if (text[c] == '"')
67                 {
68                 gint e;
69                 c++;
70                 ptr = text + c;
71                 e = c;
72                 while (e < l && text[e] !='"') e++;
73                 if (text[e] == '"')
74                         {
75                         if (e - c > 0)
76                                 {
77                                 return g_strndup(ptr, e - c);
78                                 }
79                         }
80                 }
81         return NULL;
82 }
83
84 gint history_list_load(const gchar *path)
85 {
86         FILE *f;
87         gchar *key = NULL;
88         gchar s_buf[1024];
89         gchar *pathl;
90
91         pathl = path_from_utf8(path);
92         f = fopen(pathl, "r");
93         g_free(pathl);
94         if (!f) return FALSE;
95
96         /* first line must start with History comment */
97         if (!fgets(s_buf, sizeof(s_buf), f) ||
98             strncmp(s_buf, "#History", 8) != 0)
99                 {
100                 fclose(f);
101                 return FALSE;
102                 }
103
104         while (fgets(s_buf, sizeof(s_buf), f))
105                 {
106                 if (s_buf[0]=='#') continue;
107                 if (s_buf[0]=='[')
108                         {
109                         gint c;
110                         gchar *ptr;
111
112                         ptr = s_buf + 1;
113                         c = 0;
114                         while (ptr[c] != ']' && ptr[c] != '\n' && ptr[c] != '\0') c++;
115
116                         g_free(key);
117                         key = g_strndup(ptr, c);
118                         }
119                 else
120                         {
121                         gchar *value;
122
123                         value = quoted_from_text(s_buf);
124                         if (value && key)
125                                 {
126                                 history_list_add_to_key(key, value, 0);
127                                 }
128                         g_free(value);
129                         }
130                 }
131
132         fclose(f);
133
134         g_free(key);
135
136         return TRUE;
137 }
138
139 gint history_list_save(const gchar *path)
140 {
141         SecureSaveInfo *ssi;
142         GList *list;
143         gchar *pathl;
144
145         pathl = path_from_utf8(path);
146         ssi = secure_open(pathl);
147         g_free(pathl);
148         if (!ssi)
149                 {
150                 log_printf(_("Unable to write history lists to: %s\n"), path);
151                 return FALSE;
152                 }
153
154         secure_fprintf(ssi, "#History lists\n\n");
155
156         list = g_list_last(history_list);
157         while (list && secsave_errno == SS_ERR_NONE)
158                 {
159                 HistoryData *hd;
160                 GList *work;
161
162                 hd = list->data;
163                 list = list->prev;
164
165                 secure_fprintf(ssi, "[%s]\n", hd->key);
166
167                 /* save them inverted (oldest to newest)
168                  * so that when reading they are added correctly
169                  */
170                 work = g_list_last(hd->list);
171                 while (work && secsave_errno == SS_ERR_NONE)
172                         {
173                         secure_fprintf(ssi, "\"%s\"\n", (gchar *)work->data);
174                         work = work->prev;
175                         }
176                 secure_fputc(ssi, '\n');
177                 }
178
179         secure_fprintf(ssi, "#end\n");
180
181         return (secure_close(ssi) == 0);
182 }
183
184 static void history_list_free(HistoryData *hd)
185 {
186         GList *work;
187
188         if (!hd) return;
189
190         work = hd->list;
191         while (work)
192                 {
193                 g_free(work->data);
194                 work = work->next;
195                 }
196
197         g_free(hd->key);
198         g_free(hd);
199 }
200
201 static HistoryData *history_list_find_by_key(const gchar* key)
202 {
203         GList *work = history_list;
204
205         if (!key) return NULL;
206
207         while (work)
208                 {
209                 HistoryData *hd = work->data;
210                 if (strcmp(hd->key, key) == 0) return hd;
211                 work = work->next;
212                 }
213         return NULL;
214 }
215
216 const gchar *history_list_find_last_path_by_key(const gchar* key)
217 {
218         HistoryData *hd;
219
220         hd = history_list_find_by_key(key);
221         if (!hd || !hd->list) return NULL;
222
223         return hd->list->data;
224 }
225
226 void history_list_free_key(const gchar *key)
227 {
228         HistoryData *hd;
229         hd = history_list_find_by_key(key);
230         if (!hd) return;
231
232         history_list = g_list_remove(history_list, hd);
233         history_list_free(hd);
234 }
235
236 void history_list_add_to_key(const gchar *key, const gchar *path, gint max)
237 {
238         HistoryData *hd;
239         GList *work;
240
241         if (!key || !path) return;
242
243         hd = history_list_find_by_key(key);
244         if (!hd)
245                 {
246                 hd = g_new(HistoryData, 1);
247                 hd->key = g_strdup(key);
248                 hd->list = NULL;
249                 history_list = g_list_prepend(history_list, hd);
250                 }
251
252         /* if already in the list, simply move it to the top */
253         work = hd->list;
254         while (work)
255                 {
256                 gchar *buf = work->data;
257
258                 if (strcmp(buf, path) == 0)
259                         {
260                         /* if not first, move it */
261                         if (work != hd->list)
262                                 {
263                                 hd->list = g_list_remove(hd->list, buf);
264                                 hd->list = g_list_prepend(hd->list, buf);
265                                 }
266                         return;
267                         }
268                 work = work->next;
269                 }
270
271         hd->list = g_list_prepend(hd->list, g_strdup(path));
272
273         if (max == -1) max = HISTORY_DEFAULT_KEY_COUNT;
274         if (max > 0)
275                 {
276                 gint len = 0;
277                 GList *work = hd->list;
278                 GList *last = NULL;
279
280                 while (work)
281                         {
282                         len++;
283                         last = work;
284                         work = work->next;
285                         }
286
287                 work = last;
288                 while (work && len > max)
289                         {
290                         GList *node = work;
291                         work = work->prev;
292
293                         g_free(node->data);
294                         hd->list = g_list_delete_link(hd->list, node);
295                         len--;
296                         }
297                 }
298 }
299
300 void history_list_item_change(const gchar *key, const gchar *oldpath, const gchar *newpath)
301 {
302         HistoryData *hd;
303         GList *work;
304
305         if (!oldpath) return;
306         hd = history_list_find_by_key(key);
307         if (!hd) return;
308
309         work = hd->list;
310         while (work)
311                 {
312                 gchar *buf = work->data;
313                 if (strcmp(buf, oldpath) == 0)
314                         {
315                         if (newpath)
316                                 {
317                                 work->data = g_strdup(newpath);
318                                 }
319                         else
320                                 {
321                                 hd->list = g_list_remove(hd->list, buf);
322                                 }
323                         g_free(buf);
324                         return;
325                         }
326                 work = work->next;
327                 }
328 }
329
330 void history_list_item_move(const gchar *key, const gchar *path, gint direction)
331 {
332         HistoryData *hd;
333         GList *work;
334         gint p = 0;
335
336         if (!path) return;
337         hd = history_list_find_by_key(key);
338         if (!hd) return;
339
340         work = hd->list;
341         while (work)
342                 {
343                 gchar *buf = work->data;
344                 if (strcmp(buf, path) == 0)
345                         {
346                         p += direction;
347                         if (p < 0) return;
348                         hd->list = g_list_remove(hd->list, buf);
349                         hd->list = g_list_insert(hd->list, buf, p);
350                         return;
351                         }
352                 work = work->next;
353                 p++;
354                 }
355 }
356
357 void history_list_item_remove(const gchar *key, const gchar *path)
358 {
359         history_list_item_change(key, path, NULL);
360 }
361
362 GList *history_list_get_by_key(const gchar *key)
363 {
364         HistoryData *hd;
365
366         hd = history_list_find_by_key(key);
367         if (!hd) return NULL;
368
369         return hd->list;
370 }
371
372 /*
373  *-----------------------------------------------------------------------------
374  * bookmarks
375  *-----------------------------------------------------------------------------
376  */
377
378 #define BOOKMARK_DATA_KEY "bookmarkdata"
379 #define MARKER_PATH "[path]"
380 #define MARKER_ICON "[icon]"
381
382 typedef struct _BookMarkData BookMarkData;
383 typedef struct _BookButtonData BookButtonData;
384 typedef struct _BookPropData BookPropData;
385
386 struct _BookMarkData
387 {
388         GtkWidget *widget;
389         GtkWidget *box;
390         gchar *key;
391
392         void (*select_func)(const gchar *path, gpointer data);
393         gpointer select_data;
394
395         gint no_defaults;
396         gint editable;
397         gint only_directories;
398
399         BookButtonData *active_button;
400 };
401
402 struct _BookButtonData
403 {
404         GtkWidget *button;
405         GtkWidget *image;
406         GtkWidget *label;
407
408         gchar *key;
409         gchar *name;
410         gchar *path;
411         gchar *icon;
412         gchar *parent;
413 };
414
415 struct _BookPropData
416 {
417         GtkWidget *name_entry;
418         GtkWidget *path_entry;
419         GtkWidget *icon_entry;
420
421         BookButtonData *bb;
422 };
423
424 enum {
425         TARGET_URI_LIST,
426         TARGET_X_URL,
427         TARGET_TEXT_PLAIN
428 };
429
430 static GtkTargetEntry bookmark_drop_types[] = {
431         { "text/uri-list", 0, TARGET_URI_LIST },
432         { "x-url/http",    0, TARGET_X_URL },
433         { "_NETSCAPE_URL", 0, TARGET_X_URL }
434 };
435 #define bookmark_drop_types_n 3
436
437 static GtkTargetEntry bookmark_drag_types[] = {
438         { "text/uri-list", 0, TARGET_URI_LIST },
439         { "text/plain",    0, TARGET_TEXT_PLAIN }
440 };
441 #define bookmark_drag_types_n 2
442
443
444 static GList *bookmark_widget_list = NULL;
445 static GList *bookmark_default_list = NULL;
446
447
448 static void bookmark_populate_all(const gchar *key);
449
450
451 static BookButtonData *bookmark_from_string(const gchar *text)
452 {
453         BookButtonData *b;
454         const gchar *path_ptr;
455         const gchar *icon_ptr;
456
457         b = g_new0(BookButtonData, 1);
458
459         if (!text)
460                 {
461                 b->name = g_strdup(_("New Bookmark"));
462                 b->path = g_strdup(homedir());
463                 b->key = NULL;
464                 return b;
465                 }
466
467         b->key = g_strdup(text);
468
469         path_ptr = strstr(text, MARKER_PATH);
470         icon_ptr = strstr(text, MARKER_ICON);
471
472         if (path_ptr && icon_ptr && icon_ptr < path_ptr)
473                 {
474                 log_printf("warning, bookmark icon must be after path\n");
475                 return NULL;
476                 }
477
478         if (path_ptr)
479                 {
480                 gint l;
481
482                 l = path_ptr - text;
483                 b->name = g_strndup(text, l);
484                 path_ptr += strlen(MARKER_PATH);
485                 if (icon_ptr)
486                         {
487                         l = icon_ptr - path_ptr;
488                         b->path = g_strndup(path_ptr, l);
489                         }
490                 else
491                         {
492                         b->path = g_strdup(path_ptr);
493                         }
494                 }
495         else
496                 {
497                 b->name = g_strdup(text);
498                 b->path = g_strdup("");
499                 }
500
501         if (icon_ptr)
502                 {
503                 icon_ptr += strlen(MARKER_ICON);
504                 b->icon = g_strdup(icon_ptr);
505                 }
506
507         return b;
508 }
509
510 static void bookmark_free(BookButtonData *b)
511 {
512         if (!b) return;
513
514         g_free(b->name);
515         g_free(b->path);
516         g_free(b->icon);
517         g_free(b->key);
518         g_free(b->parent);
519         g_free(b);
520 }
521
522 static gchar *bookmark_string(const gchar *name, const gchar *path, const gchar *icon)
523 {
524         if (!name) name = _("New Bookmark");
525         if (icon && strncmp(icon, G_DIR_SEPARATOR_S, 1) != 0) icon = NULL;
526
527         if (icon)
528                 {
529                 return g_strdup_printf("%s"MARKER_PATH"%s"MARKER_ICON"%s", name, path, icon);
530                 }
531
532         return g_strdup_printf("%s"MARKER_PATH"%s", name, path);
533 }
534
535 static void bookmark_select_cb(GtkWidget *button, gpointer data)
536 {
537         BookMarkData *bm = data;
538         BookButtonData *b;
539
540         b = g_object_get_data(G_OBJECT(button), "bookbuttondata");
541         if (!b) return;
542
543         if (bm->select_func) bm->select_func(b->path, bm->select_data);
544 }
545
546 static void bookmark_edit_destroy_cb(GtkWidget *widget, gpointer data)
547 {
548         BookPropData *p = data;
549
550         bookmark_free(p->bb);
551         g_free(p);
552 }
553
554 static void bookmark_edit_cancel_cb(GenericDialog *gd, gpointer data)
555 {
556 }
557
558 static void bookmark_edit_ok_cb(GenericDialog *gd, gpointer data)
559 {
560         BookPropData *p = data;
561         const gchar *name;
562         gchar *path;
563         const gchar *icon;
564         gchar *new;
565
566         name = gtk_entry_get_text(GTK_ENTRY(p->name_entry));
567         path = remove_trailing_slash(gtk_entry_get_text(GTK_ENTRY(p->path_entry)));
568         icon = gtk_entry_get_text(GTK_ENTRY(p->icon_entry));
569
570         new = bookmark_string(name, path, icon);
571
572         if (p->bb->key)
573                 {
574                 history_list_item_change(p->bb->parent, p->bb->key, new);
575                 }
576         else
577                 {
578                 history_list_add_to_key(p->bb->parent, new, 0);
579                 }
580
581         if (path && strlen(path) > 0) tab_completion_append_to_history(p->path_entry, path);
582         if (icon && strlen(icon) > 0) tab_completion_append_to_history(p->icon_entry, icon);
583
584         g_free(path);
585         g_free(new);
586
587         bookmark_populate_all(p->bb->parent);
588 }
589
590 /* simply pass NULL for text to turn this into a 'new bookmark' dialog */
591
592 static void bookmark_edit(const gchar *key, const gchar *text, GtkWidget *parent)
593 {
594         BookPropData *p;
595         GenericDialog *gd;
596         GtkWidget *table;
597         GtkWidget *label;
598         const gchar *icon;
599
600         if (!key) key = "bookmarks";
601
602         p = g_new0(BookPropData, 1);
603
604         p->bb = bookmark_from_string(text);
605         p->bb->parent = g_strdup(key);
606
607         gd = generic_dialog_new(_("Edit Bookmark"), GQ_WMCLASS, "bookmark_edit",
608                                 parent, TRUE,
609                                 bookmark_edit_cancel_cb, p);
610         g_signal_connect(G_OBJECT(gd->dialog), "destroy",
611                          G_CALLBACK(bookmark_edit_destroy_cb), p);
612
613         generic_dialog_add_message(gd, NULL, _("Edit Bookmark"), NULL);
614
615         generic_dialog_add_button(gd, GTK_STOCK_OK, NULL,
616                                   bookmark_edit_ok_cb, TRUE);
617
618         table = pref_table_new(gd->vbox, 3, 2, FALSE, TRUE);
619         pref_table_label(table, 0, 0, _("Name:"), 1.0);
620
621         p->name_entry = gtk_entry_new();
622         gtk_widget_set_size_request(p->name_entry, 300, -1);
623         if (p->bb->name) gtk_entry_set_text(GTK_ENTRY(p->name_entry), p->bb->name);
624         gtk_table_attach_defaults(GTK_TABLE(table), p->name_entry, 1, 2, 0, 1);
625         generic_dialog_attach_default(gd, p->name_entry);
626         gtk_widget_show(p->name_entry);
627
628         pref_table_label(table, 0, 1, _("Path:"), 1.0);
629
630         label = tab_completion_new_with_history(&p->path_entry, p->bb->path,
631                                                 "bookmark_path", -1, NULL, NULL);
632         tab_completion_add_select_button(p->path_entry, NULL, TRUE);
633         gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 1, 2);
634         generic_dialog_attach_default(gd, p->path_entry);
635         gtk_widget_show(label);
636
637         pref_table_label(table, 0, 2, _("Icon:"), 1.0);
638
639         icon = p->bb->icon;
640         if (!icon) icon = "";
641         label = tab_completion_new_with_history(&p->icon_entry, icon,
642                                                 "bookmark_icons", -1, NULL, NULL);
643         tab_completion_add_select_button(p->icon_entry, _("Select icon"), FALSE);
644         gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 2, 3);
645         generic_dialog_attach_default(gd, p->icon_entry);
646         gtk_widget_show(label);
647
648         gtk_widget_show(gd->dialog);
649 }
650
651 static void bookmark_move(BookMarkData *bm, GtkWidget *button, gint direction)
652 {
653         BookButtonData *b;
654         gint p;
655         GList *list;
656         gchar *key_holder;
657
658         if (!bm->editable) return;
659
660         b = g_object_get_data(G_OBJECT(button), "bookbuttondata");
661         if (!b) return;
662
663         list = gtk_container_get_children(GTK_CONTAINER(bm->box));
664         p = g_list_index(list, button);
665         g_list_free(list);
666
667         if (p < 0 || p + direction < 0) return;
668
669         key_holder = bm->key;
670         bm->key = "_TEMPHOLDER";
671         history_list_item_move(key_holder, b->key, -direction);
672         bookmark_populate_all(key_holder);
673         bm->key = key_holder;
674
675         gtk_box_reorder_child(GTK_BOX(bm->box), button, p + direction);
676 }
677
678 static void bookmark_menu_prop_cb(GtkWidget *widget, gpointer data)
679 {
680         BookMarkData *bm = data;
681
682         if (!bm->active_button) return;
683
684         bookmark_edit(bm->key, bm->active_button->key, widget);
685 }
686
687 static void bookmark_menu_move(BookMarkData *bm, gint direction)
688 {
689         if (!bm->active_button) return;
690
691         bookmark_move(bm, bm->active_button->button, direction);
692 }
693
694 static void bookmark_menu_up_cb(GtkWidget *widget, gpointer data)
695 {
696         bookmark_menu_move(data, -1);
697 }
698
699 static void bookmark_menu_down_cb(GtkWidget *widget, gpointer data)
700 {
701         bookmark_menu_move(data, 1);
702 }
703
704 static void bookmark_menu_remove_cb(GtkWidget *widget, gpointer data)
705 {
706         BookMarkData *bm = data;
707
708         if (!bm->active_button) return;
709
710         history_list_item_remove(bm->key, bm->active_button->key);
711         bookmark_populate_all(bm->key);
712 }
713
714 static void bookmark_menu_position_cb(GtkMenu *menu, gint *x, gint *y, gint *pushed_in, gpointer data)
715 {
716         GtkWidget *button = data;
717
718         gdk_window_get_origin(button->window, x, y);
719         *y += button->allocation.y + button->allocation.height;
720 }
721
722 static void bookmark_menu_popup(BookMarkData *bm, GtkWidget *button,
723                                 gint button_n, guint32 time, gint local)
724 {
725         GtkWidget *menu;
726         BookButtonData *b;
727
728         b = g_object_get_data(G_OBJECT(button), "bookbuttondata");
729         if (!b) return;
730
731         bm->active_button = b;
732
733         menu = popup_menu_short_lived();
734         menu_item_add_stock_sensitive(menu, _("_Properties..."), GTK_STOCK_PROPERTIES, bm->editable,
735                       G_CALLBACK(bookmark_menu_prop_cb), bm);
736         menu_item_add_stock_sensitive(menu, _("Move _up"), GTK_STOCK_GO_UP, bm->editable,
737                       G_CALLBACK(bookmark_menu_up_cb), bm);
738         menu_item_add_stock_sensitive(menu, _("Move _down"), GTK_STOCK_GO_DOWN, bm->editable,
739                       G_CALLBACK(bookmark_menu_down_cb), bm);
740         menu_item_add_stock_sensitive(menu, _("_Remove"), GTK_STOCK_REMOVE, bm->editable,
741                       G_CALLBACK(bookmark_menu_remove_cb), bm);
742
743         if (local)
744                 {
745                 gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
746                                bookmark_menu_position_cb, button, button_n, time);
747                 }
748         else
749                 {
750                 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, button_n, time);
751                 }
752 }
753
754 static gint bookmark_press_cb(GtkWidget *button, GdkEventButton *event, gpointer data)
755 {
756         BookMarkData *bm = data;
757
758         if (event->button != MOUSE_BUTTON_RIGHT) return FALSE;
759
760         bookmark_menu_popup(bm, button, event->button, event->time, FALSE);
761
762         return TRUE;
763 }
764
765 static gint bookmark_keypress_cb(GtkWidget *button, GdkEventKey *event, gpointer data)
766 {
767         BookMarkData *bm = data;
768
769         switch (event->keyval)
770                 {
771                 case GDK_F10:
772                         if (!(event->state & GDK_CONTROL_MASK)) return FALSE;
773                 case GDK_Menu:
774                         bookmark_menu_popup(bm, button, 0, event->time, TRUE);
775                         return TRUE;
776                         break;
777                 case GDK_Up:
778                         if (event->state & GDK_SHIFT_MASK)
779                                 {
780                                 bookmark_move(bm, button, -1);
781                                 return TRUE;
782                                 }
783                         break;
784                 case GDK_Down:
785                         if (event->state & GDK_SHIFT_MASK)
786                                 {
787                                 bookmark_move(bm, button, 1);
788                                 return TRUE;
789                                 }
790                         break;
791                 }
792
793         return FALSE;
794 }
795
796 static void bookmark_drag_set_data(GtkWidget *button,
797                                    GdkDragContext *context, GtkSelectionData *selection_data,
798                                    guint info, guint time, gpointer data)
799 {
800         BookMarkData *bm = data;
801         BookButtonData *b;
802         gchar *uri_text = NULL;
803         gint length = 0;
804         GList *list = NULL;
805
806         if (context->dest_window == bm->widget->window) return;
807
808         b = g_object_get_data(G_OBJECT(button), "bookbuttondata");
809         if (!b) return;
810
811         list = g_list_append(list, b->path);
812
813         switch (info)
814                 {
815                 case TARGET_URI_LIST:
816                         uri_text = uri_text_from_list(list, &length, FALSE);
817                         break;
818                 case TARGET_TEXT_PLAIN:
819                         uri_text = uri_text_from_list(list, &length, TRUE);
820                         break;
821                 }
822
823         g_list_free(list);
824
825         if (!uri_text) return;
826
827         gtk_selection_data_set(selection_data, selection_data->target,
828                                8, (guchar *)uri_text, length);
829         g_free(uri_text);
830 }
831
832 static void bookmark_drag_begin(GtkWidget *button, GdkDragContext *context, gpointer data)
833 {
834         GdkPixbuf *pixbuf;
835         GdkModifierType mask;
836         gint x, y;
837
838         pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8,
839                                 button->allocation.width, button->allocation.height);
840         gdk_pixbuf_get_from_drawable(pixbuf, button->window, NULL,
841                                      button->allocation.x, button->allocation.y,
842                                      0, 0, button->allocation.width, button->allocation.height);
843
844         gdk_window_get_pointer(button->window, &x, &y, &mask);
845
846         gtk_drag_set_icon_pixbuf(context, pixbuf,
847                                  x - button->allocation.x, y - button->allocation.y);
848         g_object_unref(pixbuf);
849 }
850
851 static void bookmark_populate(BookMarkData *bm)
852 {
853         GtkBox *box;
854         GList *work;
855         GList *children;
856
857         box = GTK_BOX(bm->box);
858         children = gtk_container_get_children(GTK_CONTAINER(box));
859         work = children;
860         while (work)
861                 {
862                 GtkWidget *widget = GTK_WIDGET(work->data);
863                 work = work->next;
864                 gtk_widget_destroy(widget);
865                 }
866
867         if (!bm->no_defaults && !history_list_get_by_key(bm->key))
868                 {
869                 gchar *buf;
870                 gchar *path;
871
872                 if (!bookmark_default_list)
873                         {
874                         buf = bookmark_string(_("Home"), homedir(), NULL);
875                         history_list_add_to_key(bm->key, buf, 0);
876                         g_free(buf);
877
878                         path = g_build_filename(homedir(), "Desktop", NULL);
879                         if (isname(path))
880                                 {
881                                 buf = bookmark_string(_("Desktop"), path, NULL);
882                                 history_list_add_to_key(bm->key, buf, 0);
883                                 g_free(buf);
884                                 }
885                         g_free(path);
886                         }
887
888                 work = bookmark_default_list;
889                 while (work && work->next)
890                         {
891                         gchar *name;
892
893                         name = work->data;
894                         work = work->next;
895                         path = work->data;
896                         work = work->next;
897
898                         buf = bookmark_string(name, path, NULL);
899                         history_list_add_to_key(bm->key, buf, 0);
900                         g_free(buf);
901                         }
902                 }
903
904         work = history_list_get_by_key(bm->key);
905         work = g_list_last(work);
906         while (work)
907                 {
908                 BookButtonData *b;
909
910                 b = bookmark_from_string(work->data);
911                 if (b)
912                         {
913                         GtkWidget *box;
914
915                         b->button = gtk_button_new();
916                         gtk_button_set_relief(GTK_BUTTON(b->button), GTK_RELIEF_NONE);
917                         gtk_box_pack_start(GTK_BOX(bm->box), b->button, FALSE, FALSE, 0);
918                         gtk_widget_show(b->button);
919
920                         g_object_set_data_full(G_OBJECT(b->button), "bookbuttondata",
921                                                b, (GDestroyNotify)bookmark_free);
922
923                         box = gtk_hbox_new(FALSE, PREF_PAD_BUTTON_GAP);
924                         gtk_container_add(GTK_CONTAINER(b->button), box);
925                         gtk_widget_show(box);
926
927                         if (b->icon)
928                                 {
929                                 GdkPixbuf *pixbuf;
930                                 gchar *iconl;
931
932                                 iconl = path_from_utf8(b->icon);
933                                 pixbuf = gdk_pixbuf_new_from_file(iconl, NULL);
934                                 g_free(iconl);
935                                 if (pixbuf)
936                                         {
937                                         GdkPixbuf *scaled;
938                                         gint w, h;
939
940                                         w = h = 16;
941                                         gtk_icon_size_lookup(GTK_ICON_SIZE_BUTTON, &w, &h);
942
943                                         scaled = gdk_pixbuf_scale_simple(pixbuf, w, h,
944                                                                          GDK_INTERP_BILINEAR);
945                                         b->image = gtk_image_new_from_pixbuf(scaled);
946                                         g_object_unref(scaled);
947                                         g_object_unref(pixbuf);
948                                         }
949                                 else
950                                         {
951                                         b->image = gtk_image_new_from_stock(GTK_STOCK_MISSING_IMAGE,
952                                                                             GTK_ICON_SIZE_BUTTON);
953                                         }
954                                 }
955                         else
956                                 {
957                                 b->image = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_BUTTON);
958                                 }
959                         gtk_box_pack_start(GTK_BOX(box), b->image, FALSE, FALSE, 0);
960                         gtk_widget_show(b->image);
961
962                         b->label = gtk_label_new(b->name);
963                         gtk_box_pack_start(GTK_BOX(box), b->label, FALSE, FALSE, 0);
964                         gtk_widget_show(b->label);
965
966                         g_signal_connect(G_OBJECT(b->button), "clicked",
967                                          G_CALLBACK(bookmark_select_cb), bm);
968                         g_signal_connect(G_OBJECT(b->button), "button_press_event",
969                                          G_CALLBACK(bookmark_press_cb), bm);
970                         g_signal_connect(G_OBJECT(b->button), "key_press_event",
971                                          G_CALLBACK(bookmark_keypress_cb), bm);
972
973                         gtk_drag_source_set(b->button, GDK_BUTTON1_MASK,
974                                             bookmark_drag_types, bookmark_drag_types_n,
975                                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
976                         g_signal_connect(G_OBJECT(b->button), "drag_data_get",
977                                          G_CALLBACK(bookmark_drag_set_data), bm);
978                         g_signal_connect(G_OBJECT(b->button), "drag_begin",
979                                          G_CALLBACK(bookmark_drag_begin), bm);
980                         }
981
982                 work = work->prev;
983                 }
984 }
985
986 static void bookmark_populate_all(const gchar *key)
987 {
988         GList *work;
989
990         if (!key) return;
991
992         work = bookmark_widget_list;
993         while (work)
994                 {
995                 BookMarkData *bm;
996
997                 bm = work->data;
998                 work = work->next;
999
1000                 if (strcmp(bm->key, key) == 0)
1001                         {
1002                         bookmark_populate(bm);
1003                         }
1004                 }
1005 }
1006
1007 static void bookmark_dnd_get_data(GtkWidget *widget,
1008                                   GdkDragContext *context, gint x, gint y,
1009                                   GtkSelectionData *selection_data, guint info,
1010                                   guint time, gpointer data)
1011 {
1012         BookMarkData *bm = data;
1013         GList *list = NULL;
1014         GList *work;
1015
1016         if (!bm->editable) return;
1017
1018         switch (info)
1019                 {
1020                 case TARGET_URI_LIST:
1021                 case TARGET_X_URL:
1022                         list = uri_list_from_text((gchar *)selection_data->data, FALSE);
1023                         break;
1024                 }
1025
1026         work = list;
1027         while (work)
1028                 {
1029                 gchar *path = work->data;
1030                 gchar *buf;
1031
1032                 work = work->next;
1033
1034                 if (bm->only_directories && !isdir(path)) continue;
1035                 buf = bookmark_string(filename_from_path(path), path, NULL);
1036                 history_list_add_to_key(bm->key, buf, 0);
1037                 g_free(buf);
1038                 }
1039
1040         string_list_free(list);
1041
1042         bookmark_populate_all(bm->key);
1043 }
1044
1045 static void bookmark_list_destroy(GtkWidget *widget, gpointer data)
1046 {
1047         BookMarkData *bm = data;
1048
1049         bookmark_widget_list = g_list_remove(bookmark_widget_list, bm);
1050
1051         g_free(bm->key);
1052         g_free(bm);
1053 }
1054
1055 GtkWidget *bookmark_list_new(const gchar *key,
1056                              void (*select_func)(const gchar *path, gpointer data), gpointer select_data)
1057 {
1058         GtkWidget *scrolled;
1059         BookMarkData *bm;
1060
1061         if (!key) key = "bookmarks";
1062
1063         bm = g_new0(BookMarkData, 1);
1064         bm->key = g_strdup(key);
1065
1066         bm->select_func = select_func;
1067         bm->select_data = select_data;
1068
1069         bm->no_defaults = FALSE;
1070         bm->editable = TRUE;
1071         bm->only_directories = FALSE;
1072
1073         scrolled = gtk_scrolled_window_new(NULL, NULL);
1074         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
1075                                        GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1076
1077         bm->box = gtk_vbox_new(FALSE, 0);
1078         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled), bm->box);
1079         gtk_widget_show(bm->box);
1080
1081         bookmark_populate(bm);
1082
1083         g_signal_connect(G_OBJECT(bm->box), "destroy",
1084                          G_CALLBACK(bookmark_list_destroy), bm);
1085         g_object_set_data(G_OBJECT(bm->box), BOOKMARK_DATA_KEY, bm);
1086         g_object_set_data(G_OBJECT(scrolled), BOOKMARK_DATA_KEY, bm);
1087         bm->widget = scrolled;
1088
1089         gtk_drag_dest_set(scrolled,
1090                           GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
1091                           bookmark_drop_types, bookmark_drop_types_n,
1092                           GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
1093         g_signal_connect(G_OBJECT(scrolled), "drag_data_received",
1094                          G_CALLBACK(bookmark_dnd_get_data), bm);
1095
1096         bookmark_widget_list = g_list_append(bookmark_widget_list, bm);
1097
1098         return scrolled;
1099 }
1100
1101 void bookmark_list_set_key(GtkWidget *list, const gchar *key)
1102 {
1103         BookMarkData *bm;
1104
1105         if (!list || !key) return;
1106
1107         bm = g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY);
1108         if (!bm) return;
1109
1110         if (bm->key && strcmp(bm->key, key) == 0) return;
1111
1112         g_free(bm->key);
1113         bm->key = g_strdup(key);
1114
1115         bookmark_populate(bm);
1116 }
1117
1118 void bookmark_list_set_no_defaults(GtkWidget *list, gint no_defaults)
1119 {
1120         BookMarkData *bm;
1121
1122         bm = g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY);
1123         if (!bm) return;
1124
1125         bm->no_defaults = no_defaults;
1126 }
1127
1128 void bookmark_list_set_editable(GtkWidget *list, gint editable)
1129 {
1130         BookMarkData *bm;
1131
1132         bm = g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY);
1133         if (!bm) return;
1134
1135         bm->editable = editable;
1136 }
1137
1138 void bookmark_list_set_only_directories(GtkWidget *list, gint only_directories)
1139 {
1140         BookMarkData *bm;
1141
1142         bm = g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY);
1143         if (!bm) return;
1144
1145         bm->only_directories = only_directories;
1146 }
1147
1148 void bookmark_list_add(GtkWidget *list, const gchar *name, const gchar *path)
1149 {
1150         BookMarkData *bm;
1151         gchar *buf;
1152
1153         bm = g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY);
1154         if (!bm) return;
1155
1156         buf = bookmark_string(name, path, NULL);
1157         history_list_add_to_key(bm->key, buf, 0);
1158         g_free(buf);
1159
1160         bookmark_populate_all(bm->key);
1161 }
1162
1163 void bookmark_add_default(const gchar *name, const gchar *path)
1164 {
1165         if (!name || !path) return;
1166         bookmark_default_list = g_list_append(bookmark_default_list, g_strdup(name));
1167         bookmark_default_list = g_list_append(bookmark_default_list, g_strdup(path));
1168 }
1169
1170 /*
1171  *-----------------------------------------------------------------------------
1172  * combo with history key
1173  *-----------------------------------------------------------------------------
1174  */
1175
1176 typedef struct _HistoryComboData HistoryComboData;
1177 struct _HistoryComboData
1178 {
1179         GtkWidget *combo;
1180         GtkWidget *entry;
1181         gchar *history_key;
1182         gint history_levels;
1183 };
1184
1185 static void history_combo_destroy(GtkWidget *widget, gpointer data)
1186 {
1187         HistoryComboData *hc = data;
1188
1189         g_free(hc->history_key);
1190         g_free(data);
1191 }
1192
1193 /* if text is NULL, entry is set to the most recent item */
1194 GtkWidget *history_combo_new(GtkWidget **entry, const gchar *text,
1195                              const gchar *history_key, gint max_levels)
1196 {
1197         HistoryComboData *hc;
1198         GList *work;
1199         gint n = 0;
1200
1201         hc = g_new0(HistoryComboData, 1);
1202         hc->history_key = g_strdup(history_key);
1203         hc->history_levels = max_levels;
1204
1205         hc->combo = gtk_combo_box_entry_new_text();
1206 #if 0
1207         gtk_combo_set_case_sensitive(GTK_COMBO(hc->combo), TRUE);
1208         gtk_combo_set_use_arrows(GTK_COMBO(hc->combo), FALSE);
1209 #endif
1210
1211         hc->entry = GTK_BIN(hc->combo)->child;
1212
1213         g_object_set_data(G_OBJECT(hc->combo), "history_combo_data", hc);
1214         g_object_set_data(G_OBJECT(hc->entry), "history_combo_data", hc);
1215         g_signal_connect(G_OBJECT(hc->combo), "destroy",
1216                          G_CALLBACK(history_combo_destroy), hc);
1217
1218         work = history_list_get_by_key(hc->history_key);
1219         while (work)
1220                 {
1221                 gtk_combo_box_append_text(GTK_COMBO_BOX(hc->combo), (gchar *)work->data);
1222                 work = work->next;
1223                 n++;
1224                 }
1225
1226         if (text)
1227                 {
1228                 gtk_entry_set_text(GTK_ENTRY(hc->entry), text);
1229                 }
1230         else if (n > 0)
1231                 {
1232                 gtk_combo_box_set_active(GTK_COMBO_BOX(hc->combo), 0);
1233                 }
1234
1235         if (entry) *entry = hc->entry;
1236         return hc->combo;
1237 }
1238
1239 /* if text is NULL, current entry text is used
1240  * widget can be the combo or entry widget
1241  */
1242 void history_combo_append_history(GtkWidget *widget, const gchar *text)
1243 {
1244         HistoryComboData *hc;
1245         gchar *new_text;
1246
1247         hc = g_object_get_data(G_OBJECT(widget), "history_combo_data");
1248         if (!hc)
1249                 {
1250                 log_printf("widget is not a history combo\n");
1251                 return;
1252                 }
1253
1254         if (text)
1255                 {
1256                 new_text = g_strdup(text);
1257                 }
1258         else
1259                 {
1260                 new_text = g_strdup(gtk_entry_get_text(GTK_ENTRY(hc->entry)));
1261                 }
1262
1263         if (new_text && strlen(new_text) > 0)
1264                 {
1265                 GtkTreeModel *store;
1266                 GList *work;
1267
1268                 history_list_add_to_key(hc->history_key, new_text, hc->history_levels);
1269
1270                 gtk_combo_box_set_active(GTK_COMBO_BOX(hc->combo), -1);
1271
1272                 store = gtk_combo_box_get_model(GTK_COMBO_BOX(hc->combo));
1273                 gtk_list_store_clear(GTK_LIST_STORE(store));
1274
1275                 work = history_list_get_by_key(hc->history_key);
1276                 while (work)
1277                         {
1278                         gtk_combo_box_append_text(GTK_COMBO_BOX(hc->combo), (gchar *)work->data);
1279                         work = work->next;
1280                         }
1281                 }
1282
1283         g_free(new_text);
1284 }
1285
1286 /*
1287  *-----------------------------------------------------------------------------
1288  * drag and drop uri utils
1289  *-----------------------------------------------------------------------------
1290  */
1291
1292 /* the following characters are allowed to be unencoded for pathnames:
1293  *     $ & + , / : = @
1294  */
1295 static gint escape_char_list[] = {
1296         1, 1, 1, 1, 1, 1, 1, 1, 1, 1,   /*   0 */
1297         1, 1, 1, 1, 1, 1, 1, 1, 1, 1,   /*  10 */
1298         1, 1, 1, 1, 1, 1, 1, 1, 1, 1,   /*  20 */
1299 /*           spc !  "  #  $  %  &  '           */
1300         1, 1, 0, 0, 1, 1, 0, 1, 0, 0,   /*  30 */
1301 /*      (  )  *  +  ,  -  .  /  0  1           */
1302         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   /*  40 */
1303 /*      2  3  4  5  6  7  8  9  :  ;           */
1304         0, 0, 0, 0, 0, 0, 0, 0, 0, 1,   /*  50 */
1305 /*      <  =  >  ?  @  A  B  C  D  E           */
1306         1, 0, 1, 1, 0, 0, 0, 0, 0, 0,   /*  60 */
1307         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   /*  70 */
1308         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   /*  80 */
1309 /*      Z  [  \  ]  ^  _  `  a  b  c           */
1310         0, 1, 1, 1, 1, 0, 1, 0, 0, 0,   /*  90 */
1311         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   /* 100 */
1312         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   /* 110 */
1313 /*      x  y  z  {  |  }  ~ del                */
1314         0, 0, 0, 1, 1, 1, 0, 0          /* 120, 127 is end */
1315 };
1316
1317 static gchar *hex_char = "0123456789ABCDEF";
1318
1319 static gint escape_test(guchar c)
1320 {
1321         if (c < 32 || c > 127) return TRUE;
1322         return (escape_char_list[c] != 0);
1323 }
1324
1325 static const gchar *escape_code(guchar c)
1326 {
1327         static gchar text[4];
1328
1329         text[0] = '%';
1330         text[1] = hex_char[c>>4];
1331         text[2] = hex_char[c%16];
1332         text[3] = '\0';
1333
1334         return text;
1335 }
1336
1337 gchar *uri_text_escape(const gchar *text)
1338 {
1339         GString *string;
1340         gchar *result;
1341         const gchar *p;
1342
1343         if (!text) return NULL;
1344
1345         string = g_string_new("");
1346
1347         p = text;
1348         while (*p != '\0')
1349                 {
1350                 if (escape_test(*p))
1351                         {
1352                         g_string_append(string, escape_code(*p));
1353                         }
1354                 else
1355                         {
1356                         g_string_append_c(string, *p);
1357                         }
1358                 p++;
1359                 }
1360
1361         result = string->str;
1362         g_string_free(string, FALSE);
1363
1364         /* dropped filenames are expected to be utf-8 compatible */
1365         if (!g_utf8_validate(result, -1, NULL))
1366                 {
1367                 gchar *tmp;
1368
1369                 tmp = g_locale_to_utf8(result, -1, NULL, NULL, NULL);
1370                 if (tmp)
1371                         {
1372                         g_free(result);
1373                         result = tmp;
1374                         }
1375                 }
1376
1377         return result;
1378 }
1379
1380 /* this operates on the passed string, decoding escaped characters */
1381 void uri_text_decode(gchar *text)
1382 {
1383         if (strchr(text, '%'))
1384                 {
1385                 gchar *w;
1386                 gchar *r;
1387
1388                 w = r = text;
1389
1390                 while (*r != '\0')
1391                         {
1392                         if (*r == '%' && *(r + 1) != '\0' && *(r + 2) != '\0')
1393                                 {
1394                                 gchar t[3];
1395                                 gint n;
1396
1397                                 r++;
1398                                 t[0] = *r;
1399                                 r++;
1400                                 t[1] = *r;
1401                                 t[2] = '\0';
1402                                 n = (gint)strtol(t, NULL, 16);
1403                                 if (n > 0 && n < 256)
1404                                         {
1405                                         *w = (gchar)n;
1406                                         }
1407                                 else
1408                                         {
1409                                         /* invalid number, rewind and ignore this escape */
1410                                         r -= 2;
1411                                         *w = *r;
1412                                         }
1413                                 }
1414                         else if (w != r)
1415                                 {
1416                                 *w = *r;
1417                                 }
1418                         r++;
1419                         w++;
1420                         }
1421                 if (*w != '\0') *w = '\0';
1422                 }
1423 }
1424
1425 static void uri_list_parse_encoded_chars(GList *list)
1426 {
1427         GList *work = list;
1428
1429         while (work)
1430                 {
1431                 gchar *text = work->data;
1432
1433                 uri_text_decode(text);
1434
1435                 work = work->next;
1436                 }
1437 }
1438
1439 GList *uri_list_from_text(gchar *data, gint files_only)
1440 {
1441         GList *list = NULL;
1442         gint b, e;
1443
1444         b = e = 0;
1445
1446         while (data[b] != '\0')
1447                 {
1448                 while (data[e] != '\r' && data[e] != '\n' && data[e] != '\0') e++;
1449                 if (strncmp(data + b, "file:", 5) == 0)
1450                         {
1451                         gchar *path;
1452                         b += 5;
1453                         while (data[b] == '/' && data[b+1] == '/') b++;
1454                         path = g_strndup(data + b, e - b);
1455                         list = g_list_append(list, path_to_utf8(path));
1456                         g_free(path);
1457                         }
1458                 else if (!files_only && strncmp(data + b, "http:", 5) == 0)
1459                         {
1460                         list = g_list_append(list, g_strndup(data + b, e - b));
1461                         }
1462                 else if (!files_only && strncmp(data + b, "ftp:", 3) == 0)
1463                         {
1464                         list = g_list_append(list, g_strndup(data + b, e - b));
1465                         }
1466                 while (data[e] == '\r' || data[e] == '\n') e++;
1467                 b = e;
1468                 }
1469
1470         uri_list_parse_encoded_chars(list);
1471
1472         return list;
1473 }
1474
1475 GList *uri_filelist_from_text(gchar *data, gint files_only)
1476 {
1477         GList *path_list = uri_list_from_text(data, files_only);
1478         GList *filelist = filelist_from_path_list(path_list);
1479         string_list_free(path_list);
1480         return filelist;
1481 }
1482
1483 gchar *uri_text_from_list(GList *list, gint *len, gint plain_text)
1484 {
1485         gchar *uri_text = NULL;
1486         GString *string;
1487         GList *work;
1488
1489         if (!list)
1490                 {
1491                 if (len) *len = 0;
1492                 return NULL;
1493                 }
1494
1495         string = g_string_new("");
1496
1497         work = list;
1498         while (work)
1499                 {
1500                 const gchar *name8;     /* dnd filenames are in utf-8 */
1501
1502                 name8 = work->data;
1503
1504                 if (!plain_text)
1505                         {
1506                         gchar *escaped;
1507
1508                         escaped = uri_text_escape(name8);
1509                         g_string_append(string, "file:");
1510                         g_string_append(string, escaped);
1511                         g_free(escaped);
1512
1513                         g_string_append(string, "\r\n");
1514                         }
1515                 else
1516                         {
1517                         g_string_append(string, name8);
1518                         if (work->next) g_string_append(string, "\n");
1519                         }
1520
1521                 work = work->next;
1522                 }
1523
1524         uri_text = string->str;
1525         if (len) *len = string->len;
1526         g_string_free(string, FALSE);
1527
1528         return uri_text;
1529 }
1530
1531 gchar *uri_text_from_filelist(GList *list, gint *len, gint plain_text)
1532 {
1533         GList *path_list = filelist_to_path_list(list);
1534         gchar *ret = uri_text_from_list(path_list, len, plain_text);
1535         string_list_free(path_list);
1536         return ret;
1537 }