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