2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA
20 /* bookmarks.c - handles the bookmarks menu */
30 #include "bookmarks.h"
35 #include "gui_support.h"
42 static GList
*history
= NULL
; /* Most recent first */
43 static GList
*history_tail
= NULL
; /* Oldest item */
44 static GHashTable
*history_hash
= NULL
; /* Path -> GList link */
45 static gint history_free
= 30; /* Space left in history */
47 static XMLwrapper
*bookmarks
= NULL
;
48 static GtkWidget
*bookmarks_window
= NULL
;
50 /* Static prototypes */
51 static void update_bookmarks(void);
52 static xmlNode
*bookmark_find(const gchar
*mark
);
53 static void bookmarks_save(void);
54 static void bookmarks_add(GtkMenuItem
*menuitem
, gpointer user_data
);
55 static void bookmarks_activate(GtkMenuShell
*item
, FilerWindow
*filer_window
);
56 static GtkWidget
*bookmarks_build_menu(FilerWindow
*filer_window
);
57 static void position_menu(GtkMenu
*menu
, gint
*x
, gint
*y
,
58 gboolean
*push_in
, gpointer data
);
59 static void cell_edited(GtkCellRendererText
*cell
,
60 const gchar
*path_string
,
61 const gchar
*new_text
,
63 static void reorder_up(GtkButton
*button
, GtkTreeView
*view
);
64 static void reorder_down(GtkButton
*button
, GtkTreeView
*view
);
65 static void edit_response(GtkWidget
*window
, gint response
,
67 static void edit_delete(GtkButton
*button
, GtkTreeView
*view
);
68 static gboolean
dir_dropped(GtkWidget
*window
, GdkDragContext
*context
,
70 GtkSelectionData
*selection_data
, guint info
,
71 guint time
, GtkTreeView
*view
);
72 static void bookmarks_add_dir(const guchar
*dir
);
73 static void commit_edits(GtkTreeModel
*model
);
76 /****************************************************************
77 * EXTERNAL INTERFACE *
78 ****************************************************************/
80 /* Shows the bookmarks menu */
81 void bookmarks_show_menu(FilerWindow
*filer_window
)
87 event
= gtk_get_current_event();
88 if (event
->type
== GDK_BUTTON_RELEASE
||
89 event
->type
== GDK_BUTTON_PRESS
)
90 button
= ((GdkEventButton
*) event
)->button
;
91 gdk_event_free(event
);
93 menu
= GTK_MENU(bookmarks_build_menu(filer_window
));
94 gtk_menu_popup(menu
, NULL
, NULL
, position_menu
, filer_window
,
95 button
, gtk_get_current_event_time());
98 /* Show the Edit Bookmarks dialog */
99 void bookmarks_edit(void)
102 GtkWidget
*list
, *hbox
, *button
, *swin
;
103 GtkTreeSelection
*selection
;
104 GtkCellRenderer
*cell
;
108 if (bookmarks_window
)
110 gtk_window_present(GTK_WINDOW(bookmarks_window
));
116 bookmarks_window
= gtk_dialog_new();
119 gtk_dialog_add_button(GTK_DIALOG(bookmarks_window
),
120 GTK_STOCK_CLOSE
, GTK_RESPONSE_OK
);
122 g_signal_connect(bookmarks_window
, "destroy",
123 G_CALLBACK(gtk_widget_destroyed
), &bookmarks_window
);
124 g_signal_connect(bookmarks_window
, "destroy",
125 G_CALLBACK(one_less_window
), NULL
);
127 swin
= gtk_scrolled_window_new(NULL
, NULL
);
128 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin
),
130 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin
),
131 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
132 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(bookmarks_window
)->vbox
),
133 swin
, TRUE
, TRUE
, 0);
135 model
= gtk_list_store_new(2, G_TYPE_STRING
, G_TYPE_STRING
);
137 list
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(model
));
139 cell
= gtk_cell_renderer_text_new();
140 g_signal_connect(G_OBJECT(cell
), "edited",
141 G_CALLBACK(cell_edited
), model
);
142 g_object_set(G_OBJECT(cell
), "editable", TRUE
, NULL
);
143 g_object_set_data(G_OBJECT(cell
), "column", GINT_TO_POINTER(0));
144 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(list
), -1,
145 _("Path"), cell
, "text", 0, NULL
);
147 cell
= gtk_cell_renderer_text_new();
148 g_signal_connect(G_OBJECT(cell
), "edited",
149 G_CALLBACK(cell_edited
), model
);
150 g_object_set(G_OBJECT(cell
), "editable", TRUE
, NULL
);
151 g_object_set_data(G_OBJECT(cell
), "column", GINT_TO_POINTER(1));
152 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(list
), -1,
153 _("Title"), cell
, "text", 1, NULL
);
155 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(list
), TRUE
);
156 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list
), TRUE
);
158 node
= xmlDocGetRootElement(bookmarks
->doc
);
159 for (node
= node
->xmlChildrenNode
; node
; node
= node
->next
)
164 if (node
->type
!= XML_ELEMENT_NODE
)
166 if (strcmp(node
->name
, "bookmark") != 0)
169 mark
= xmlNodeListGetString(bookmarks
->doc
,
170 node
->xmlChildrenNode
, 1);
174 title
=xmlGetProp(node
, "title");
178 gtk_list_store_append(model
, &iter
);
179 gtk_list_store_set(model
, &iter
, 0, mark
, 1, title
, -1);
186 gtk_widget_set_size_request(list
, 300, 300);
187 gtk_container_add(GTK_CONTAINER(swin
), list
);
189 hbox
= gtk_hbutton_box_new();
190 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(bookmarks_window
)->vbox
),
191 hbox
, FALSE
, TRUE
, 0);
192 gtk_container_set_border_width(GTK_CONTAINER(hbox
), 5);
194 button
= gtk_button_new_from_stock(GTK_STOCK_DELETE
);
195 gtk_box_pack_start(GTK_BOX(hbox
), button
, FALSE
, TRUE
, 0);
196 g_signal_connect(button
, "clicked", G_CALLBACK(edit_delete
), list
);
198 button
= gtk_button_new_from_stock(GTK_STOCK_GO_UP
);
199 gtk_box_pack_start(GTK_BOX(hbox
), button
, FALSE
, TRUE
, 0);
200 g_signal_connect(button
, "clicked", G_CALLBACK(reorder_up
), list
);
201 button
= gtk_button_new_from_stock(GTK_STOCK_GO_DOWN
);
202 gtk_box_pack_start(GTK_BOX(hbox
), button
, FALSE
, TRUE
, 0);
203 g_signal_connect(button
, "clicked", G_CALLBACK(reorder_down
), list
);
205 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(list
));
206 gtk_tree_selection_set_mode(selection
, GTK_SELECTION_MULTIPLE
);
208 /* Select the first item, otherwise the first click starts edit
209 * mode, which is very confusing!
211 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
212 gtk_tree_selection_select_iter(selection
, &iter
);
214 g_signal_connect(bookmarks_window
, "response",
215 G_CALLBACK(edit_response
), model
);
217 /* Allow directories to be dropped in */
219 GtkTargetEntry targets
[] = { {"text/uri-list", 0, 0} };
220 gtk_drag_dest_set(bookmarks_window
, GTK_DEST_DEFAULT_ALL
,
221 targets
, G_N_ELEMENTS(targets
),
222 GDK_ACTION_COPY
|GDK_ACTION_PRIVATE
);
223 g_signal_connect(bookmarks_window
, "drag-data-received",
224 G_CALLBACK(dir_dropped
), list
);
227 g_signal_connect_swapped(model
, "row-changed",
228 G_CALLBACK(commit_edits
), model
);
229 g_signal_connect_swapped(model
, "row-inserted",
230 G_CALLBACK(commit_edits
), model
);
231 g_signal_connect_swapped(model
, "row-deleted",
232 G_CALLBACK(commit_edits
), model
);
233 g_signal_connect_swapped(model
, "rows-reordered",
234 G_CALLBACK(commit_edits
), model
);
236 gtk_widget_show_all(bookmarks_window
);
239 static void history_remove(const char *path
)
243 old
= g_hash_table_lookup(history_hash
, path
);
246 g_hash_table_remove(history_hash
, path
);
248 if (history_tail
== old
)
249 history_tail
= old
->prev
;
251 history
= g_list_delete_link(history
, old
);
257 /* Add this path to the global history of visited directories. If it
258 * already exists there, make it the most recent. If its parent exists
259 * already, remove the parent.
261 void bookmarks_add_history(const gchar
*path
)
265 new = g_strdup(path
);
269 history_hash
= g_hash_table_new(g_str_hash
, g_str_equal
);
275 parent
= g_dirname(path
);
276 history_remove(parent
);
280 history
= g_list_prepend(history
, new);
282 history_tail
= history
;
283 g_hash_table_insert(history_hash
, new, history
);
286 if (history_free
== -1)
288 g_return_if_fail(history_tail
!= NULL
);
289 history_remove((char *) history_tail
->data
);
293 void bookmarks_add_uri(const EscapedPath
*uri
)
298 path
= get_local_path(uri
);
302 delayed_error(_("Can't bookmark non-local resource '%s'\n"),
307 if (mc_stat(path
, &info
) == 0 && S_ISDIR(info
.st_mode
))
308 bookmarks_add_dir(path
);
310 delayed_error(_("'%s' isn't a directory"), path
);
314 /****************************************************************
315 * INTERNAL FUNCTIONS *
316 ****************************************************************/
318 /* Initialise the bookmarks document to be empty. Does not save. */
319 static void bookmarks_new(void)
322 g_object_unref(G_OBJECT(bookmarks
));
323 bookmarks
= xml_new(NULL
);
324 bookmarks
->doc
= xmlNewDoc("1.0");
325 xmlDocSetRootElement(bookmarks
->doc
,
326 xmlNewDocNode(bookmarks
->doc
, NULL
, "bookmarks", NULL
));
329 static void position_menu(GtkMenu
*menu
, gint
*x
, gint
*y
,
330 gboolean
*push_in
, gpointer data
)
332 FilerWindow
*filer_window
= (FilerWindow
*) data
;
334 gdk_window_get_origin(GTK_WIDGET(filer_window
->view
)->window
, x
, y
);
337 /* Makes sure that 'bookmarks' is up-to-date, reloading from file if it has
338 * changed. If no bookmarks were loaded and there is no file then initialise
339 * bookmarks to an empty document.
341 static void update_bookmarks()
345 /* Update the bookmarks, if possible */
346 path
= choices_find_xdg_path_load("Bookmarks.xml", PROJECT
, SITE
);
350 wrapper
= xml_cache_load(path
);
354 g_object_unref(bookmarks
);
365 /* Return the node for the 'mark' bookmark */
366 static xmlNode
*bookmark_find(const gchar
*mark
)
372 node
= xmlDocGetRootElement(bookmarks
->doc
);
374 for (node
= node
->xmlChildrenNode
; node
; node
= node
->next
)
379 if (node
->type
!= XML_ELEMENT_NODE
)
381 if (strcmp(node
->name
, "bookmark") != 0)
384 path
= xmlNodeListGetString(bookmarks
->doc
,
385 node
->xmlChildrenNode
, 1);
389 same
= strcmp(mark
, path
) == 0;
399 /* Save the bookmarks to a file */
400 static void bookmarks_save()
404 save_path
= choices_find_xdg_path_save("Bookmarks.xml", PROJECT
, SITE
,
408 save_xml_file(bookmarks
->doc
, save_path
);
413 /* Add a bookmark if it doesn't already exist, and save the
416 static void bookmarks_add(GtkMenuItem
*menuitem
, gpointer user_data
)
418 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
420 bookmarks_add_dir(filer_window
->sym_path
);
423 static void bookmarks_add_dir(const guchar
*dir
)
427 if (bookmark_find(dir
))
430 bookmark
= xmlNewTextChild(xmlDocGetRootElement(bookmarks
->doc
),
431 NULL
, "bookmark", dir
);
432 xmlSetProp(bookmark
, "title", dir
);
436 if (bookmarks_window
)
437 gtk_widget_destroy(bookmarks_window
);
440 /* Called when a bookmark has been chosen */
441 static void bookmarks_activate(GtkMenuShell
*item
, FilerWindow
*filer_window
)
446 gboolean new_win
=FALSE
;
448 mark
=g_object_get_data(G_OBJECT(item
), "bookmark-path");
450 label
= GTK_LABEL(GTK_BIN(item
)->child
);
451 mark
= gtk_label_get_text(label
);
454 event
=gtk_get_current_event();
457 if(event
->type
==GDK_BUTTON_PRESS
||
458 event
->type
==GDK_BUTTON_RELEASE
)
460 GdkEventButton
*button
=(GdkEventButton
*) event
;
462 new_win
=o_new_button_1
.int_value
?
463 button
->button
==1: button
->button
!=1;
465 gdk_event_free(event
);
468 if (strcmp(mark
, filer_window
->sym_path
) != 0)
471 filer_opendir(mark
, filer_window
, NULL
);
473 filer_change_to(filer_window
, mark
, NULL
);
475 if (g_hash_table_lookup(fstab_mounts
, filer_window
->real_path
) &&
476 !mount_is_mounted(filer_window
->real_path
, NULL
, NULL
))
480 paths
= g_list_prepend(NULL
, filer_window
->real_path
);
481 action_mount(paths
, FALSE
, TRUE
, -1);
486 static void edit_delete(GtkButton
*button
, GtkTreeView
*view
)
490 GtkTreeSelection
*selection
;
492 gboolean more
, any
= FALSE
;
494 model
= gtk_tree_view_get_model(view
);
495 list
= GTK_LIST_STORE(model
);
497 selection
= gtk_tree_view_get_selection(view
);
499 more
= gtk_tree_model_get_iter_first(model
, &iter
);
503 GtkTreeIter old
= iter
;
505 more
= gtk_tree_model_iter_next(model
, &iter
);
507 if (gtk_tree_selection_iter_is_selected(selection
, &old
))
510 gtk_list_store_remove(list
, &old
);
516 report_error(_("You should first select some rows to delete"));
521 static void reorder(GtkTreeView
*view
, int dir
)
525 GtkTreePath
*cursor
= NULL
;
526 GtkTreeIter iter
, old
, new;
531 g_return_if_fail(view
!= NULL
);
532 g_return_if_fail(dir
== 1 || dir
== -1);
534 model
= gtk_tree_view_get_model(view
);
535 list
= GTK_LIST_STORE(model
);
537 gtk_tree_view_get_cursor(view
, &cursor
, NULL
);
540 report_error(_("Put the cursor on an entry in the "
545 gtk_tree_model_get_iter(model
, &old
, cursor
);
548 gtk_tree_path_next(cursor
);
549 ok
= gtk_tree_model_get_iter(model
, &iter
, cursor
);
553 ok
= gtk_tree_path_prev(cursor
);
555 gtk_tree_model_get_iter(model
, &iter
, cursor
);
559 gtk_tree_path_free(cursor
);
560 report_error(_("This item is already at the end"));
564 gtk_tree_model_get_value(model
, &old
, 0, &mark
);
565 gtk_tree_model_get_value(model
, &old
, 1, &title
);
567 gtk_list_store_insert_after(list
, &new, &iter
);
569 gtk_list_store_insert_before(list
, &new, &iter
);
570 gtk_list_store_set(list
, &new, 0, g_value_get_string(&mark
), -1);
571 gtk_list_store_set(list
, &new, 1, g_value_get_string(&title
), -1);
572 gtk_list_store_remove(list
, &old
);
574 g_value_unset(&mark
);
575 g_value_unset(&title
);
577 gtk_tree_view_set_cursor(view
, cursor
, 0, FALSE
);
578 gtk_tree_path_free(cursor
);
581 static void reorder_up(GtkButton
*button
, GtkTreeView
*view
)
586 static void reorder_down(GtkButton
*button
, GtkTreeView
*view
)
591 static gboolean
dir_dropped(GtkWidget
*window
, GdkDragContext
*context
,
593 GtkSelectionData
*selection_data
, guint info
,
594 guint time
, GtkTreeView
*view
)
599 if (!selection_data
->data
)
602 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
606 model
= GTK_LIST_STORE(gtk_tree_view_get_model(view
));
608 uris
= uri_list_to_glist(selection_data
->data
);
610 for (next
= uris
; next
; next
= next
->next
)
614 path
= get_local_path((EscapedPath
*) next
->data
);
621 if (mc_stat(path
, &info
) == 0 && S_ISDIR(info
.st_mode
))
623 gtk_list_store_append(model
, &iter
);
624 gtk_list_store_set(model
, &iter
, 0, path
,
628 delayed_error(_("'%s' isn't a directory"),
634 delayed_error(_("Can't bookmark non-local directories "
635 "like '%s'"), (gchar
*) next
->data
);
638 destroy_glist(&uris
);
643 static void commit_edits(GtkTreeModel
*model
)
649 if (gtk_tree_model_get_iter_first(model
, &iter
))
651 GValue mark
= {0}, title
={0};
652 xmlNode
*root
= xmlDocGetRootElement(bookmarks
->doc
);
658 gtk_tree_model_get_value(model
, &iter
, 0, &mark
);
659 bookmark
= xmlNewTextChild(root
, NULL
, "bookmark",
660 g_value_get_string(&mark
));
661 g_value_unset(&mark
);
662 gtk_tree_model_get_value(model
, &iter
, 1, &title
);
663 xmlSetProp(bookmark
, "title",
664 g_value_get_string(&title
));
665 g_value_unset(&title
);
666 } while (gtk_tree_model_iter_next(model
, &iter
));
672 static void edit_response(GtkWidget
*window
, gint response
, GtkTreeModel
*model
)
676 gtk_widget_destroy(window
);
679 static void cell_edited(GtkCellRendererText
*cell
,
680 const gchar
*path_string
,
681 const gchar
*new_text
,
684 GtkTreeModel
*model
= (GtkTreeModel
*) data
;
689 path
= gtk_tree_path_new_from_string(path_string
);
690 gtk_tree_model_get_iter(model
, &iter
, path
);
691 gtk_tree_path_free(path
);
692 col
=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell
), "column"));
694 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
, col
, new_text
, -1);
697 static void activate_edit(GtkMenuShell
*item
, gpointer data
)
702 static gint
cmp_dirname(gconstpointer a
, gconstpointer b
)
704 return g_utf8_collate(*(gchar
**) a
, *(gchar
**) b
);
707 static void free_path_for_item(GtkWidget
*widget
, gpointer udata
)
709 gchar
*path
=(gchar
*) udata
;
713 static GtkWidget
*build_history_menu(FilerWindow
*filer_window
)
720 menu
= gtk_menu_new();
725 g_return_val_if_fail(history_hash
!= NULL
, menu
);
726 g_return_val_if_fail(history_tail
!= NULL
, menu
);
728 items
= g_ptr_array_new();
730 for (next
= history
; next
; next
= next
->next
)
731 g_ptr_array_add(items
, next
->data
);
733 g_ptr_array_sort(items
, cmp_dirname
);
735 for (i
= 0; i
< items
->len
; i
++)
738 const char *path
= (char *) items
->pdata
[i
];
741 item
= gtk_menu_item_new_with_label(path
);
744 g_object_set_data(G_OBJECT(item
), "bookmark-path", copy
);
745 g_signal_connect(item
, "destroy",
746 G_CALLBACK(free_path_for_item
), copy
);
748 if (strcmp(path
, filer_window
->sym_path
) == 0)
749 gtk_widget_set_sensitive(item
, FALSE
);
751 g_signal_connect(item
, "activate",
752 G_CALLBACK(bookmarks_activate
),
755 gtk_widget_show(item
);
756 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
759 g_ptr_array_free(items
, TRUE
);
764 /* Builds the bookmarks' menu. Done whenever the bookmarks icon has been
767 static GtkWidget
*bookmarks_build_menu(FilerWindow
*filer_window
)
772 gboolean need_separator
= TRUE
;
774 menu
= gtk_menu_new();
776 item
= gtk_menu_item_new_with_label(_("Add New Bookmark"));
777 g_signal_connect(item
, "activate",
778 G_CALLBACK(bookmarks_add
), filer_window
);
779 gtk_widget_show(item
);
780 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
781 gtk_menu_shell_select_item(GTK_MENU_SHELL(menu
), item
);
783 item
= gtk_menu_item_new_with_label(_("Edit Bookmarks"));
784 g_signal_connect(item
, "activate", G_CALLBACK(activate_edit
), NULL
);
785 gtk_widget_show(item
);
786 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
788 item
= gtk_menu_item_new_with_label(_("Recently Visited"));
789 gtk_widget_show(item
);
790 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
791 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item
),
792 build_history_menu(filer_window
));
794 /* Now add all the bookmarks to the menu */
798 node
= xmlDocGetRootElement(bookmarks
->doc
);
800 for (node
= node
->xmlChildrenNode
; node
; node
= node
->next
)
802 gchar
*mark
, *title
, *path
;
804 if (node
->type
!= XML_ELEMENT_NODE
)
806 if (strcmp(node
->name
, "bookmark") != 0)
809 mark
= xmlNodeListGetString(bookmarks
->doc
,
810 node
->xmlChildrenNode
, 1);
815 title
=xmlGetProp(node
, "title");
819 item
= gtk_menu_item_new_with_label(title
);
821 g_object_set_data(G_OBJECT(item
), "bookmark-path", path
);
822 g_signal_connect(item
, "destroy",
823 G_CALLBACK(free_path_for_item
), path
);
829 g_signal_connect(item
, "activate",
830 G_CALLBACK(bookmarks_activate
),
833 gtk_widget_show(item
);
838 sep
= gtk_separator_menu_item_new();
839 gtk_widget_show(sep
);
840 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), sep
);
841 need_separator
= FALSE
;
844 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);