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