comparison src/ui_bookmark.c @ 9:d907d608745f

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