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