summaryrefslogtreecommitdiff
path: root/libnautilus-private/nautilus-merged-directory.c
diff options
context:
space:
mode:
authorDarin Adler <darin@src.gnome.org>2000-08-22 02:11:45 +0000
committerDarin Adler <darin@src.gnome.org>2000-08-22 02:11:45 +0000
commit7295589544bf5b28024f25ab5783e0f8ec273502 (patch)
tree203998c60e5bc03a794a5162a8be544efe1bbeaf /libnautilus-private/nautilus-merged-directory.c
parent509e16baa8b3854b9cfab973b7b01cb11652a0ed (diff)
downloadnautilus-7295589544bf5b28024f25ab5783e0f8ec273502.tar.gz
Finished task 571 (Make a Trash "virtual directory" with trash
from all volumes). Some loose ends are already reported in bugs 2146, 2243, and 2244. * libnautilus-extensions/Makefile.am: * libnautilus-extensions/nautilus-merged-directory.h: * libnautilus-extensions/nautilus-merged-directory.c: Moved all the guts of NautilusTrashDirectory that are solely about having a single directory that's a union of a bunch of real directories into a separate base class. Also finished the implementation, leaving only unimportant loose ends. * libnautilus-extensions/nautilus-trash-directory.h: * libnautilus-extensions/nautilus-trash-directory.c: (get_volume_vfs_uri_if_writable), (find_directory_callback), (add_volume), (remove_trash_volume), (remove_volume), (add_one_volume), (volume_mounted_callback), (volume_unmounted_callback), (nautilus_trash_directory_initialize), (remove_trash_volume_cover), (trash_destroy), (nautilus_trash_directory_initialize_class): Added the code to find the trash on all volumes. * libnautilus-extensions/nautilus-glib-extensions.h: * libnautilus-extensions/nautilus-glib-extensions.c: (flatten_hash_table_element), (nautilus_g_hash_table_safe_for_each): Added a new version of the hash table iterator that works even if the callback removes items from the hash table. * libnautilus-extensions/nautilus-scalable-font.c: (nautilus_scalable_font_largest_fitting_font_size): Added code to handle the case of an empty name; the old code worked with NULL, but not with "". * src/file-manager/fm-desktop-icon-view.c: (fm_desktop_icon_view_trash_state_changed_callback), (find_and_rename_trash_link), (create_or_rename_trash): * src/file-manager/nautilus-trash-monitor.c: (nautilus_trash_monitor_initialize): Changed to use the new "trash:" URL instead of locating one of the trash folders. * src/file-manager/fm-directory-view.h: * src/file-manager/fm-directory-view.c: (fm_directory_view_initialize_class), (fm_directory_all_selected_items_in_trash): * src/file-manager/fm-search-list-view.c: (fm_search_list_view_initialize_class): The "share_parent" optimization didn't work for the new trash. Instead of fixing it, I just removed it. * src/nautilus-window-manage-views.c: (compute_default_title), (nautilus_window_update_title): Made two changes to the default title: 1) Use "" instead of "Nautilus" when there's no title. We still use "Nautilus" in the window title, but not in the sidebar any more. 2) Use the scheme part of a URI if there's nothing after the colon. This works nicely for the trash. * libnautilus-extensions/nautilus-background-canvas-group.c: (nautilus_background_canvas_group_render): Removed some unneeded code and the FIXME that goes with it. * libnautilus-extensions/nautilus-directory.c: (nautilus_directory_is_not_empty): Removed an overzealous assert.
Diffstat (limited to 'libnautilus-private/nautilus-merged-directory.c')
-rw-r--r--libnautilus-private/nautilus-merged-directory.c553
1 files changed, 553 insertions, 0 deletions
diff --git a/libnautilus-private/nautilus-merged-directory.c b/libnautilus-private/nautilus-merged-directory.c
new file mode 100644
index 000000000..b3c268efd
--- /dev/null
+++ b/libnautilus-private/nautilus-merged-directory.c
@@ -0,0 +1,553 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ nautilus-merged-directory.c: Subclass of NautilusDirectory to implement the
+ virtual merged directory.
+
+ Copyright (C) 1999, 2000 Eazel, Inc.
+
+ This program 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.
+
+ This program 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., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <darin@eazel.com>
+*/
+
+#include <config.h>
+#include "nautilus-merged-directory.h"
+
+#include "nautilus-directory-private.h"
+#include "nautilus-file.h"
+#include "nautilus-glib-extensions.h"
+#include "nautilus-gtk-macros.h"
+#include <gtk/gtksignal.h>
+
+struct NautilusMergedDirectoryDetails {
+ GList *directories;
+ GHashTable *callbacks;
+ GHashTable *monitors;
+};
+
+typedef struct {
+ /* Basic configuration. */
+ NautilusMergedDirectory *merged;
+ NautilusDirectoryCallback callback;
+ gpointer callback_data;
+
+ GList *wait_for_attributes;
+ gboolean wait_for_metadata;
+
+ GList *non_ready_directories;
+ GList *merged_file_list;
+} MergedCallback;
+
+typedef struct {
+ NautilusMergedDirectory *merged;
+
+ GList *monitor_attributes;
+ gboolean monitor_metadata;
+ gboolean force_reload;
+} MergedMonitor;
+
+static void nautilus_merged_directory_initialize (gpointer object,
+ gpointer klass);
+static void nautilus_merged_directory_initialize_class (gpointer klass);
+static void remove_all_real_directories (NautilusMergedDirectory *merged);
+static guint merged_callback_hash (gconstpointer merged_callback);
+static gboolean merged_callback_equal (gconstpointer merged_callback,
+ gconstpointer merged_callback_2);
+
+NAUTILUS_DEFINE_CLASS_BOILERPLATE (NautilusMergedDirectory,
+ nautilus_merged_directory,
+ NAUTILUS_TYPE_DIRECTORY)
+
+static void
+nautilus_merged_directory_initialize (gpointer object, gpointer klass)
+{
+ NautilusMergedDirectory *merged;
+
+ merged = NAUTILUS_MERGED_DIRECTORY (object);
+
+ merged->details = g_new0 (NautilusMergedDirectoryDetails, 1);
+ merged->details->callbacks = g_hash_table_new
+ (merged_callback_hash, merged_callback_equal);
+ merged->details->monitors = g_hash_table_new
+ (g_direct_hash, g_direct_equal);
+}
+
+static void
+merged_destroy (GtkObject *object)
+{
+ NautilusMergedDirectory *merged;
+
+ merged = NAUTILUS_MERGED_DIRECTORY (object);
+
+ remove_all_real_directories (merged);
+
+ if (g_hash_table_size (merged->details->callbacks) != 0) {
+ g_warning ("call_when_ready still pending when merged virtual directory is destroyed");
+ }
+ if (g_hash_table_size (merged->details->monitors) != 0) {
+ g_warning ("file monitor still active when merged virtual directory is destroyed");
+ }
+
+ g_hash_table_destroy (merged->details->callbacks);
+ g_hash_table_destroy (merged->details->monitors);
+ g_free (merged->details);
+
+ NAUTILUS_CALL_PARENT_CLASS (GTK_OBJECT_CLASS, destroy, (object));
+}
+
+static guint
+merged_callback_hash (gconstpointer merged_callback_as_pointer)
+{
+ const MergedCallback *merged_callback;
+
+ merged_callback = merged_callback_as_pointer;
+ return GPOINTER_TO_UINT (merged_callback->callback)
+ ^ GPOINTER_TO_UINT (merged_callback->callback_data);
+}
+
+static gboolean
+merged_callback_equal (gconstpointer merged_callback_as_pointer,
+ gconstpointer merged_callback_as_pointer_2)
+{
+ const MergedCallback *merged_callback, *merged_callback_2;
+
+ merged_callback = merged_callback_as_pointer;
+ merged_callback_2 = merged_callback_as_pointer_2;
+
+ return merged_callback->callback == merged_callback_2->callback
+ && merged_callback->callback_data == merged_callback_2->callback_data;
+}
+
+/* Return true if any directory in the list does. */
+static gboolean
+merged_contains_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ NautilusMergedDirectory *merged;
+ GList *p;
+
+ merged = NAUTILUS_MERGED_DIRECTORY (directory);
+
+ for (p = merged->details->directories; p != NULL; p = p->next) {
+ if (nautilus_directory_contains_file (p->data, file)) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void
+merged_callback_destroy (MergedCallback *merged_callback)
+{
+ g_assert (merged_callback != NULL);
+ g_assert (NAUTILUS_IS_MERGED_DIRECTORY (merged_callback->merged));
+
+ nautilus_g_list_free_deep (merged_callback->wait_for_attributes);
+ g_list_free (merged_callback->non_ready_directories);
+ nautilus_file_list_free (merged_callback->merged_file_list);
+ g_free (merged_callback);
+}
+
+static void
+merged_callback_check_done (MergedCallback *merged_callback)
+{
+ /* Check if we are ready. */
+ if (merged_callback->non_ready_directories != NULL) {
+ return;
+ }
+
+ /* Remove from the hash table before sending it. */
+ g_hash_table_remove (merged_callback->merged->details->callbacks, merged_callback);
+
+ /* We are ready, so do the real callback. */
+ (* merged_callback->callback) (NAUTILUS_DIRECTORY (merged_callback->merged),
+ merged_callback->merged_file_list,
+ merged_callback->callback_data);
+
+ /* And we are done. */
+ merged_callback_destroy (merged_callback);
+}
+
+static void
+merged_callback_remove_directory (MergedCallback *merged_callback,
+ NautilusDirectory *directory)
+{
+ merged_callback->non_ready_directories = g_list_remove
+ (merged_callback->non_ready_directories,
+ directory);
+
+ /* Check if we are ready. */
+ merged_callback_check_done (merged_callback);
+}
+
+static void
+directory_ready_callback (NautilusDirectory *directory,
+ GList *files,
+ gpointer callback_data)
+{
+ MergedCallback *merged_callback;
+
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+ g_assert (callback_data != NULL);
+
+ merged_callback = callback_data;
+ g_assert (g_list_find (merged_callback->non_ready_directories, directory) != NULL);
+
+ /* Update based on this call. */
+ merged_callback->merged_file_list = g_list_concat
+ (merged_callback->merged_file_list,
+ nautilus_file_list_copy (files));
+
+ /* Check if we are ready. */
+ merged_callback_remove_directory (merged_callback,
+ directory);
+}
+
+static void
+merged_callback_connect_directory (MergedCallback *merged_callback,
+ NautilusDirectory *real_merged)
+{
+ nautilus_directory_call_when_ready
+ (real_merged,
+ merged_callback->wait_for_attributes,
+ merged_callback->wait_for_metadata,
+ directory_ready_callback, merged_callback);
+}
+
+static void
+merged_call_when_ready (NautilusDirectory *directory,
+ GList *file_attributes,
+ gboolean wait_for_metadata,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ NautilusMergedDirectory *merged;
+ MergedCallback search_key, *merged_callback;
+ GList *p;
+
+ merged = NAUTILUS_MERGED_DIRECTORY (directory);
+
+ /* Check to be sure we aren't overwriting. */
+ search_key.callback = callback;
+ search_key.callback_data = callback_data;
+ if (g_hash_table_lookup (merged->details->callbacks, &search_key) != NULL) {
+ g_warning ("tried to add a new callback while an old one was pending");
+ return;
+ }
+
+ /* Create a merged_callback record. */
+ merged_callback = g_new0 (MergedCallback, 1);
+ merged_callback->merged = merged;
+ merged_callback->callback = callback;
+ merged_callback->callback_data = callback_data;
+ merged_callback->wait_for_attributes = nautilus_g_str_list_copy (file_attributes);
+ merged_callback->wait_for_metadata = wait_for_metadata;
+ for (p = merged->details->directories; p != NULL; p = p->next) {
+ merged_callback->non_ready_directories = g_list_prepend
+ (merged_callback->non_ready_directories, p->data);
+ }
+
+ /* Put it in the hash table. */
+ g_hash_table_insert (merged->details->callbacks,
+ merged_callback, merged_callback);
+
+ /* Handle the pathological case where there are no directories. */
+ if (merged->details->directories == NULL) {
+ merged_callback_check_done (merged_callback);
+ }
+
+ /* Now tell all the directories about it. */
+ for (p = merged->details->directories; p != NULL; p = p->next) {
+ merged_callback_connect_directory (merged_callback, p->data);
+ }
+}
+
+static void
+merged_cancel_callback (NautilusDirectory *directory,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ NautilusMergedDirectory *merged;
+ MergedCallback search_key, *merged_callback;
+ GList *p;
+
+ merged = NAUTILUS_MERGED_DIRECTORY (directory);
+
+ /* Find the entry in the table. */
+ search_key.callback = callback;
+ search_key.callback_data = callback_data;
+ merged_callback = g_hash_table_lookup (merged->details->callbacks, &search_key);
+ if (merged_callback == NULL) {
+ return;
+ }
+
+ /* Remove from the hash table before working with it. */
+ g_hash_table_remove (merged_callback->merged->details->callbacks, merged_callback);
+
+ /* Tell all the directories to cancel the call. */
+ for (p = merged_callback->non_ready_directories; p != NULL; p = p->next) {
+ nautilus_directory_cancel_callback
+ (p->data,
+ directory_ready_callback, merged_callback);
+ }
+ merged_callback_destroy (merged_callback);
+}
+
+/* Create a monitor on each of the directories in the list. */
+static void
+merged_file_monitor_add (NautilusDirectory *directory,
+ gconstpointer client,
+ GList *file_attributes,
+ gboolean monitor_metadata,
+ gboolean force_reload)
+{
+ NautilusMergedDirectory *merged;
+ MergedMonitor *monitor;
+ GList *p;
+
+ merged = NAUTILUS_MERGED_DIRECTORY (directory);
+
+ /* Map the client to a unique value so this doesn't interfere
+ * with direct monitoring of the directory by the same client.
+ */
+ monitor = g_hash_table_lookup (merged->details->monitors, client);
+ if (monitor != NULL) {
+ g_assert (monitor->merged == merged);
+ nautilus_g_list_free_deep (monitor->monitor_attributes);
+ } else {
+ monitor = g_new0 (MergedMonitor, 1);
+ monitor->merged = merged;
+ g_hash_table_insert (merged->details->monitors,
+ (gpointer) client, monitor);
+ }
+ monitor->monitor_attributes = nautilus_g_str_list_copy (file_attributes);
+ monitor->monitor_metadata = monitor_metadata;
+ monitor->force_reload = force_reload;
+
+ /* Call through to the real directory add calls. */
+ for (p = merged->details->directories; p != NULL; p = p->next) {
+ nautilus_directory_file_monitor_add
+ (p->data, monitor,
+ file_attributes, monitor_metadata, force_reload);
+ }
+}
+
+/* Remove the monitor from each of the directories in the list. */
+static void
+merged_file_monitor_remove (NautilusDirectory *directory,
+ gconstpointer client)
+{
+ NautilusMergedDirectory *merged;
+ MergedMonitor *monitor;
+ GList *p;
+
+ merged = NAUTILUS_MERGED_DIRECTORY (directory);
+
+ /* Map the client to the value used by the earlier add call. */
+ monitor = g_hash_table_lookup (merged->details->monitors, client);
+ if (monitor == NULL) {
+ return;
+ }
+ g_hash_table_remove (merged->details->monitors, client);
+
+ /* Call through to the real directory remove calls. */
+ for (p = merged->details->directories; p != NULL; p = p->next) {
+ nautilus_directory_file_monitor_remove
+ (p->data, monitor);
+ }
+
+ nautilus_g_list_free_deep (monitor->monitor_attributes);
+ g_free (monitor);
+}
+
+/* Return true only if all directories in the list do. */
+static gboolean
+merged_are_all_files_seen (NautilusDirectory *directory)
+{
+ NautilusMergedDirectory *merged;
+ GList *p;
+
+ merged = NAUTILUS_MERGED_DIRECTORY (directory);
+
+ for (p = merged->details->directories; p != NULL; p = p->next) {
+ if (!nautilus_directory_are_all_files_seen (p->data)) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/* Return true if any directory in the list does. */
+static gboolean
+merged_is_not_empty (NautilusDirectory *directory)
+{
+ NautilusMergedDirectory *merged;
+ GList *p;
+
+ merged = NAUTILUS_MERGED_DIRECTORY (directory);
+
+ for (p = merged->details->directories; p != NULL; p = p->next) {
+ if (nautilus_directory_is_not_empty (p->data)) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void
+nautilus_merged_directory_initialize_class (gpointer klass)
+{
+ GtkObjectClass *object_class;
+ NautilusDirectoryClass *directory_class;
+
+ object_class = GTK_OBJECT_CLASS (klass);
+ directory_class = NAUTILUS_DIRECTORY_CLASS (klass);
+
+ object_class->destroy = merged_destroy;
+
+ directory_class->contains_file = merged_contains_file;
+ directory_class->call_when_ready = merged_call_when_ready;
+ directory_class->cancel_callback = merged_cancel_callback;
+ directory_class->file_monitor_add = merged_file_monitor_add;
+ directory_class->file_monitor_remove = merged_file_monitor_remove;
+ directory_class->are_all_files_seen = merged_are_all_files_seen;
+ directory_class->is_not_empty = merged_is_not_empty;
+}
+
+static void
+forward_files_added_cover (NautilusDirectory *real_directory,
+ GList *files,
+ NautilusMergedDirectory *merged)
+{
+ nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (merged), files);
+}
+
+static void
+forward_files_changed_cover (NautilusDirectory *real_directory,
+ GList *files,
+ NautilusMergedDirectory *merged)
+{
+ nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (merged), files);
+}
+
+static void
+monitor_add_directory (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ MergedMonitor *monitor;
+
+ monitor = value;
+ nautilus_directory_file_monitor_add
+ (NAUTILUS_DIRECTORY (callback_data), monitor,
+ monitor->monitor_attributes,
+ monitor->monitor_metadata,
+ monitor->force_reload);
+}
+
+void
+nautilus_merged_directory_add_real_directory (NautilusMergedDirectory *merged,
+ NautilusDirectory *real_directory)
+{
+ g_return_if_fail (NAUTILUS_IS_MERGED_DIRECTORY (merged));
+ g_return_if_fail (NAUTILUS_IS_DIRECTORY (real_directory));
+ g_return_if_fail (!NAUTILUS_IS_MERGED_DIRECTORY (real_directory));
+
+ /* Quietly do nothing if asked to add something that's already there. */
+ if (g_list_find (merged->details->directories, real_directory) != NULL) {
+ return;
+ }
+
+ /* Add to our list of directories. */
+ nautilus_directory_ref (real_directory);
+ merged->details->directories = g_list_prepend
+ (merged->details->directories, real_directory);
+
+ /* Connect signals. */
+ gtk_signal_connect (GTK_OBJECT (real_directory),
+ "files_added",
+ forward_files_added_cover,
+ merged);
+ gtk_signal_connect (GTK_OBJECT (real_directory),
+ "files_changed",
+ forward_files_changed_cover,
+ merged);
+ /* FIXME: Hook up the done_loading signal too? */
+
+ /* Add the directory to any extant monitors. */
+ g_hash_table_foreach (merged->details->monitors,
+ monitor_add_directory,
+ real_directory);
+ /* FIXME: Do we need to add the directory to callbacks too? */
+}
+
+static void
+merged_callback_remove_directory_cover (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ merged_callback_remove_directory
+ (value, NAUTILUS_DIRECTORY (callback_data));
+}
+
+static void
+monitor_remove_directory (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ nautilus_directory_file_monitor_remove
+ (NAUTILUS_DIRECTORY (callback_data), value);
+}
+
+void
+nautilus_merged_directory_remove_real_directory (NautilusMergedDirectory *merged,
+ NautilusDirectory *real_directory)
+{
+ g_return_if_fail (NAUTILUS_IS_MERGED_DIRECTORY (merged));
+ g_return_if_fail (NAUTILUS_IS_DIRECTORY (real_directory));
+
+ /* Quietly do nothing if asked to remove something that's not there. */
+ if (g_list_find (merged->details->directories, real_directory) == NULL) {
+ return;
+ }
+
+ /* Remove this directory from callbacks and monitors. */
+ nautilus_g_hash_table_safe_for_each
+ (merged->details->callbacks,
+ merged_callback_remove_directory_cover,
+ real_directory);
+ g_hash_table_foreach
+ (merged->details->monitors,
+ monitor_remove_directory,
+ real_directory);
+
+ /* Disconnect all the signals. */
+ gtk_signal_disconnect_by_data (GTK_OBJECT (real_directory), merged);
+
+ /* Remove from our list of directories. */
+ merged->details->directories = g_list_remove
+ (merged->details->directories, real_directory);
+ nautilus_directory_unref (real_directory);
+}
+
+static void
+remove_all_real_directories (NautilusMergedDirectory *merged)
+{
+ while (merged->details->directories != NULL) {
+ nautilus_merged_directory_remove_real_directory
+ (merged, merged->details->directories->data);
+ }
+}