diff options
Diffstat (limited to 'src/caja-bookmark-list.c')
-rw-r--r-- | src/caja-bookmark-list.c | 775 |
1 files changed, 775 insertions, 0 deletions
diff --git a/src/caja-bookmark-list.c b/src/caja-bookmark-list.c new file mode 100644 index 00000000..17f9b961 --- /dev/null +++ b/src/caja-bookmark-list.c @@ -0,0 +1,775 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Caja + * + * Copyright (C) 1999, 2000 Eazel, Inc. + * + * Caja is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Caja is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Authors: John Sullivan <[email protected]> + */ + +/* caja-bookmark-list.c - implementation of centralized list of bookmarks. + */ + +#include <config.h> +#include "caja-bookmark-list.h" + +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-file.h> +#include <libcaja-private/caja-icon-names.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-string.h> +#include <gio/gio.h> + +#define MAX_BOOKMARK_LENGTH 80 +#define LOAD_JOB 1 +#define SAVE_JOB 2 + +enum +{ + CONTENTS_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; +static char *window_geometry; +static CajaBookmarkList *singleton = NULL; + +/* forward declarations */ + +static void caja_bookmark_list_load_file (CajaBookmarkList *bookmarks); +static void caja_bookmark_list_save_file (CajaBookmarkList *bookmarks); + +G_DEFINE_TYPE(CajaBookmarkList, caja_bookmark_list, G_TYPE_OBJECT) + +static CajaBookmark * +new_bookmark_from_uri (const char *uri, const char *label) +{ + CajaBookmark *new_bookmark; + CajaFile *file; + char *name; + GIcon *icon; + gboolean has_label; + GFile *location; + gboolean native; + + location = NULL; + if (uri) + { + location = g_file_new_for_uri (uri); + } + + has_label = FALSE; + if (!label) + { + name = caja_compute_title_for_location (location); + } + else + { + name = g_strdup (label); + has_label = TRUE; + } + + new_bookmark = NULL; + + if (uri) + { + native = g_file_is_native (location); + file = caja_file_get (location); + + icon = NULL; + if (caja_file_check_if_ready (file, + CAJA_FILE_ATTRIBUTES_FOR_ICON)) + { + icon = caja_file_get_gicon (file, 0); + } + caja_file_unref (file); + + if (icon == NULL) + { + icon = native ? g_themed_icon_new (CAJA_ICON_FOLDER) : + g_themed_icon_new (CAJA_ICON_FOLDER_REMOTE); + } + + new_bookmark = caja_bookmark_new (location, name, has_label, icon); + + g_object_unref (icon); + + } + g_free (name); + g_object_unref (location); + return new_bookmark; +} + +static GFile * +caja_bookmark_list_get_file (void) +{ + char *filename; + GFile *file; + + filename = g_build_filename (g_get_home_dir (), + ".gtk-bookmarks", + NULL); + file = g_file_new_for_path (filename); + + g_free (filename); + + return file; +} + +/* Initialization. */ + +static void +bookmark_in_list_changed_callback (CajaBookmark *bookmark, + CajaBookmarkList *bookmarks) +{ + g_assert (CAJA_IS_BOOKMARK (bookmark)); + g_assert (CAJA_IS_BOOKMARK_LIST (bookmarks)); + + /* Save changes so we'll have the good icon next time. */ + caja_bookmark_list_save_file (bookmarks); +} + +static void +stop_monitoring_bookmark (CajaBookmarkList *bookmarks, + CajaBookmark *bookmark) +{ + g_signal_handlers_disconnect_by_func (bookmark, + bookmark_in_list_changed_callback, + bookmarks); +} + +static void +stop_monitoring_one (gpointer data, gpointer user_data) +{ + g_assert (CAJA_IS_BOOKMARK (data)); + g_assert (CAJA_IS_BOOKMARK_LIST (user_data)); + + stop_monitoring_bookmark (CAJA_BOOKMARK_LIST (user_data), + CAJA_BOOKMARK (data)); +} + +static void +clear (CajaBookmarkList *bookmarks) +{ + g_list_foreach (bookmarks->list, stop_monitoring_one, bookmarks); + eel_g_object_list_free (bookmarks->list); + bookmarks->list = NULL; +} + +static void +do_finalize (GObject *object) +{ + if (CAJA_BOOKMARK_LIST (object)->monitor != NULL) + { + g_file_monitor_cancel (CAJA_BOOKMARK_LIST (object)->monitor); + CAJA_BOOKMARK_LIST (object)->monitor = NULL; + } + + g_queue_free (CAJA_BOOKMARK_LIST (object)->pending_ops); + + clear (CAJA_BOOKMARK_LIST (object)); + + G_OBJECT_CLASS (caja_bookmark_list_parent_class)->finalize (object); +} + +static GObject * +do_constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GObject *retval; + + if (singleton != NULL) + { + return g_object_ref (singleton); + } + + retval = G_OBJECT_CLASS (caja_bookmark_list_parent_class)->constructor + (type, n_construct_params, construct_params); + + singleton = CAJA_BOOKMARK_LIST (retval); + g_object_add_weak_pointer (retval, (gpointer) &singleton); + + return retval; +} + + +static void +caja_bookmark_list_class_init (CajaBookmarkListClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->finalize = do_finalize; + object_class->constructor = do_constructor; + + signals[CONTENTS_CHANGED] = + g_signal_new ("contents_changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (CajaBookmarkListClass, + contents_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +bookmark_monitor_changed_cb (GFileMonitor *monitor, + GFile *child, + GFile *other_file, + GFileMonitorEvent eflags, + gpointer user_data) +{ + if (eflags == G_FILE_MONITOR_EVENT_CHANGED || + eflags == G_FILE_MONITOR_EVENT_CREATED) + { + g_return_if_fail (CAJA_IS_BOOKMARK_LIST (CAJA_BOOKMARK_LIST (user_data))); + caja_bookmark_list_load_file (CAJA_BOOKMARK_LIST (user_data)); + } +} + +static void +caja_bookmark_list_init (CajaBookmarkList *bookmarks) +{ + GFile *file; + + bookmarks->pending_ops = g_queue_new (); + + caja_bookmark_list_load_file (bookmarks); + + file = caja_bookmark_list_get_file (); + bookmarks->monitor = g_file_monitor_file (file, 0, NULL, NULL); + g_file_monitor_set_rate_limit (bookmarks->monitor, 1000); + + g_signal_connect (bookmarks->monitor, "changed", + G_CALLBACK (bookmark_monitor_changed_cb), bookmarks); + + g_object_unref (file); +} + +static void +insert_bookmark_internal (CajaBookmarkList *bookmarks, + CajaBookmark *bookmark, + int index) +{ + bookmarks->list = g_list_insert (bookmarks->list, bookmark, index); + + g_signal_connect_object (bookmark, "contents_changed", + G_CALLBACK (bookmark_in_list_changed_callback), bookmarks, 0); +} + +/** + * caja_bookmark_list_append: + * + * Append a bookmark to a bookmark list. + * @bookmarks: CajaBookmarkList to append to. + * @bookmark: Bookmark to append a copy of. + **/ +void +caja_bookmark_list_append (CajaBookmarkList *bookmarks, + CajaBookmark *bookmark) +{ + g_return_if_fail (CAJA_IS_BOOKMARK_LIST (bookmarks)); + g_return_if_fail (CAJA_IS_BOOKMARK (bookmark)); + + insert_bookmark_internal (bookmarks, + caja_bookmark_copy (bookmark), + -1); + + caja_bookmark_list_save_file (bookmarks); +} + +/** + * caja_bookmark_list_contains: + * + * Check whether a bookmark with matching name and url is already in the list. + * @bookmarks: CajaBookmarkList to check contents of. + * @bookmark: CajaBookmark to match against. + * + * Return value: TRUE if matching bookmark is in list, FALSE otherwise + **/ +gboolean +caja_bookmark_list_contains (CajaBookmarkList *bookmarks, + CajaBookmark *bookmark) +{ + g_return_val_if_fail (CAJA_IS_BOOKMARK_LIST (bookmarks), FALSE); + g_return_val_if_fail (CAJA_IS_BOOKMARK (bookmark), FALSE); + + return g_list_find_custom (bookmarks->list, + (gpointer)bookmark, + caja_bookmark_compare_with) + != NULL; +} + +/** + * caja_bookmark_list_delete_item_at: + * + * Delete the bookmark at the specified position. + * @bookmarks: the list of bookmarks. + * @index: index, must be less than length of list. + **/ +void +caja_bookmark_list_delete_item_at (CajaBookmarkList *bookmarks, + guint index) +{ + GList *doomed; + + g_return_if_fail (CAJA_IS_BOOKMARK_LIST (bookmarks)); + g_return_if_fail (index < g_list_length (bookmarks->list)); + + doomed = g_list_nth (bookmarks->list, index); + bookmarks->list = g_list_remove_link (bookmarks->list, doomed); + + g_assert (CAJA_IS_BOOKMARK (doomed->data)); + stop_monitoring_bookmark (bookmarks, CAJA_BOOKMARK (doomed->data)); + g_object_unref (doomed->data); + + g_list_free_1 (doomed); + + caja_bookmark_list_save_file (bookmarks); +} + +/** + * caja_bookmark_list_move_item: + * + * Move the item from the given position to the destination. + * @index: the index of the first bookmark. + * @destination: the index of the second bookmark. + **/ +void +caja_bookmark_list_move_item (CajaBookmarkList *bookmarks, + guint index, + guint destination) +{ + GList *bookmark_item; + + if (index == destination) + { + return; + } + + bookmark_item = g_list_nth (bookmarks->list, index); + bookmarks->list = g_list_remove_link (bookmarks->list, + bookmark_item); + + if (index < destination) + { + bookmarks->list = g_list_insert (bookmarks->list, + bookmark_item->data, + destination - 1); + } + else + { + bookmarks->list = g_list_insert (bookmarks->list, + bookmark_item->data, + destination); + } + + caja_bookmark_list_save_file (bookmarks); +} + +/** + * caja_bookmark_list_delete_items_with_uri: + * + * Delete all bookmarks with the given uri. + * @bookmarks: the list of bookmarks. + * @uri: The uri to match. + **/ +void +caja_bookmark_list_delete_items_with_uri (CajaBookmarkList *bookmarks, + const char *uri) +{ + GList *node, *next; + gboolean list_changed; + char *bookmark_uri; + + g_return_if_fail (CAJA_IS_BOOKMARK_LIST (bookmarks)); + g_return_if_fail (uri != NULL); + + list_changed = FALSE; + for (node = bookmarks->list; node != NULL; node = next) + { + next = node->next; + + bookmark_uri = caja_bookmark_get_uri (CAJA_BOOKMARK (node->data)); + if (eel_strcmp (bookmark_uri, uri) == 0) + { + bookmarks->list = g_list_remove_link (bookmarks->list, node); + stop_monitoring_bookmark (bookmarks, CAJA_BOOKMARK (node->data)); + g_object_unref (node->data); + g_list_free_1 (node); + list_changed = TRUE; + } + g_free (bookmark_uri); + } + + if (list_changed) + { + caja_bookmark_list_save_file (bookmarks); + } +} + +/** + * caja_bookmark_list_get_window_geometry: + * + * Get a string representing the bookmark_list's window's geometry. + * This is the value set earlier by caja_bookmark_list_set_window_geometry. + * @bookmarks: the list of bookmarks associated with the window. + * Return value: string representation of window's geometry, suitable for + * passing to mate_parse_geometry(), or NULL if + * no window geometry has yet been saved for this bookmark list. + **/ +const char * +caja_bookmark_list_get_window_geometry (CajaBookmarkList *bookmarks) +{ + return window_geometry; +} + +/** + * caja_bookmark_list_insert_item: + * + * Insert a bookmark at a specified position. + * @bookmarks: the list of bookmarks. + * @index: the position to insert the bookmark at. + * @new_bookmark: the bookmark to insert a copy of. + **/ +void +caja_bookmark_list_insert_item (CajaBookmarkList *bookmarks, + CajaBookmark *new_bookmark, + guint index) +{ + g_return_if_fail (CAJA_IS_BOOKMARK_LIST (bookmarks)); + g_return_if_fail (index <= g_list_length (bookmarks->list)); + + insert_bookmark_internal (bookmarks, + caja_bookmark_copy (new_bookmark), + index); + + caja_bookmark_list_save_file (bookmarks); +} + +/** + * caja_bookmark_list_item_at: + * + * Get the bookmark at the specified position. + * @bookmarks: the list of bookmarks. + * @index: index, must be less than length of list. + * + * Return value: the bookmark at position @index in @bookmarks. + **/ +CajaBookmark * +caja_bookmark_list_item_at (CajaBookmarkList *bookmarks, guint index) +{ + g_return_val_if_fail (CAJA_IS_BOOKMARK_LIST (bookmarks), NULL); + g_return_val_if_fail (index < g_list_length (bookmarks->list), NULL); + + return CAJA_BOOKMARK (g_list_nth_data (bookmarks->list, index)); +} + +/** + * caja_bookmark_list_length: + * + * Get the number of bookmarks in the list. + * @bookmarks: the list of bookmarks. + * + * Return value: the length of the bookmark list. + **/ +guint +caja_bookmark_list_length (CajaBookmarkList *bookmarks) +{ + g_return_val_if_fail (CAJA_IS_BOOKMARK_LIST(bookmarks), 0); + + return g_list_length (bookmarks->list); +} + +static void +load_file_finish (CajaBookmarkList *bookmarks, + GObject *source, + GAsyncResult *res) +{ + GError *error = NULL; + gchar *contents = NULL; + + g_file_load_contents_finish (G_FILE (source), + res, &contents, NULL, NULL, &error); + + if (error == NULL) + { + char **lines; + int i; + + lines = g_strsplit (contents, "\n", -1); + for (i = 0; lines[i]; i++) + { + /* Ignore empty or invalid lines that cannot be parsed properly */ + if (lines[i][0] != '\0' && lines[i][0] != ' ') + { + /* gtk 2.7/2.8 might have labels appended to bookmarks which are separated by a space */ + /* we must seperate the bookmark uri and the potential label */ + char *space, *label; + + label = NULL; + space = strchr (lines[i], ' '); + if (space) + { + *space = '\0'; + label = g_strdup (space + 1); + } + insert_bookmark_internal (bookmarks, + new_bookmark_from_uri (lines[i], label), + -1); + + g_free (label); + } + } + g_free (contents); + g_strfreev (lines); + + g_signal_emit (bookmarks, signals[CONTENTS_CHANGED], 0); + } + else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + g_warning ("Could not load bookmark file: %s\n", error->message); + g_error_free (error); + } +} + +static void +load_file_async (CajaBookmarkList *self, + GAsyncReadyCallback callback) +{ + GFile *file; + + file = caja_bookmark_list_get_file (); + + /* Wipe out old list. */ + clear (self); + + /* keep the bookmark list alive */ + g_object_ref (self); + g_file_load_contents_async (file, NULL, callback, self); + + g_object_unref (file); +} + +static void +save_file_finish (CajaBookmarkList *bookmarks, + GObject *source, + GAsyncResult *res) +{ + GError *error = NULL; + GFile *file; + + g_file_replace_contents_finish (G_FILE (source), + res, NULL, &error); + + if (error != NULL) + { + g_warning ("Unable to replace contents of the bookmarks file: %s", + error->message); + g_error_free (error); + } + + file = caja_bookmark_list_get_file (); + + /* re-enable bookmark file monitoring */ + bookmarks->monitor = g_file_monitor_file (file, 0, NULL, NULL); + g_file_monitor_set_rate_limit (bookmarks->monitor, 1000); + g_signal_connect (bookmarks->monitor, "changed", + G_CALLBACK (bookmark_monitor_changed_cb), bookmarks); + + g_object_unref (file); +} + +static void +save_file_async (CajaBookmarkList *bookmarks, + GAsyncReadyCallback callback) +{ + GFile *file; + GList *l; + GString *bookmark_string; + + /* temporarily disable bookmark file monitoring when writing file */ + if (bookmarks->monitor != NULL) + { + g_file_monitor_cancel (bookmarks->monitor); + bookmarks->monitor = NULL; + } + + file = caja_bookmark_list_get_file (); + bookmark_string = g_string_new (NULL); + + for (l = bookmarks->list; l; l = l->next) + { + CajaBookmark *bookmark; + + bookmark = CAJA_BOOKMARK (l->data); + + /* make sure we save label if it has one for compatibility with GTK 2.7 and 2.8 */ + if (caja_bookmark_get_has_custom_name (bookmark)) + { + char *label, *uri; + label = caja_bookmark_get_name (bookmark); + uri = caja_bookmark_get_uri (bookmark); + g_string_append_printf (bookmark_string, + "%s %s\n", uri, label); + g_free (uri); + g_free (label); + } + else + { + char *uri; + uri = caja_bookmark_get_uri (bookmark); + g_string_append_printf (bookmark_string, "%s\n", uri); + g_free (uri); + } + } + + /* keep the bookmark list alive */ + g_object_ref (bookmarks); + g_file_replace_contents_async (file, bookmark_string->str, + bookmark_string->len, NULL, + FALSE, 0, NULL, callback, + bookmarks); + + g_object_unref (file); +} + +static void +process_next_op (CajaBookmarkList *bookmarks); + +static void +op_processed_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + CajaBookmarkList *self = user_data; + int op; + + op = GPOINTER_TO_INT (g_queue_pop_tail (self->pending_ops)); + + if (op == LOAD_JOB) + { + load_file_finish (self, source, res); + } + else + { + save_file_finish (self, source, res); + } + + if (!g_queue_is_empty (self->pending_ops)) + { + process_next_op (self); + } + + /* release the reference acquired during the _async method */ + g_object_unref (self); +} + +static void +process_next_op (CajaBookmarkList *bookmarks) +{ + gint op; + + op = GPOINTER_TO_INT (g_queue_peek_tail (bookmarks->pending_ops)); + + if (op == LOAD_JOB) + { + load_file_async (bookmarks, op_processed_cb); + } + else + { + save_file_async (bookmarks, op_processed_cb); + } +} + +/** + * caja_bookmark_list_load_file: + * + * Reads bookmarks from file, clobbering contents in memory. + * @bookmarks: the list of bookmarks to fill with file contents. + **/ +static void +caja_bookmark_list_load_file (CajaBookmarkList *bookmarks) +{ + g_queue_push_head (bookmarks->pending_ops, GINT_TO_POINTER (LOAD_JOB)); + + if (g_queue_get_length (bookmarks->pending_ops) == 1) + { + process_next_op (bookmarks); + } +} + +/** + * caja_bookmark_list_save_file: + * + * Save bookmarks to disk. + * @bookmarks: the list of bookmarks to save. + **/ +static void +caja_bookmark_list_save_file (CajaBookmarkList *bookmarks) +{ + g_signal_emit (bookmarks, signals[CONTENTS_CHANGED], 0); + + g_queue_push_head (bookmarks->pending_ops, GINT_TO_POINTER (SAVE_JOB)); + + if (g_queue_get_length (bookmarks->pending_ops) == 1) + { + process_next_op (bookmarks); + } +} + +/** + * caja_bookmark_list_new: + * + * Create a new bookmark_list, with contents read from disk. + * + * Return value: A pointer to the new widget. + **/ +CajaBookmarkList * +caja_bookmark_list_new (void) +{ + CajaBookmarkList *list; + + list = CAJA_BOOKMARK_LIST (g_object_new (CAJA_TYPE_BOOKMARK_LIST, NULL)); + + return list; +} + +/** + * caja_bookmark_list_set_window_geometry: + * + * Set a bookmarks window's geometry (position & size), in string form. This is + * stored to disk by this class, and can be retrieved later in + * the same session or in a future session. + * @bookmarks: the list of bookmarks associated with the window. + * @geometry: the new window geometry string. + **/ +void +caja_bookmark_list_set_window_geometry (CajaBookmarkList *bookmarks, + const char *geometry) +{ + g_return_if_fail (CAJA_IS_BOOKMARK_LIST (bookmarks)); + g_return_if_fail (geometry != NULL); + + g_free (window_geometry); + window_geometry = g_strdup (geometry); + + caja_bookmark_list_save_file (bookmarks); +} + |