Use sizeof().
[geeqie.git] / src / ui_bookmark.c
1 /*
2  * (SLIK) SimpLIstic sKin functions
3  * (C) 2004 John Ellis
4  * Copyright (C) 2008 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13 #ifdef HAVE_CONFIG_H
14 #  include "config.h"
15 #endif
16 #include "intl.h"
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21
22 #include <gtk/gtk.h>
23
24 #include <gdk/gdkkeysyms.h> /* for key values */
25
26 #include "main.h"
27 #include "filelist.h"
28
29 #include "debug.h"
30 #include "secure_save.h"
31 #include "ui_bookmark.h"
32 #include "ui_fileops.h"
33 #include "ui_menu.h"
34 #include "ui_misc.h"
35 #include "ui_utildlg.h"
36 #include "ui_tabcomp.h"
37
38
39 /*
40  *-----------------------------------------------------------------------------
41  * history lists
42  *-----------------------------------------------------------------------------
43  */
44
45 #define HISTORY_DEFAULT_KEY_COUNT 16
46
47
48 typedef struct _HistoryData HistoryData;
49 struct _HistoryData
50 {
51         gchar *key;
52         GList *list;
53 };
54
55 static GList *history_list = NULL;
56
57
58 static gchar *quoted_from_text(const gchar *text)
59 {
60         const gchar *ptr;
61         gint c = 0;
62         gint l = strlen(text);
63
64         if (l == 0) return NULL;
65
66         while (c < l && text[c] !='"') c++;
67         if (text[c] == '"')
68                 {
69                 gint e;
70                 c++;
71                 ptr = text + c;
72                 e = c;
73                 while (e < l && text[e] !='"') e++;
74                 if (text[e] == '"')
75                         {
76                         if (e - c > 0)
77                                 {
78                                 return g_strndup(ptr, e - c);
79                                 }
80                         }
81                 }
82         return NULL;
83 }
84
85 gint history_list_load(const gchar *path)
86 {
87         FILE *f;
88         gchar *key = NULL;
89         gchar s_buf[1024];
90         gchar *pathl;
91
92         pathl = path_from_utf8(path);
93         f = fopen(pathl, "r");
94         g_free(pathl);
95         if (!f) return FALSE;
96
97         /* first line must start with History comment */
98         if (!fgets(s_buf, sizeof(s_buf), f) ||
99             strncmp(s_buf, "#History", 8) != 0)
100                 {
101                 fclose(f);
102                 return FALSE;
103                 }
104
105         while (fgets(s_buf, sizeof(s_buf), f))
106                 {
107                 if (s_buf[0]=='#') continue;
108                 if (s_buf[0]=='[')
109                         {
110                         gint c;
111                         gchar *ptr;
112
113                         ptr = s_buf + 1;
114                         c = 0;
115                         while(ptr[c] != ']' && ptr[c] != '\n' && ptr[c] != '\0') c++;
116
117                         g_free(key);
118                         key = g_strndup(ptr, c);
119                         }
120                 else
121                         {
122                         gchar *value;
123
124                         value = quoted_from_text(s_buf);
125                         if (value && key)
126                                 {
127                                 history_list_add_to_key(key, value, 0);
128                                 }
129                         g_free(value);
130                         }
131                 }
132
133         fclose(f);
134
135         g_free(key);
136
137         return TRUE;
138 }
139
140 gint history_list_save(const gchar *path)
141 {
142         SecureSaveInfo *ssi;
143         GList *list;
144         gchar *pathl;
145
146         pathl = path_from_utf8(path);
147         ssi = secure_open(pathl);
148         g_free(pathl);
149         if (!ssi)
150                 {
151                 printf_term(_("Unable to write history lists to: %s\n"), path);
152                 return FALSE;
153                 }
154
155         secure_fprintf(ssi, "#History lists\n");
156         secure_fprintf(ssi, "\n");
157
158         list = g_list_last(history_list);
159         while(list && secsave_errno == SS_ERR_NONE)
160                 {
161                 HistoryData *hd;
162                 GList *work;
163
164                 hd = list->data;
165                 list = list->prev;
166
167                 secure_fprintf(ssi, "[%s]\n", hd->key);
168
169                 /* save them inverted (oldest to newest)
170                  * so that when reading they are added correctly
171                  */
172                 work = g_list_last(hd->list);
173                 while(work && secsave_errno == SS_ERR_NONE)
174                         {
175                         secure_fprintf(ssi, "\"%s\"\n", (gchar *)work->data);
176                         work = work->prev;
177                         }
178                 secure_fputc(ssi, '\n');
179                 }
180
181         secure_fprintf(ssi, "#end\n");
182
183         return (secure_close(ssi) == 0);
184 }
185
186 static void history_list_free(HistoryData *hd)
187 {
188         GList *work;
189
190         if (!hd) return;
191
192         work = hd->list;
193         while(work)
194                 {
195                 g_free(work->data);
196                 work = work->next;
197                 }
198
199         g_free(hd->key);
200         g_free(hd);
201 }
202
203 static HistoryData *history_list_find_by_key(const gchar* key)
204 {
205         GList *work = history_list;
206
207         if (!key) return NULL;
208
209         while (work)
210                 {
211                 HistoryData *hd = work->data;
212                 if (strcmp(hd->key, key) == 0) return hd;
213                 work = work->next;
214                 }
215         return NULL;
216 }
217
218 const gchar *history_list_find_last_path_by_key(const gchar* key)
219 {
220         HistoryData *hd;
221
222         hd = history_list_find_by_key(key);
223         if (!hd || !hd->list) return NULL;
224
225         return hd->list->data;
226 }
227
228 void history_list_free_key(const gchar *key)
229 {
230         HistoryData *hd;
231         hd = history_list_find_by_key(key);
232         if (!hd) return;
233
234         history_list = g_list_remove(history_list, hd);
235         history_list_free(hd);
236 }
237
238 void history_list_add_to_key(const gchar *key, const gchar *path, gint max)
239 {
240         HistoryData *hd;
241         GList *work;
242
243         if (!key || !path) return;
244
245         hd = history_list_find_by_key(key);
246         if (!hd)
247                 {
248                 hd = g_new(HistoryData, 1);
249                 hd->key = g_strdup(key);
250                 hd->list = NULL;
251                 history_list = g_list_prepend(history_list, hd);
252                 }
253
254         /* if already in the list, simply move it to the top */
255         work = hd->list;
256         while(work)
257                 {
258                 gchar *buf = work->data;
259                 work = work->next;
260                 if (strcmp(buf, path) == 0)
261                         {
262                         hd->list = g_list_remove(hd->list, buf);
263                         hd->list = g_list_prepend(hd->list, buf);
264                         return;
265                         }
266                 }
267
268         hd->list = g_list_prepend(hd->list, g_strdup(path));
269
270         if (max == -1) max = HISTORY_DEFAULT_KEY_COUNT;
271         if (max > 0)
272                 {
273                 while(hd->list && g_list_length(hd->list) > max)
274                         {
275                         GList *work = g_list_last(hd->list);
276                         gchar *buf = work->data;
277                         hd->list = g_list_remove(hd->list, buf);
278                         g_free(buf);
279                         }
280                 }
281 }
282
283 void history_list_item_change(const gchar *key, const gchar *oldpath, const gchar *newpath)
284 {
285         HistoryData *hd;
286         GList *work;
287
288         if (!oldpath) return;
289         hd = history_list_find_by_key(key);
290         if (!hd) return;
291
292         work = hd->list;
293         while(work)
294                 {
295                 gchar *buf = work->data;
296                 if (strcmp(buf, oldpath) == 0)
297                         {
298                         if (newpath)
299                                 {
300                                 work->data = g_strdup(newpath);
301                                 }
302                         else
303                                 {
304                                 hd->list = g_list_remove(hd->list, buf);
305                                 }
306                         g_free(buf);
307                         return;
308                         }
309                 work = work->next;
310                 }
311 }
312
313 void history_list_item_move(const gchar *key, const gchar *path, gint direction)
314 {
315         HistoryData *hd;
316         GList *work;
317         gint p = 0;
318
319         if (!path) return;
320         hd = history_list_find_by_key(key);
321         if (!hd) return;
322
323         work = hd->list;
324         while (work)
325                 {
326                 gchar *buf = work->data;
327                 if (strcmp(buf, path) == 0)
328                         {
329                         p += direction;
330                         if (p < 0) return;
331                         hd->list = g_list_remove(hd->list, buf);
332                         hd->list = g_list_insert(hd->list, buf, p);
333                         return;
334                         }
335                 work = work->next;
336                 p++;
337                 }
338 }
339
340 void history_list_item_remove(const gchar *key, const gchar *path)
341 {
342         history_list_item_change(key, path, NULL);
343 }
344
345 GList *history_list_get_by_key(const gchar *key)
346 {
347         HistoryData *hd;
348
349         hd = history_list_find_by_key(key);
350         if (!hd) return NULL;
351
352         return hd->list;
353 }
354
355 /*
356  *-----------------------------------------------------------------------------
357  * bookmarks
358  *-----------------------------------------------------------------------------
359  */
360
361 #define BOOKMARK_DATA_KEY "bookmarkdata"
362 #define MARKER_PATH "[path]"
363 #define MARKER_ICON "[icon]"
364
365 typedef struct _BookMarkData BookMarkData;
366 typedef struct _BookButtonData BookButtonData;
367 typedef struct _BookPropData BookPropData;
368
369 struct _BookMarkData
370 {
371         GtkWidget *widget;
372         GtkWidget *box;
373         gchar *key;
374
375         void (*select_func)(const gchar *path, gpointer data);
376         gpointer select_data;
377
378         gint no_defaults;
379         gint editable;
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                 buf = bookmark_string(filename_from_path(path), path, NULL);
1015                 history_list_add_to_key(bm->key, buf, 0);
1016                 g_free(buf);
1017
1018                 work = work->next;
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
1053         scrolled = gtk_scrolled_window_new(NULL, NULL);
1054         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
1055                                        GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1056
1057         bm->box = gtk_vbox_new(FALSE, 0);
1058         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled), bm->box);
1059         gtk_widget_show(bm->box);
1060
1061         bookmark_populate(bm);
1062
1063         g_signal_connect(G_OBJECT(bm->box), "destroy",
1064                          G_CALLBACK(bookmark_list_destroy), bm);
1065         g_object_set_data(G_OBJECT(bm->box), BOOKMARK_DATA_KEY, bm);
1066         g_object_set_data(G_OBJECT(scrolled), BOOKMARK_DATA_KEY, bm);
1067         bm->widget = scrolled;
1068
1069         gtk_drag_dest_set(scrolled,
1070                           GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
1071                           bookmark_drop_types, bookmark_drop_types_n,
1072                           GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
1073         g_signal_connect(G_OBJECT(scrolled), "drag_data_received",
1074                          G_CALLBACK(bookmark_dnd_get_data), bm);
1075
1076         bookmark_widget_list = g_list_append(bookmark_widget_list, bm);
1077
1078         return scrolled;
1079 }
1080
1081 void bookmark_list_set_key(GtkWidget *list, const gchar *key)
1082 {
1083         BookMarkData *bm;
1084
1085         if (!list || !key) return;
1086
1087         bm = g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY);
1088         if (!bm) return;
1089
1090         if (bm->key && strcmp(bm->key, key) == 0) return;
1091
1092         g_free(bm->key);
1093         bm->key = g_strdup(key);
1094
1095         bookmark_populate(bm);
1096 }
1097
1098 void bookmark_list_set_no_defaults(GtkWidget *list, gint no_defaults)
1099 {
1100         BookMarkData *bm;
1101
1102         bm = g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY);
1103         if (!bm) return;
1104
1105         bm->no_defaults = no_defaults;
1106 }
1107
1108 void bookmark_list_set_editable(GtkWidget *list, gint editable)
1109 {
1110         BookMarkData *bm;
1111
1112         bm = g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY);
1113         if (!bm) return;
1114
1115         bm->editable = editable;
1116 }
1117
1118 void bookmark_list_add(GtkWidget *list, const gchar *name, const gchar *path)
1119 {
1120         BookMarkData *bm;
1121         gchar *buf;
1122
1123         bm = g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY);
1124         if (!bm) return;
1125
1126         buf = bookmark_string(name, path, NULL);
1127         history_list_add_to_key(bm->key, buf, 0);
1128         g_free(buf);
1129
1130         bookmark_populate_all(bm->key);
1131 }
1132
1133 void bookmark_add_default(const gchar *name, const gchar *path)
1134 {
1135         if (!name || !path) return;
1136         bookmark_default_list = g_list_append(bookmark_default_list, g_strdup(name));
1137         bookmark_default_list = g_list_append(bookmark_default_list, g_strdup(path));
1138 }
1139
1140 /*
1141  *-----------------------------------------------------------------------------
1142  * combo with history key
1143  *-----------------------------------------------------------------------------
1144  */
1145
1146 typedef struct _HistoryComboData HistoryComboData;
1147 struct _HistoryComboData
1148 {
1149         GtkWidget *combo;
1150         GtkWidget *entry;
1151         gchar *history_key;
1152         gint history_levels;
1153 };
1154
1155 static void history_combo_destroy(GtkWidget *widget, gpointer data)
1156 {
1157         HistoryComboData *hc = data;
1158
1159         g_free(hc->history_key);
1160         g_free(data);
1161 }
1162
1163 /* if text is NULL, entry is set to the most recent item */
1164 GtkWidget *history_combo_new(GtkWidget **entry, const gchar *text,
1165                              const gchar *history_key, gint max_levels)
1166 {
1167         HistoryComboData *hc;
1168         GList *work;
1169         gint n = 0;
1170
1171         hc = g_new0(HistoryComboData, 1);
1172         hc->history_key = g_strdup(history_key);
1173         hc->history_levels = max_levels;
1174
1175         hc->combo = gtk_combo_box_entry_new_text();
1176 #if 0
1177         gtk_combo_set_case_sensitive(GTK_COMBO(hc->combo), TRUE);
1178         gtk_combo_set_use_arrows(GTK_COMBO(hc->combo), FALSE);
1179 #endif
1180
1181         hc->entry = GTK_BIN(hc->combo)->child;
1182
1183         g_object_set_data(G_OBJECT(hc->combo), "history_combo_data", hc);
1184         g_object_set_data(G_OBJECT(hc->entry), "history_combo_data", hc);
1185         g_signal_connect(G_OBJECT(hc->combo), "destroy",
1186                          G_CALLBACK(history_combo_destroy), hc);
1187
1188         work = history_list_get_by_key(hc->history_key);
1189         while (work)
1190                 {
1191                 gtk_combo_box_append_text(GTK_COMBO_BOX(hc->combo), (gchar *)work->data);
1192                 work = work->next;
1193                 n++;
1194                 }
1195
1196         if (text)
1197                 {
1198                 gtk_entry_set_text(GTK_ENTRY(hc->entry), text);
1199                 }
1200         else if (n > 0)
1201                 {
1202                 gtk_combo_box_set_active(GTK_COMBO_BOX(hc->combo), 0);
1203                 }
1204
1205         if (entry) *entry = hc->entry;
1206         return hc->combo;
1207 }
1208
1209 /* if text is NULL, current entry text is used
1210  * widget can be the combo or entry widget
1211  */
1212 void history_combo_append_history(GtkWidget *widget, const gchar *text)
1213 {
1214         HistoryComboData *hc;
1215         gchar *new_text;
1216
1217         hc = g_object_get_data(G_OBJECT(widget), "history_combo_data");
1218         if (!hc)
1219                 {
1220                 printf("widget is not a history combo\n");
1221                 return;
1222                 }
1223
1224         if (text)
1225                 {
1226                 new_text = g_strdup(text);
1227                 }
1228         else
1229                 {
1230                 new_text = g_strdup(gtk_entry_get_text(GTK_ENTRY(hc->entry)));
1231                 }
1232
1233         if (new_text && strlen(new_text) > 0)
1234                 {
1235                 GtkTreeModel *store;
1236                 GList *work;
1237
1238                 history_list_add_to_key(hc->history_key, new_text, hc->history_levels);
1239
1240                 gtk_combo_box_set_active(GTK_COMBO_BOX(hc->combo), -1);
1241
1242                 store = gtk_combo_box_get_model(GTK_COMBO_BOX(hc->combo));
1243                 gtk_list_store_clear(GTK_LIST_STORE(store));
1244
1245                 work = history_list_get_by_key(hc->history_key);
1246                 while (work)
1247                         {
1248                         gtk_combo_box_append_text(GTK_COMBO_BOX(hc->combo), (gchar *)work->data);
1249                         work = work->next;
1250                         }
1251                 }
1252
1253         g_free(new_text);
1254 }
1255
1256 /*
1257  *-----------------------------------------------------------------------------
1258  * drag and drop uri utils
1259  *-----------------------------------------------------------------------------
1260  */
1261
1262 /* the following characters are allowed to be unencoded for pathnames:
1263  *     $ & + , / : = @
1264  */
1265 static gint escape_char_list[] = {
1266         1, 1, 1, 1, 1, 1, 1, 1, 1, 1,   /*   0 */
1267         1, 1, 1, 1, 1, 1, 1, 1, 1, 1,   /*  10 */
1268         1, 1, 1, 1, 1, 1, 1, 1, 1, 1,   /*  20 */
1269 /*           spc !  "  #  $  %  &  '           */
1270         1, 1, 0, 0, 1, 1, 0, 1, 0, 0,   /*  30 */
1271 /*      (  )  *  +  ,  -  .  /  0  1           */
1272         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   /*  40 */
1273 /*      2  3  4  5  6  7  8  9  :  ;           */
1274         0, 0, 0, 0, 0, 0, 0, 0, 0, 1,   /*  50 */
1275 /*      <  =  >  ?  @  A  B  C  D  E           */
1276         1, 0, 1, 1, 0, 0, 0, 0, 0, 0,   /*  60 */
1277         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   /*  70 */
1278         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   /*  80 */
1279 /*      Z  [  \  ]  ^  _  `  a  b  c           */
1280         0, 1, 1, 1, 1, 0, 1, 0, 0, 0,   /*  90 */
1281         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   /* 100 */
1282         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   /* 110 */
1283 /*      x  y  z  {  |  }  ~ del                */
1284         0, 0, 0, 1, 1, 1, 0, 0          /* 120, 127 is end */
1285 };
1286
1287 static gchar *hex_char = "0123456789ABCDEF";
1288
1289 static gint escape_test(guchar c)
1290 {
1291         if (c < 32 || c > 127) return TRUE;
1292         return (escape_char_list[c] != 0);
1293 }
1294
1295 static const gchar *escape_code(guchar c)
1296 {
1297         static gchar text[4];
1298
1299         text[0] = '%';
1300         text[1] = hex_char[c>>4];
1301         text[2] = hex_char[c%16];
1302         text[3] = '\0';
1303
1304         return text;
1305 }
1306
1307 gchar *uri_text_escape(const gchar *text)
1308 {
1309         GString *string;
1310         gchar *result;
1311         const gchar *p;
1312
1313         if (!text) return NULL;
1314
1315         string = g_string_new("");
1316
1317         p = text;
1318         while (*p != '\0')
1319                 {
1320                 if (escape_test(*p))
1321                         {
1322                         g_string_append(string, escape_code(*p));
1323                         }
1324                 else
1325                         {
1326                         g_string_append_c(string, *p);
1327                         }
1328                 p++;
1329                 }
1330
1331         result = string->str;
1332         g_string_free(string, FALSE);
1333
1334         /* dropped filenames are expected to be utf-8 compatible */
1335         if (!g_utf8_validate(result, -1, NULL))
1336                 {
1337                 gchar *tmp;
1338
1339                 tmp = g_locale_to_utf8(result, -1, NULL, NULL, NULL);
1340                 if (tmp)
1341                         {
1342                         g_free(result);
1343                         result = tmp;
1344                         }
1345                 }
1346
1347         return result;
1348 }
1349
1350 /* this operates on the passed string, decoding escaped characters */
1351 void uri_text_decode(gchar *text)
1352 {
1353         if (strchr(text, '%'))
1354                 {
1355                 gchar *w;
1356                 gchar *r;
1357
1358                 w = r = text;
1359
1360                 while(*r != '\0')
1361                         {
1362                         if (*r == '%' && *(r + 1) != '\0' && *(r + 2) != '\0')
1363                                 {
1364                                 gchar t[3];
1365                                 gint n;
1366
1367                                 r++;
1368                                 t[0] = *r;
1369                                 r++;
1370                                 t[1] = *r;
1371                                 t[2] = '\0';
1372                                 n = (gint)strtol(t, NULL, 16);
1373                                 if (n > 0 && n < 256)
1374                                         {
1375                                         *w = (gchar)n;
1376                                         }
1377                                 else
1378                                         {
1379                                         /* invalid number, rewind and ignore this escape */
1380                                         r -= 2;
1381                                         *w = *r;
1382                                         }
1383                                 }
1384                         else if (w != r)
1385                                 {
1386                                 *w = *r;
1387                                 }
1388                         r++;
1389                         w++;
1390                         }
1391                 if (*w != '\0') *w = '\0';
1392                 }
1393 }
1394
1395 static void uri_list_parse_encoded_chars(GList *list)
1396 {
1397         GList *work = list;
1398
1399         while (work)
1400                 {
1401                 gchar *text = work->data;
1402
1403                 uri_text_decode(text);
1404
1405                 work = work->next;
1406                 }
1407 }
1408
1409 GList *uri_list_from_text(gchar *data, gint files_only)
1410 {
1411         GList *list = NULL;
1412         gint b, e;
1413
1414         b = e = 0;
1415
1416         while (data[b] != '\0')
1417                 {
1418                 while (data[e] != '\r' && data[e] != '\n' && data[e] != '\0') e++;
1419                 if (strncmp(data + b, "file:", 5) == 0)
1420                         {
1421                         gchar *path;
1422                         b += 5;
1423                         while (data[b] == '/' && data[b+1] == '/') b++;
1424                         path = g_strndup(data + b, e - b);
1425                         list = g_list_append(list, path_to_utf8(path));
1426                         g_free(path);
1427                         }
1428                 else if (!files_only && strncmp(data + b, "http:", 5) == 0)
1429                         {
1430                         list = g_list_append(list, g_strndup(data + b, e - b));
1431                         }
1432                 else if (!files_only && strncmp(data + b, "ftp:", 3) == 0)
1433                         {
1434                         list = g_list_append(list, g_strndup(data + b, e - b));
1435                         }
1436                 while (data[e] == '\r' || data[e] == '\n') e++;
1437                 b = e;
1438                 }
1439
1440         uri_list_parse_encoded_chars(list);
1441
1442         return list;
1443 }
1444
1445 GList *uri_filelist_from_text(gchar *data, gint files_only)
1446 {
1447         GList *path_list = uri_list_from_text(data, files_only);
1448         GList *filelist = filelist_from_path_list(path_list);
1449         string_list_free(path_list);
1450         return filelist;
1451 }
1452
1453 gchar *uri_text_from_list(GList *list, gint *len, gint plain_text)
1454 {
1455         gchar *uri_text = NULL;
1456         GString *string;
1457         GList *work;
1458
1459         if (!list)
1460                 {
1461                 if (len) *len = 0;
1462                 return NULL;
1463                 }
1464
1465         string = g_string_new("");
1466
1467         work = list;
1468         while (work)
1469                 {
1470                 const gchar *name8;     /* dnd filenames are in utf-8 */
1471
1472                 name8 = work->data;
1473
1474                 if (!plain_text)
1475                         {
1476                         gchar *escaped;
1477
1478                         escaped = uri_text_escape(name8);
1479                         g_string_append(string, "file:");
1480                         g_string_append(string, escaped);
1481                         g_free(escaped);
1482
1483                         g_string_append(string, "\r\n");
1484                         }
1485                 else
1486                         {
1487                         g_string_append(string, name8);
1488                         if (work->next) g_string_append(string, "\n");
1489                         }
1490
1491                 work = work->next;
1492                 }
1493
1494         uri_text = string->str;
1495         if (len) *len = string->len;
1496         g_string_free(string, FALSE);
1497
1498         return uri_text;
1499 }
1500
1501 gchar *uri_text_from_filelist(GList *list, gint *len, gint plain_text)
1502 {
1503         GList *path_list = filelist_to_path_list(list);
1504         gchar *ret = uri_text_from_list(path_list, len, plain_text);
1505         string_list_free(path_list);
1506         return ret;
1507 }