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