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