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