summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexandru Pandelea <alexandru.pandelea@gmail.com>2017-06-28 17:47:41 +0100
committerAlexandru Pandelea <alexandru.pandelea@gmail.com>2017-08-25 10:13:13 +0100
commit8cbdfdfa41d896a0be6d2057b6e77fc5f384d1c0 (patch)
tree36581d14d58b3498a9ffddcd58905dd3945475fd
parent921cd365a92cf4c9a4edaeab00fa4966c09da93b (diff)
downloadnautilus-wip/alexpandelea/favorites.tar.gz
Add favorite fileswip/alexpandelea/favorites
Add option to make files Favorite, by either toggling a star in the list view, or from the context menu. https://bugzilla.gnome.org/show_bug.cgi?id=786039
-rw-r--r--data/org.gnome.nautilus.gschema.xml4
-rw-r--r--eel/eel-vfs-extensions.c6
-rw-r--r--eel/eel-vfs-extensions.h1
-rw-r--r--src/meson.build6
-rw-r--r--src/nautilus-application.c14
-rw-r--r--src/nautilus-bookmark-list.c5
-rw-r--r--src/nautilus-column-utilities.c11
-rw-r--r--src/nautilus-directory.c14
-rw-r--r--src/nautilus-directory.h1
-rw-r--r--src/nautilus-favorite-directory.c585
-rw-r--r--src/nautilus-favorite-directory.h57
-rw-r--r--src/nautilus-file-undo-operations.c221
-rw-r--r--src/nautilus-file-undo-operations.h26
-rw-r--r--src/nautilus-file-utilities.c18
-rw-r--r--src/nautilus-file-utilities.h1
-rw-r--r--src/nautilus-file.c103
-rw-r--r--src/nautilus-file.h3
-rw-r--r--src/nautilus-files-view.c161
-rw-r--r--src/nautilus-list-view-private.h4
-rw-r--r--src/nautilus-list-view.c211
-rw-r--r--src/nautilus-pathbar.c12
-rw-r--r--src/nautilus-query.c19
-rw-r--r--src/nautilus-query.h4
-rw-r--r--src/nautilus-search-engine-model.c17
-rw-r--r--src/nautilus-search-engine-simple.c19
-rw-r--r--src/nautilus-search-engine-tracker.c5
-rw-r--r--src/nautilus-tag-manager.c887
-rw-r--r--src/nautilus-tag-manager.h57
-rw-r--r--src/nautilus-window.c14
-rw-r--r--src/resources/nautilus.gresource.xml2
-rw-r--r--src/resources/ui/nautilus-files-view-context-menus.ui17
-rw-r--r--src/resources/ui/nautilus-starred-is-empty.ui42
-rw-r--r--src/resources/ui/nautilus-tags-dialog.ui157
-rw-r--r--src/resources/ui/nautilus-window.ui2
34 files changed, 2674 insertions, 32 deletions
diff --git a/data/org.gnome.nautilus.gschema.xml b/data/org.gnome.nautilus.gschema.xml
index 023035872..235aeab83 100644
--- a/data/org.gnome.nautilus.gschema.xml
+++ b/data/org.gnome.nautilus.gschema.xml
@@ -257,12 +257,12 @@
<description>Default zoom level used by the list view.</description>
</key>
<key type="as" name="default-visible-columns">
- <default>[ 'name', 'size', 'date_modified' ]</default>
+ <default>[ 'name', 'size', 'date_modified', 'favorite']</default>
<summary>Default list of columns visible in the list view</summary>
<description>Default list of columns visible in the list view.</description>
</key>
<key type="as" name="default-column-order">
- <default>[ 'name', 'size', 'type', 'owner', 'group', 'permissions', 'mime_type', 'where', 'date_modified', 'date_modified_with_time', 'date_accessed' ]</default>
+ <default>[ 'name', 'size', 'type', 'owner', 'group', 'permissions', 'mime_type', 'where', 'date_modified', 'date_modified_with_time', 'date_accessed', 'recency', 'favorite' ]</default>
<summary>Default column order in the list view</summary>
<description>Default column order in the list view.</description>
</key>
diff --git a/eel/eel-vfs-extensions.c b/eel/eel-vfs-extensions.c
index 8bb33f7d2..6c626f7e3 100644
--- a/eel/eel-vfs-extensions.c
+++ b/eel/eel-vfs-extensions.c
@@ -38,6 +38,12 @@
#include <stdlib.h>
gboolean
+eel_uri_is_favorites (const gchar *uri)
+{
+ return g_str_has_prefix (uri, "favorites:");
+}
+
+gboolean
eel_uri_is_trash (const char *uri)
{
return g_str_has_prefix (uri, "trash:");
diff --git a/eel/eel-vfs-extensions.h b/eel/eel-vfs-extensions.h
index 8336efe51..0ad6b2be5 100644
--- a/eel/eel-vfs-extensions.h
+++ b/eel/eel-vfs-extensions.h
@@ -35,6 +35,7 @@ G_BEGIN_DECLS
#define EEL_DESKTOP_URI "x-nautilus-desktop:"
#define EEL_SEARCH_URI "x-nautilus-search:"
+gboolean eel_uri_is_favorites (const char *uri);
gboolean eel_uri_is_trash (const char *uri);
gboolean eel_uri_is_trash_folder (const char *uri);
gboolean eel_uri_is_in_trash (const char *uri);
diff --git a/src/meson.build b/src/meson.build
index 9f01f5ba2..46f010a0e 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -259,7 +259,11 @@ libnautilus_sources = [
'nautilus-batch-rename-utilities.c',
'nautilus-batch-rename-utilities.h',
'nautilus-search-engine-tracker.c',
- 'nautilus-search-engine-tracker.h'
+ 'nautilus-search-engine-tracker.h',
+ 'nautilus-tag-manager.c',
+ 'nautilus-tag-manager.h',
+ 'nautilus-favorite-directory.c',
+ 'nautilus-favorite-directory.h'
]
nautilus_deps = [glib,
diff --git a/src/nautilus-application.c b/src/nautilus-application.c
index 98e6f35d4..adcb7d3e7 100644
--- a/src/nautilus-application.c
+++ b/src/nautilus-application.c
@@ -39,6 +39,7 @@
#include "nautilus-window.h"
#include "nautilus-window-slot.h"
#include "nautilus-preferences-window.h"
+#include "nautilus-tag-manager.h"
#include "nautilus-directory-private.h"
#include "nautilus-file-utilities.h"
@@ -80,6 +81,9 @@ typedef struct
GHashTable *notifications;
NautilusFileUndoManager *undo_manager;
+
+ NautilusTagManager *tag_manager;
+ GCancellable *tag_manager_cancellable;
} NautilusApplicationPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (NautilusApplication, nautilus_application, GTK_TYPE_APPLICATION);
@@ -612,6 +616,11 @@ nautilus_application_finalize (GObject *object)
g_clear_object (&priv->undo_manager);
+ g_clear_object (&priv->tag_manager);
+
+ g_cancellable_cancel (priv->tag_manager_cancellable);
+ g_clear_object (&priv->tag_manager_cancellable);
+
G_OBJECT_CLASS (nautilus_application_parent_class)->finalize (object);
}
@@ -1103,6 +1112,11 @@ nautilus_application_init (NautilusApplication *self)
priv->undo_manager = nautilus_file_undo_manager_new ();
+ priv->tag_manager_cancellable = g_cancellable_new ();
+ priv->tag_manager = nautilus_tag_manager_get ();
+ nautilus_tag_manager_set_cancellable (priv->tag_manager,
+ priv->tag_manager_cancellable);
+
g_application_add_main_option_entries (G_APPLICATION (self), options);
nautilus_ensure_extension_points ();
diff --git a/src/nautilus-bookmark-list.c b/src/nautilus-bookmark-list.c
index d7a0ea092..926d949fe 100644
--- a/src/nautilus-bookmark-list.c
+++ b/src/nautilus-bookmark-list.c
@@ -643,6 +643,11 @@ nautilus_bookmark_list_can_bookmark_location (NautilusBookmarkList *list,
return FALSE;
}
+ if (nautilus_is_favorite_directory (location))
+ {
+ return FALSE;
+ }
+
bookmark = nautilus_bookmark_new (location, NULL);
is_builtin = nautilus_bookmark_get_is_builtin (bookmark);
g_object_unref (bookmark);
diff --git a/src/nautilus-column-utilities.c b/src/nautilus-column-utilities.c
index 1a2a0927c..22789938b 100644
--- a/src/nautilus-column-utilities.c
+++ b/src/nautilus-column-utilities.c
@@ -42,6 +42,7 @@ static const char *default_column_order[] =
"date_modified",
"date_accessed",
"recency",
+ "favorite",
NULL
};
@@ -149,6 +150,16 @@ get_builtin_columns (void)
"xalign", 1.0,
NULL));
+ columns = g_list_append (columns,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "favorite",
+ "attribute", "favorite",
+ "label", _("Star"),
+ "description", _("Shows if file is favorite."),
+ "default-sort-order", GTK_SORT_DESCENDING,
+ "xalign", 0.5,
+ NULL));
+
return columns;
}
diff --git a/src/nautilus-directory.c b/src/nautilus-directory.c
index a1b44a1ee..6a71747d6 100644
--- a/src/nautilus-directory.c
+++ b/src/nautilus-directory.c
@@ -27,6 +27,7 @@
#include "nautilus-file-private.h"
#include "nautilus-file-utilities.h"
#include "nautilus-search-directory.h"
+#include "nautilus-favorite-directory.h"
#include "nautilus-search-directory-file.h"
#include "nautilus-vfs-file.h"
#include "nautilus-global-preferences.h"
@@ -757,6 +758,19 @@ nautilus_directory_is_in_recent (NautilusDirectory *directory)
}
gboolean
+nautilus_directory_is_in_starred (NautilusDirectory *directory)
+{
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+
+ if (directory->details->location == NULL)
+ {
+ return FALSE;
+ }
+
+ return g_file_has_uri_scheme (directory->details->location, "favorites");
+}
+
+gboolean
nautilus_directory_is_in_admin (NautilusDirectory *directory)
{
g_assert (NAUTILUS_IS_DIRECTORY (directory));
diff --git a/src/nautilus-directory.h b/src/nautilus-directory.h
index 231384f3b..71b172f7c 100644
--- a/src/nautilus-directory.h
+++ b/src/nautilus-directory.h
@@ -225,6 +225,7 @@ gboolean nautilus_directory_is_local_or_fuse (NautilusDirector
gboolean nautilus_directory_is_in_trash (NautilusDirectory *directory);
gboolean nautilus_directory_is_in_recent (NautilusDirectory *directory);
+gboolean nautilus_directory_is_in_starred (NautilusDirectory *directory);
gboolean nautilus_directory_is_in_admin (NautilusDirectory *directory);
/* Return false if directory contains anything besides a Nautilus metafile.
diff --git a/src/nautilus-favorite-directory.c b/src/nautilus-favorite-directory.c
new file mode 100644
index 000000000..7f367e45b
--- /dev/null
+++ b/src/nautilus-favorite-directory.c
@@ -0,0 +1,585 @@
+/* nautilus-favorite-directory.c
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru.pandelea@gmail.com>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nautilus-favorite-directory.h"
+#include "nautilus-tag-manager.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-directory-private.h"
+#include <glib/gi18n.h>
+
+struct NautilusFavoriteDirectoryDetails
+{
+ NautilusTagManager *tag_manager;
+ GList *files;
+
+ GList *monitor_list;
+ GList *callback_list;
+ GList *pending_callback_list;
+};
+
+typedef struct
+{
+ gboolean monitor_hidden_files;
+ NautilusFileAttributes monitor_attributes;
+
+ gconstpointer client;
+} FavoriteMonitor;
+
+typedef struct
+{
+ NautilusFavoriteDirectory *favorite_directory;
+
+ NautilusDirectoryCallback callback;
+ gpointer callback_data;
+
+ NautilusFileAttributes wait_for_attributes;
+ gboolean wait_for_file_list;
+ GList *file_list;
+} FavoriteCallback;
+
+G_DEFINE_TYPE_WITH_CODE (NautilusFavoriteDirectory, nautilus_favorite_directory, NAUTILUS_TYPE_DIRECTORY,
+ nautilus_ensure_extension_points ();
+ g_io_extension_point_implement (NAUTILUS_DIRECTORY_PROVIDER_EXTENSION_POINT_NAME,
+ g_define_type_id,
+ NAUTILUS_FAVORITE_DIRECTORY_PROVIDER_NAME,
+ 0));
+
+static void
+file_changed (NautilusFile *file,
+ NautilusFavoriteDirectory *favorite)
+{
+ GList list;
+
+ list.data = file;
+ list.next = NULL;
+
+ nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (favorite), &list);
+}
+
+static void
+nautilus_favorite_directory_update_files (NautilusFavoriteDirectory *self)
+{
+ GList *l;
+ GList *tmp_l;
+ GList *new_favorite_files;
+ GList *monitor_list;
+ FavoriteMonitor *monitor;
+ NautilusFile *file;
+ GHashTable *uri_table;
+ GList *files_added;
+ GList *files_removed;
+ gchar *uri;
+
+ files_added = NULL;
+ files_removed = NULL;
+
+ uri_table = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ NULL);
+
+ for (l = self->details->files; l != NULL; l = l->next)
+ {
+ g_hash_table_add (uri_table, nautilus_file_get_uri (NAUTILUS_FILE (l->data)));
+ }
+
+ new_favorite_files = nautilus_tag_manager_get_favorite_files (self->details->tag_manager);
+
+ for (l = new_favorite_files; l != NULL; l = l->next)
+ {
+ if (!g_hash_table_contains (uri_table, l->data))
+ {
+ file = nautilus_file_get_by_uri ((gchar*) l->data);
+
+ for (monitor_list = self->details->monitor_list; monitor_list; monitor_list = monitor_list->next)
+ {
+ monitor = monitor_list->data;
+
+ /* Add monitors */
+ nautilus_file_monitor_add (file, monitor, monitor->monitor_attributes);
+ }
+
+ g_signal_connect (file, "changed", G_CALLBACK (file_changed), self);
+
+ files_added = g_list_prepend (files_added, file);
+ }
+ }
+
+ l = self->details->files;
+ while (l != NULL)
+ {
+ uri = nautilus_file_get_uri (NAUTILUS_FILE (l->data));
+
+ if (!nautilus_tag_manager_file_is_favorite (self->details->tag_manager, uri))
+ {
+ files_removed = g_list_prepend (files_removed,
+ nautilus_file_ref (NAUTILUS_FILE (l->data)));
+
+ g_signal_handlers_disconnect_by_func (NAUTILUS_FILE (l->data),
+ file_changed,
+ self);
+
+ /* Remove monitors */
+ for (monitor_list = self->details->monitor_list; monitor_list;
+ monitor_list = monitor_list->next)
+ {
+ monitor = monitor_list->data;
+ nautilus_file_monitor_remove (NAUTILUS_FILE (l->data), monitor);
+ }
+
+ if (l == self->details->files)
+ {
+ self->details->files = g_list_delete_link (self->details->files, l);
+ l = self->details->files;
+ }
+ else
+ {
+ tmp_l = l->prev;
+ self->details->files = g_list_delete_link (self->details->files, l);
+ l = tmp_l->next;
+ }
+ }
+ else
+ {
+ l = l->next;
+ }
+
+ g_free (uri);
+ }
+
+ if (files_added)
+ {
+ nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (self), files_added);
+
+ for (l = files_added; l != NULL; l = l->next)
+ {
+ self->details->files = g_list_prepend (self->details->files, nautilus_file_ref (NAUTILUS_FILE (l->data)));
+ }
+ }
+
+ if (files_removed)
+ {
+ nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (self), files_removed);
+ }
+
+ nautilus_file_list_free (files_added);
+ nautilus_file_list_free (files_removed);
+ g_hash_table_destroy (uri_table);
+}
+
+static void
+on_favorites_files_changed (NautilusTagManager *tag_manager,
+ GList *changed_files,
+ gpointer user_data)
+{
+ NautilusFavoriteDirectory *self;
+
+ self = NAUTILUS_FAVORITE_DIRECTORY (user_data);
+
+ nautilus_favorite_directory_update_files (self);
+}
+
+static gboolean
+real_contains_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ NautilusFavoriteDirectory *self;
+ g_autofree gchar *uri = NULL;
+
+ self = NAUTILUS_FAVORITE_DIRECTORY (directory);
+
+ uri = nautilus_file_get_uri (file);
+
+ return nautilus_tag_manager_file_is_favorite (self->details->tag_manager, uri);
+}
+
+static gboolean
+real_is_editable (NautilusDirectory *directory)
+{
+ return FALSE;
+}
+
+static void
+real_force_reload (NautilusDirectory *directory)
+{
+ nautilus_favorite_directory_update_files (NAUTILUS_FAVORITE_DIRECTORY (directory));
+}
+
+static void
+real_call_when_ready (NautilusDirectory *directory,
+ NautilusFileAttributes file_attributes,
+ gboolean wait_for_file_list,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ GList *file_list;
+ NautilusFavoriteDirectory *favorite;
+
+ favorite = NAUTILUS_FAVORITE_DIRECTORY (directory);
+
+ file_list = nautilus_file_list_copy (favorite->details->files);
+
+ callback (NAUTILUS_DIRECTORY (directory),
+ file_list,
+ callback_data);
+}
+
+static gboolean
+real_are_all_files_seen (NautilusDirectory *directory)
+{
+ return TRUE;
+}
+
+static void
+real_file_monitor_add (NautilusDirectory *directory,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ NautilusFileAttributes file_attributes,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ GList *list;
+ FavoriteMonitor *monitor;
+ NautilusFavoriteDirectory *favorite;
+ NautilusFile *file;
+
+ favorite = NAUTILUS_FAVORITE_DIRECTORY (directory);
+
+ monitor = g_new0 (FavoriteMonitor, 1);
+ monitor->monitor_hidden_files = monitor_hidden_files;
+ monitor->monitor_attributes = file_attributes;
+ monitor->client = client;
+
+ favorite->details->monitor_list = g_list_prepend (favorite->details->monitor_list, monitor);
+
+ if (callback != NULL)
+ {
+ (*callback) (directory, favorite->details->files, callback_data);
+ }
+
+ for (list = favorite->details->files; list != NULL; list = list->next)
+ {
+ file = list->data;
+
+ /* Add monitors */
+ nautilus_file_monitor_add (file, monitor, file_attributes);
+ }
+}
+
+static void
+favorite_monitor_destroy (FavoriteMonitor *monitor,
+ NautilusFavoriteDirectory *favorite)
+{
+ GList *l;
+ NautilusFile *file;
+
+ for (l = favorite->details->files; l != NULL; l = l->next)
+ {
+ file = l->data;
+
+ nautilus_file_monitor_remove (file, monitor);
+ }
+
+ g_free (monitor);
+}
+
+static void
+real_monitor_remove (NautilusDirectory *directory,
+ gconstpointer client)
+{
+ NautilusFavoriteDirectory *favorite;
+ FavoriteMonitor *monitor;
+ GList *list;
+
+ favorite = NAUTILUS_FAVORITE_DIRECTORY (directory);
+
+ for (list = favorite->details->monitor_list; list != NULL; list = list->next)
+ {
+ monitor = list->data;
+
+ if (monitor->client != client)
+ continue;
+
+ favorite->details->monitor_list = g_list_delete_link (favorite->details->monitor_list, list);
+
+ favorite_monitor_destroy (monitor, favorite);
+
+ break;
+ }
+}
+
+static gboolean
+real_handles_location (GFile *location)
+{
+ g_autofree gchar *uri = NULL;
+
+ uri = g_file_get_uri (location);
+
+ if (eel_uri_is_favorites (uri))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static FavoriteCallback*
+favorite_callback_find_pending (NautilusFavoriteDirectory *favorite,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ FavoriteCallback *favorite_callback;
+ GList *list;
+
+ for (list = favorite->details->pending_callback_list; list != NULL; list = list->next)
+ {
+ favorite_callback = list->data;
+
+ if (favorite_callback->callback == callback &&
+ favorite_callback->callback_data == callback_data)
+ {
+ return favorite_callback;
+ }
+ }
+
+ return NULL;
+}
+
+static FavoriteCallback*
+favorite_callback_find (NautilusFavoriteDirectory *favorite,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ FavoriteCallback *favorite_callback;
+ GList *list;
+
+ for (list = favorite->details->callback_list; list != NULL; list = list->next)
+ {
+ favorite_callback = list->data;
+
+ if (favorite_callback->callback == callback &&
+ favorite_callback->callback_data == callback_data)
+ {
+ return favorite_callback;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+favorite_callback_destroy (FavoriteCallback *favorite_callback)
+{
+ nautilus_file_list_free (favorite_callback->file_list);
+
+ g_free (favorite_callback);
+}
+
+static void
+real_cancel_callback (NautilusDirectory *directory,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ NautilusFavoriteDirectory *favorite;
+ FavoriteCallback *favorite_callback;
+
+ favorite = NAUTILUS_FAVORITE_DIRECTORY (directory);
+ favorite_callback = favorite_callback_find (favorite, callback, callback_data);
+
+ if (favorite_callback)
+ {
+ favorite->details->callback_list = g_list_remove (favorite->details->callback_list, favorite_callback);
+
+ favorite_callback_destroy (favorite_callback);
+
+ return;
+ }
+
+ /* Check for a pending callback */
+ favorite_callback = favorite_callback_find_pending (favorite, callback, callback_data);
+
+ if (favorite_callback)
+ {
+ favorite->details->pending_callback_list = g_list_remove (favorite->details->pending_callback_list, favorite_callback);
+
+ favorite_callback_destroy (favorite_callback);
+ }
+}
+
+static GList*
+real_get_file_list (NautilusDirectory *directory)
+{
+ NautilusFavoriteDirectory *favorite;
+
+ favorite = NAUTILUS_FAVORITE_DIRECTORY (directory);
+
+ return nautilus_file_list_copy (favorite->details->files);
+}
+
+static void
+nautilus_favorite_directory_set_files (NautilusFavoriteDirectory *self)
+{
+ GList *favorite_files;
+ NautilusFile *file;
+ GList *l;
+ GList *file_list;
+ FavoriteMonitor *monitor;
+ GList *monitor_list;
+
+ file_list = NULL;
+
+ favorite_files = nautilus_tag_manager_get_favorite_files (self->details->tag_manager);
+
+ for (l = favorite_files; l != NULL; l = l->next)
+ {
+ file = nautilus_file_get_by_uri ((gchar*) l->data);
+
+ g_signal_connect (file, "changed", G_CALLBACK (file_changed), self);
+
+ for (monitor_list = self->details->monitor_list; monitor_list; monitor_list = monitor_list->next)
+ {
+ monitor = monitor_list->data;
+
+ /* Add monitors */
+ nautilus_file_monitor_add (file, monitor, monitor->monitor_attributes);
+ }
+
+ file_list = g_list_prepend (file_list, file);
+ }
+
+ nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (self), file_list);
+
+ self->details->files = file_list;
+}
+
+static void
+nautilus_favorite_directory_finalize (GObject *object)
+{
+ NautilusFavoriteDirectory *self;
+
+ self = NAUTILUS_FAVORITE_DIRECTORY (object);
+
+ g_signal_handlers_disconnect_by_func (self->details->tag_manager,
+ on_favorites_files_changed,
+ self);
+
+ g_object_unref (self->details->tag_manager);
+ nautilus_file_list_free (self->details->files);
+
+ G_OBJECT_CLASS (nautilus_favorite_directory_parent_class)->finalize (object);
+}
+
+static void
+nautilus_favorite_directory_dispose (GObject *object)
+{
+ NautilusFavoriteDirectory *favorite;
+ GList *l;
+ GList *monitor_list;
+ FavoriteMonitor *monitor;
+ NautilusFile *file;
+
+ favorite = NAUTILUS_FAVORITE_DIRECTORY (object);
+
+ /* Remove file connections */
+ for (l = favorite->details->files; l != NULL; l = l->next)
+ {
+ file = l->data;
+
+ /* Disconnect change handler */
+ g_signal_handlers_disconnect_by_func (file, file_changed, favorite);
+
+ /* Remove monitors */
+ for (monitor_list = favorite->details->monitor_list; monitor_list;
+ monitor_list = monitor_list->next)
+ {
+ monitor = monitor_list->data;
+ nautilus_file_monitor_remove (file, monitor);
+ }
+ }
+
+ /* Remove search monitors */
+ if (favorite->details->monitor_list)
+ {
+ for (l = favorite->details->monitor_list; l != NULL; l = l->next)
+ {
+ favorite_monitor_destroy ((FavoriteMonitor*) l->data, favorite);
+ }
+
+ g_list_free (favorite->details->monitor_list);
+ favorite->details->monitor_list = NULL;
+ }
+
+ G_OBJECT_CLASS (nautilus_favorite_directory_parent_class)->dispose (object);
+}
+
+static void
+nautilus_favorite_directory_class_init (NautilusFavoriteDirectoryClass *klass)
+{
+ GObjectClass *oclass;
+ NautilusDirectoryClass *directory_class;
+
+ oclass = G_OBJECT_CLASS (klass);
+ directory_class = NAUTILUS_DIRECTORY_CLASS (klass);
+
+ oclass->finalize = nautilus_favorite_directory_finalize;
+ oclass->dispose = nautilus_favorite_directory_dispose;
+
+ directory_class->handles_location = real_handles_location;
+ directory_class->contains_file = real_contains_file;
+ directory_class->is_editable = real_is_editable;
+ directory_class->force_reload = real_force_reload;
+ directory_class->call_when_ready = real_call_when_ready;
+ directory_class->are_all_files_seen = real_are_all_files_seen;
+ directory_class->file_monitor_add = real_file_monitor_add;
+ directory_class->file_monitor_remove = real_monitor_remove;
+ directory_class->cancel_callback = real_cancel_callback;
+ directory_class->get_file_list = real_get_file_list;
+
+ g_type_class_add_private (klass, sizeof (NautilusFavoriteDirectoryDetails));
+}
+
+NautilusFavoriteDirectory*
+nautilus_favorite_directory_new ()
+{
+ NautilusFavoriteDirectory *self;
+
+ self = g_object_new (NAUTILUS_TYPE_FAVORITE_DIRECTORY, NULL);
+
+ return self;
+}
+
+static void
+nautilus_favorite_directory_init (NautilusFavoriteDirectory *self)
+{
+ NautilusTagManager *tag_manager;
+
+ self->details = G_TYPE_INSTANCE_GET_PRIVATE (self, NAUTILUS_TYPE_FAVORITE_DIRECTORY,
+ NautilusFavoriteDirectoryDetails);
+
+ tag_manager = nautilus_tag_manager_get ();
+
+ g_signal_connect (tag_manager,
+ "favorites-changed",
+ (GCallback) on_favorites_files_changed,
+ self);
+
+ self->details->tag_manager = tag_manager;
+
+ nautilus_favorite_directory_set_files (self);
+
+}
diff --git a/src/nautilus-favorite-directory.h b/src/nautilus-favorite-directory.h
new file mode 100644
index 000000000..2acf13c42
--- /dev/null
+++ b/src/nautilus-favorite-directory.h
@@ -0,0 +1,57 @@
+/* nautilus-favorite-directory.h
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru.pandelea@gmail.com>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NAUTILUS_FAVORITE_DIRECTORY_H
+#define NAUTILUS_FAVORITE_DIRECTORY_H
+
+#include "nautilus-directory.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_FAVORITE_DIRECTORY_PROVIDER_NAME "favorite-directory-provider"
+
+#define NAUTILUS_TYPE_FAVORITE_DIRECTORY nautilus_favorite_directory_get_type()
+#define NAUTILUS_FAVORITE_DIRECTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_FAVORITE_DIRECTORY, NautilusFavoriteDirectory))
+#define NAUTILUS_FAVORITE_DIRECTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_FAVORITE_DIRECTORY, NautilusFavoriteDirectoryClass))
+#define NAUTILUS_IS_FAVORITE_DIRECTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_FAVORITE_DIRECTORY))
+#define NAUTILUS_IS_FAVORITE_DIRECTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_FAVORITE_DIRECTORY))
+#define NAUTILUS_FAVORITE_DIRECTORY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_FAVORITE_DIRECTORY, NautilusFavoriteDirectoryClass))
+
+typedef struct NautilusFavoriteDirectoryDetails NautilusFavoriteDirectoryDetails;
+
+typedef struct {
+ NautilusDirectory parent_slot;
+ NautilusFavoriteDirectoryDetails *details;
+} NautilusFavoriteDirectory;
+
+typedef struct {
+ NautilusDirectoryClass parent_slot;
+} NautilusFavoriteDirectoryClass;
+
+GType nautilus_favorite_directory_get_type (void);
+
+NautilusFavoriteDirectory* nautilus_favorite_directory_new ();
+
+G_END_DECLS
+
+#endif
diff --git a/src/nautilus-file-undo-operations.c b/src/nautilus-file-undo-operations.c
index e833d0578..e971274ef 100644
--- a/src/nautilus-file-undo-operations.c
+++ b/src/nautilus-file-undo-operations.c
@@ -33,6 +33,7 @@
#include "nautilus-file-undo-manager.h"
#include "nautilus-batch-rename-dialog.h"
#include "nautilus-batch-rename-utilities.h"
+#include "nautilus-tag-manager.h"
/* Since we use g_get_current_time for setting "orig_trash_time" in the undo
@@ -1304,6 +1305,226 @@ nautilus_file_undo_info_batch_rename_set_data_post (NautilusFileUndoInfoBatchRen
self->priv->new_display_names = g_list_reverse (self->priv->new_display_names);
}
+/* favorite files */
+G_DEFINE_TYPE (NautilusFileUndoInfoFavorites, nautilus_file_undo_info_favorites, NAUTILUS_TYPE_FILE_UNDO_INFO);
+
+struct _NautilusFileUndoInfoFavoritesDetails
+{
+ GList *files;
+ /* Whether the action was starring or unstarring */
+ gboolean starred;
+};
+
+enum
+{
+ PROP_FILES = 1,
+ PROP_STARRED,
+ NUM_PROPERTIES
+};
+
+static void
+favorites_strings_func (NautilusFileUndoInfo *info,
+ gchar **undo_label,
+ gchar **undo_description,
+ gchar **redo_label,
+ gchar **redo_description)
+{
+ NautilusFileUndoInfoFavorites *self = NAUTILUS_FILE_UNDO_INFO_FAVORITES (info);
+
+ if (self->priv->starred)
+ {
+ *undo_description = g_strdup_printf (ngettext ("Unstar %d file",
+ "Unstar %d files",
+ g_list_length (self->priv->files)),
+ g_list_length (self->priv->files));
+ *redo_description = g_strdup_printf (ngettext ("Star %d file",
+ "Star %d files",
+ g_list_length (self->priv->files)),
+ g_list_length (self->priv->files));
+ *undo_label = g_strdup (_("_Undo Starring"));
+ *redo_label = g_strdup (_("_Redo Starring"));
+ }
+ else
+ {
+ *undo_description = g_strdup_printf (ngettext ("Star %d file",
+ "Star %d files",
+ g_list_length (self->priv->files)),
+ g_list_length (self->priv->files));
+ *redo_description = g_strdup_printf (ngettext ("Unstar %d file",
+ "Unstar %d files",
+ g_list_length (self->priv->files)),
+ g_list_length (self->priv->files));
+ *undo_label = g_strdup (_("_Undo Unstarring"));
+ *redo_label = g_strdup (_("_Redo Unstarring"));
+ }
+}
+
+static void
+on_undo_favorite_tags_updated (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GTask *task;
+ NautilusFileUndoInfo *undo_info;
+
+ undo_info = NAUTILUS_FILE_UNDO_INFO (object);
+
+ task = user_data;
+ g_clear_object (&task);
+
+ file_undo_info_operation_callback (NULL, NULL, NULL, undo_info);
+}
+
+static void
+favorites_redo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window)
+{
+ NautilusFileUndoInfoFavorites *self = NAUTILUS_FILE_UNDO_INFO_FAVORITES (info);
+ NautilusTagManager *tag_manager;
+
+ tag_manager = nautilus_tag_manager_get ();
+
+ if (self->priv->starred)
+ {
+ nautilus_tag_manager_star_files (tag_manager,
+ G_OBJECT (info),
+ self->priv->files,
+ on_undo_favorite_tags_updated,
+ NULL);
+ }
+ else
+ {
+
+ nautilus_tag_manager_unstar_files (tag_manager,
+ G_OBJECT (info),
+ self->priv->files,
+ on_undo_favorite_tags_updated,
+ NULL);
+ }
+}
+
+static void
+favorites_undo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window)
+{
+ NautilusFileUndoInfoFavorites *self = NAUTILUS_FILE_UNDO_INFO_FAVORITES (info);
+ NautilusTagManager *tag_manager;
+
+ tag_manager = nautilus_tag_manager_get ();
+
+ if (self->priv->starred)
+ {
+ nautilus_tag_manager_unstar_files (tag_manager,
+ G_OBJECT (info),
+ self->priv->files,
+ on_undo_favorite_tags_updated,
+ NULL);
+ }
+ else
+ {
+ nautilus_tag_manager_star_files (tag_manager,
+ G_OBJECT (info),
+ self->priv->files,
+ on_undo_favorite_tags_updated,
+ NULL);
+ }
+}
+
+static void
+nautilus_file_undo_info_favorites_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusFileUndoInfoFavorites *self = NAUTILUS_FILE_UNDO_INFO_FAVORITES (object);
+
+ switch (prop_id)
+ {
+ case PROP_FILES:
+ {
+ self->priv->files = nautilus_file_list_copy (g_value_get_pointer (value));
+ }
+ break;
+
+ case PROP_STARRED:
+ {
+ self->priv->starred = g_value_get_boolean (value);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_file_undo_info_favorites_init (NautilusFileUndoInfoFavorites *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, nautilus_file_undo_info_favorites_get_type (),
+ NautilusFileUndoInfoFavoritesDetails);
+}
+
+static void
+nautilus_file_undo_info_favorites_finalize (GObject *obj)
+{
+ NautilusFileUndoInfoFavorites *self = NAUTILUS_FILE_UNDO_INFO_FAVORITES (obj);
+
+ nautilus_file_list_free (self->priv->files);
+
+ G_OBJECT_CLASS (nautilus_file_undo_info_favorites_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_file_undo_info_favorites_class_init (NautilusFileUndoInfoFavoritesClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass);
+
+ oclass->finalize = nautilus_file_undo_info_favorites_finalize;
+ oclass->set_property = nautilus_file_undo_info_favorites_set_property;
+
+ iclass->undo_func = favorites_undo_func;
+ iclass->redo_func = favorites_redo_func;
+ iclass->strings_func = favorites_strings_func;
+
+ g_type_class_add_private (klass, sizeof (NautilusFileUndoInfoFavoritesDetails));
+
+ g_object_class_install_property (oclass,
+ PROP_FILES,
+ g_param_spec_pointer ("files",
+ "files",
+ "The files for which to undo star/unstar",
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (oclass,
+ PROP_STARRED,
+ g_param_spec_boolean ("starred",
+ "starred",
+ "Whether the files were starred or unstarred",
+ FALSE,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_favorites_new (GList *files,
+ gboolean starred)
+{
+ NautilusFileUndoInfoFavorites *self;
+
+ self = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES,
+ "op-type", NAUTILUS_FILE_UNDO_OP_FAVORITES,
+ "item-count", g_list_length (files),
+ "files", files,
+ "starred", starred,
+ NULL);
+
+ return NAUTILUS_FILE_UNDO_INFO (self);
+}
+
/* trash */
G_DEFINE_TYPE (NautilusFileUndoInfoTrash, nautilus_file_undo_info_trash, NAUTILUS_TYPE_FILE_UNDO_INFO)
diff --git a/src/nautilus-file-undo-operations.h b/src/nautilus-file-undo-operations.h
index 630443f10..795a01f01 100644
--- a/src/nautilus-file-undo-operations.h
+++ b/src/nautilus-file-undo-operations.h
@@ -35,6 +35,7 @@ typedef enum {
NAUTILUS_FILE_UNDO_OP_MOVE,
NAUTILUS_FILE_UNDO_OP_RENAME,
NAUTILUS_FILE_UNDO_OP_BATCH_RENAME,
+ NAUTILUS_FILE_UNDO_OP_FAVORITES,
NAUTILUS_FILE_UNDO_OP_CREATE_EMPTY_FILE,
NAUTILUS_FILE_UNDO_OP_CREATE_FILE_FROM_TEMPLATE,
NAUTILUS_FILE_UNDO_OP_CREATE_FOLDER,
@@ -217,6 +218,31 @@ void nautilus_file_undo_info_batch_rename_set_data_pre (NautilusFileUndoInfoBatc
void nautilus_file_undo_info_batch_rename_set_data_post (NautilusFileUndoInfoBatchRename *self,
GList *new_files);
+/* favorite files */
+#define NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES (nautilus_file_undo_info_favorites_get_type ())
+#define NAUTILUS_FILE_UNDO_INFO_FAVORITES(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES, NautilusFileUndoInfoFavorites))
+#define NAUTILUS_FILE_UNDO_INFO_FAVORITES_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES, NautilusFileUndoInfoFavoritesClass))
+#define NAUTILUS_IS_FILE_UNDO_INFO_FAVORITES(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES))
+#define NAUTILUS_IS_FILE_UNDO_INFO_FAVORITES_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES))
+#define NAUTILUS_FILE_UNDO_INFO_FAVORITES_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES, NautilusFileUndoInfoFavoritesClass))
+
+typedef struct _NautilusFileUndoInfoFavorites NautilusFileUndoInfoFavorites;
+typedef struct _NautilusFileUndoInfoFavoritesClass NautilusFileUndoInfoFavoritesClass;
+typedef struct _NautilusFileUndoInfoFavoritesDetails NautilusFileUndoInfoFavoritesDetails;
+
+struct _NautilusFileUndoInfoFavorites {
+ NautilusFileUndoInfo parent;
+ NautilusFileUndoInfoFavoritesDetails *priv;
+};
+
+struct _NautilusFileUndoInfoFavoritesClass {
+ NautilusFileUndoInfoClass parent_class;
+};
+
+GType nautilus_file_undo_info_favorites_get_type (void) G_GNUC_CONST;
+NautilusFileUndoInfo *nautilus_file_undo_info_favorites_new (GList *files,
+ gboolean starred);
+
/* trash */
#define NAUTILUS_TYPE_FILE_UNDO_INFO_TRASH (nautilus_file_undo_info_trash_get_type ())
#define NAUTILUS_FILE_UNDO_INFO_TRASH(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_TRASH, NautilusFileUndoInfoTrash))
diff --git a/src/nautilus-file-utilities.c b/src/nautilus-file-utilities.c
index 59add50e4..a8946914b 100644
--- a/src/nautilus-file-utilities.c
+++ b/src/nautilus-file-utilities.c
@@ -322,7 +322,10 @@ nautilus_compute_title_for_location (GFile *location)
{
title = g_strdup (_("Other Locations"));
}
- else
+ else if (nautilus_file_is_favorite_location (file))
+ {
+ title = g_strdup (_("Starred"));
+ }
{
title = nautilus_file_get_description (file);
@@ -593,6 +596,19 @@ nautilus_is_search_directory (GFile *dir)
}
gboolean
+nautilus_is_favorite_directory (GFile *dir)
+{
+ g_autofree gchar *uri = NULL;
+
+ uri = g_file_get_uri (dir);
+
+ if (eel_uri_is_favorites (uri))
+ return TRUE;
+
+ return FALSE;
+}
+
+gboolean
nautilus_is_other_locations_directory (GFile *dir)
{
g_autofree gchar *uri = NULL;
diff --git a/src/nautilus-file-utilities.h b/src/nautilus-file-utilities.h
index da165cd73..44defe174 100644
--- a/src/nautilus-file-utilities.h
+++ b/src/nautilus-file-utilities.h
@@ -45,6 +45,7 @@ gboolean nautilus_is_home_directory_file (GFile *dir,
const char *filename);
gboolean nautilus_is_in_system_dir (GFile *location);
gboolean nautilus_is_search_directory (GFile *dir);
+gboolean nautilus_is_favorite_directory (GFile *dir);
gboolean nautilus_is_other_locations_directory (GFile *dir);
GMount * nautilus_get_mounted_mount_for_root (GFile *location);
diff --git a/src/nautilus-file.c b/src/nautilus-file.c
index 2d668c1ce..e8a631423 100644
--- a/src/nautilus-file.c
+++ b/src/nautilus-file.c
@@ -40,6 +40,7 @@
#include "nautilus-vfs-file.h"
#include "nautilus-file-undo-operations.h"
#include "nautilus-file-undo-manager.h"
+#include "nautilus-tag-manager.h"
#include <eel/eel-debug.h>
#include <eel/eel-glib-extensions.h>
#include <eel/eel-gtk-extensions.h>
@@ -159,7 +160,8 @@ static GQuark attribute_name_q,
attribute_where_q,
attribute_link_target_q,
attribute_volume_q,
- attribute_free_space_q;
+ attribute_free_space_q,
+ attribute_favorite_q;
static void nautilus_file_info_iface_init (NautilusFileInfoIface *iface);
static char *nautilus_file_get_owner_as_string (NautilusFile *file,
@@ -3658,6 +3660,39 @@ compare_by_type (NautilusFile *file_1,
return result;
}
+static int
+compare_by_favorite (NautilusFile *file_1,
+ NautilusFile *file_2)
+{
+ NautilusTagManager *tag_manager;
+ g_autofree gchar *uri_1 = NULL;
+ g_autofree gchar *uri_2 = NULL;
+ gboolean file_1_is_favorite;
+ gboolean file_2_is_favorite;
+
+ tag_manager = nautilus_tag_manager_get ();
+
+ uri_1 = nautilus_file_get_uri (file_1);
+ uri_2 = nautilus_file_get_uri (file_2);
+
+ file_1_is_favorite = nautilus_tag_manager_file_is_favorite (tag_manager,
+ uri_1);
+ file_2_is_favorite = nautilus_tag_manager_file_is_favorite (tag_manager,
+ uri_2);
+ if (!!file_1_is_favorite == !!file_2_is_favorite)
+ {
+ return 0;
+ }
+ else if (file_1_is_favorite && !file_2_is_favorite)
+ {
+ return -1;
+ }
+ else
+ {
+ return 1;
+ }
+}
+
static Knowledge
get_search_relevance (NautilusFile *file,
gdouble *relevance_out)
@@ -3861,6 +3896,16 @@ nautilus_file_compare_for_sort (NautilusFile *file_1,
}
break;
+ case NAUTILUS_FILE_SORT_BY_FAVORITE:
+ {
+ result = compare_by_favorite (file_1, file_2);
+ if (result == 0)
+ {
+ result = compare_by_full_path (file_1, file_2);
+ }
+ }
+ break;
+
case NAUTILUS_FILE_SORT_BY_MTIME:
{
result = compare_by_time (file_1, file_2, NAUTILUS_DATE_TYPE_MODIFIED);
@@ -3965,6 +4010,13 @@ nautilus_file_compare_for_sort_by_attribute_q (NautilusFile *file_1,
directories_first,
reversed);
}
+ else if (attribute == attribute_favorite_q)
+ {
+ return nautilus_file_compare_for_sort (file_1, file_2,
+ NAUTILUS_FILE_SORT_BY_FAVORITE,
+ directories_first,
+ reversed);
+ }
else if (attribute == attribute_modification_date_q || attribute == attribute_date_modified_q || attribute == attribute_date_modified_with_time_q || attribute == attribute_date_modified_full_q)
{
return nautilus_file_compare_for_sort (file_1, file_2,
@@ -4572,6 +4624,11 @@ nautilus_file_peek_display_name (NautilusFile *file)
char *
nautilus_file_get_display_name (NautilusFile *file)
{
+ if (nautilus_file_is_other_locations (file))
+ return g_strdup (_("Other Locations"));
+ if (nautilus_file_is_favorite_location (file))
+ return g_strdup (_("Starred"));
+
return g_strdup (nautilus_file_peek_display_name (file));
}
@@ -7604,6 +7661,11 @@ nautilus_file_get_string_attribute_with_default_q (NautilusFile *file,
/* If n/a */
return g_strdup ("");
}
+ if (attribute_q == attribute_favorite_q)
+ {
+ /* If n/a */
+ return g_strdup ("");
+ }
/* Fallback, use for both unknown attributes and attributes
* for which we have no more appropriate default.
@@ -8293,6 +8355,23 @@ nautilus_file_is_in_recent (NautilusFile *file)
return nautilus_directory_is_in_recent (file->details->directory);
}
+/**
+ * nautilus_file_is_in_starred
+ *
+ * Check if this file is a file in Starred.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: TRUE if @file is in Starred.
+ *
+ **/
+gboolean
+nautilus_file_is_in_starred (NautilusFile *file)
+{
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ return nautilus_directory_is_in_starred (file->details->directory);
+}
+
static const gchar * const remote_types[] =
{
"afp",
@@ -8352,6 +8431,27 @@ nautilus_file_is_other_locations (NautilusFile *file)
}
/**
+ * nautilus_file_is_favorite_location
+ *
+ * Check if this file is the Favorite location.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: TRUE if @file is the Favorite location.
+ *
+ **/
+gboolean
+nautilus_file_is_favorite_location (NautilusFile *file)
+{
+ g_autofree gchar *uri = NULL;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ uri = nautilus_file_get_uri (file);
+
+ return eel_uri_is_favorites (uri);
+}
+
+/**
* nautilus_file_is_in_admin
*
* Check if this file is using admin backend.
@@ -9366,6 +9466,7 @@ nautilus_file_class_init (NautilusFileClass *class)
attribute_link_target_q = g_quark_from_static_string ("link_target");
attribute_volume_q = g_quark_from_static_string ("volume");
attribute_free_space_q = g_quark_from_static_string ("free_space");
+ attribute_favorite_q = g_quark_from_static_string ("favorite");
G_OBJECT_CLASS (class)->finalize = finalize;
G_OBJECT_CLASS (class)->constructor = nautilus_file_constructor;
diff --git a/src/nautilus-file.h b/src/nautilus-file.h
index 3a83bd4de..464c66e0e 100644
--- a/src/nautilus-file.h
+++ b/src/nautilus-file.h
@@ -55,6 +55,7 @@ typedef enum {
NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
NAUTILUS_FILE_SORT_BY_SIZE,
NAUTILUS_FILE_SORT_BY_TYPE,
+ NAUTILUS_FILE_SORT_BY_FAVORITE,
NAUTILUS_FILE_SORT_BY_MTIME,
NAUTILUS_FILE_SORT_BY_ATIME,
NAUTILUS_FILE_SORT_BY_TRASHED_TIME,
@@ -215,9 +216,11 @@ gboolean nautilus_file_is_archive (NautilusFile *file);
gboolean nautilus_file_is_in_search (NautilusFile *file);
gboolean nautilus_file_is_in_trash (NautilusFile *file);
gboolean nautilus_file_is_in_recent (NautilusFile *file);
+gboolean nautilus_file_is_in_starred (NautilusFile *file);
gboolean nautilus_file_is_in_admin (NautilusFile *file);
gboolean nautilus_file_is_remote (NautilusFile *file);
gboolean nautilus_file_is_other_locations (NautilusFile *file);
+gboolean nautilus_file_is_favorite_location (NautilusFile *file);
gboolean nautilus_file_is_home (NautilusFile *file);
gboolean nautilus_file_is_desktop_directory (NautilusFile *file);
gboolean nautilus_file_is_child_of_desktop_directory (NautilusFile *file);
diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c
index b5402a87b..c195840a5 100644
--- a/src/nautilus-files-view.c
+++ b/src/nautilus-files-view.c
@@ -42,6 +42,7 @@
#include "nautilus-window.h"
#include "nautilus-toolbar.h"
#include "nautilus-view.h"
+#include "nautilus-tag-manager.h"
#ifdef HAVE_X11_XF86KEYSYM_H
#include <X11/XF86keysym.h>
@@ -70,6 +71,7 @@
#include <libnautilus-extension/nautilus-menu-provider.h>
#include "nautilus-clipboard.h"
#include "nautilus-search-directory.h"
+#include "nautilus-favorite-directory.h"
#include "nautilus-directory.h"
#include "nautilus-dnd.h"
#include "nautilus-file-attributes.h"
@@ -254,6 +256,7 @@ typedef struct
GtkWidget *folder_is_empty_widget;
GtkWidget *trash_is_empty_widget;
GtkWidget *no_search_results_widget;
+ GtkWidget *starred_is_empty_widget;
/* Floating bar */
guint floating_bar_set_status_timeout_id;
@@ -272,6 +275,9 @@ typedef struct
gulong stop_signal_handler;
gulong reload_signal_handler;
+
+ GCancellable *favorite_cancellable;
+ NautilusTagManager *tag_manager;
} NautilusFilesViewPrivate;
typedef struct
@@ -721,13 +727,27 @@ showing_recent_directory (NautilusFilesView *view)
}
static gboolean
+showing_starred_directory (NautilusFilesView *view)
+{
+ NautilusFile *file;
+
+ file = nautilus_files_view_get_directory_as_file (view);
+ if (file != NULL)
+ {
+ return nautilus_file_is_in_starred (file);
+ }
+ return FALSE;
+}
+
+static gboolean
nautilus_files_view_supports_creating_files (NautilusFilesView *view)
{
g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE);
return !nautilus_files_view_is_read_only (view)
&& !showing_trash_directory (view)
- && !showing_recent_directory (view);
+ && !showing_recent_directory (view)
+ && !showing_starred_directory (view);
}
static gboolean
@@ -1535,6 +1555,46 @@ action_delete (GSimpleAction *action,
}
static void
+action_star (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ GList *selection;
+ NautilusFilesViewPrivate *priv;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ priv = nautilus_files_view_get_instance_private (view);
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+ nautilus_tag_manager_star_files (priv->tag_manager,
+ G_OBJECT (view),
+ selection,
+ NULL,
+ priv->favorite_cancellable);
+}
+
+static void
+action_unstar (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ GList *selection;
+ NautilusFilesViewPrivate *priv;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ priv = nautilus_files_view_get_instance_private (view);
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+ nautilus_tag_manager_unstar_files (priv->tag_manager,
+ G_OBJECT (view),
+ selection,
+ NULL,
+ priv->favorite_cancellable);
+}
+
+static void
action_restore_from_trash (GSimpleAction *action,
GVariant *state,
gpointer user_data)
@@ -3191,6 +3251,9 @@ nautilus_files_view_finalize (GObject *object)
g_hash_table_destroy (priv->non_ready_files);
g_hash_table_destroy (priv->pending_reveal);
+ g_cancellable_cancel (priv->favorite_cancellable);
+ g_clear_object (&priv->favorite_cancellable);
+
G_OBJECT_CLASS (nautilus_files_view_parent_class)->finalize (object);
}
@@ -3439,6 +3502,10 @@ nautilus_files_view_set_location (NautilusView *view,
set_search_query_internal (files_view, previous_query, base_model);
g_object_unref (previous_query);
}
+ else if (NAUTILUS_IS_FAVORITE_DIRECTORY (directory))
+ {
+ load_directory (NAUTILUS_FILES_VIEW (view), directory);
+ }
else
{
load_directory (NAUTILUS_FILES_VIEW (view), directory);
@@ -3479,6 +3546,7 @@ real_check_empty_states (NautilusFilesView *view)
gtk_widget_hide (priv->no_search_results_widget);
gtk_widget_hide (priv->folder_is_empty_widget);
gtk_widget_hide (priv->trash_is_empty_widget);
+ gtk_widget_hide (priv->starred_is_empty_widget);
if (!priv->loading &&
nautilus_files_view_is_empty (view))
@@ -3493,6 +3561,10 @@ real_check_empty_states (NautilusFilesView *view)
{
gtk_widget_show (priv->trash_is_empty_widget);
}
+ else if (eel_uri_is_favorites (uri))
+ {
+ gtk_widget_show (priv->starred_is_empty_widget);
+ }
else
{
gtk_widget_show (priv->folder_is_empty_widget);
@@ -6979,6 +7051,8 @@ const GActionEntry view_entries[] =
{ "copy-to", action_copy_to},
{ "move-to-trash", action_move_to_trash},
{ "delete-from-trash", action_delete },
+ { "star", action_star},
+ { "unstar", action_unstar},
/* We separate the shortcut and the menu item since we want the shortcut
* to always be available, but we don't want the menu item shown if not
* completely necesary. Since the visibility of the menu item is based on
@@ -7058,6 +7132,7 @@ on_clipboard_contents_received (GtkClipboard *clipboard,
gboolean settings_show_create_link;
gboolean is_read_only;
gboolean selection_contains_recent;
+ gboolean selection_contains_starred;
GAction *action;
view = NAUTILUS_FILES_VIEW (user_data);
@@ -7075,9 +7150,10 @@ on_clipboard_contents_received (GtkClipboard *clipboard,
NAUTILUS_PREFERENCES_SHOW_CREATE_LINK);
is_read_only = nautilus_files_view_is_read_only (view);
selection_contains_recent = showing_recent_directory (view);
+ selection_contains_starred = showing_starred_directory (view);
can_link_from_copied_files = !nautilus_clipboard_is_cut_from_selection_data (selection_data) &&
- !selection_contains_recent && !is_read_only &&
- gtk_selection_data_get_length (selection_data) > 0;
+ !selection_contains_recent && !selection_contains_starred &&
+ !is_read_only && gtk_selection_data_get_length (selection_data) > 0;
action = g_action_map_lookup_action (G_ACTION_MAP (priv->view_action_group),
"create-link");
@@ -7364,6 +7440,7 @@ real_update_actions_state (NautilusFilesView *view)
gboolean selection_contains_desktop_or_home_dir;
gboolean selection_contains_recent;
gboolean selection_contains_search;
+ gboolean selection_contains_starred;
gboolean selection_all_in_trash;
gboolean selection_is_read_only;
gboolean can_create_files;
@@ -7388,6 +7465,9 @@ real_update_actions_state (NautilusFilesView *view)
gboolean settings_show_delete_permanently;
gboolean settings_show_create_link;
GDriveStartStopType start_stop_type;
+ gboolean show_star;
+ gboolean show_unstar;
+ gchar *uri;
priv = nautilus_files_view_get_instance_private (view);
@@ -7398,6 +7478,7 @@ real_update_actions_state (NautilusFilesView *view)
selection_contains_special_link = nautilus_files_view_special_link_in_selection (view, selection);
selection_contains_desktop_or_home_dir = desktop_or_home_dir_in_selection (selection);
selection_contains_recent = showing_recent_directory (view);
+ selection_contains_starred = showing_starred_directory (view);
selection_contains_search = nautilus_view_is_searching (NAUTILUS_VIEW (view));
selection_is_read_only = selection_count == 1 &&
(!nautilus_file_can_write (NAUTILUS_FILE (selection->data)) &&
@@ -7419,8 +7500,10 @@ real_update_actions_state (NautilusFilesView *view)
!selection_contains_desktop_or_home_dir;
can_copy_files = selection_count != 0
&& !selection_contains_special_link;
- can_move_files = can_delete_files && !selection_contains_recent;
+ can_move_files = can_delete_files && !selection_contains_recent &&
+ !selection_contains_starred;
can_paste_files_into = (!selection_contains_recent &&
+ !selection_contains_starred &&
selection_count == 1 &&
can_paste_into_file (NAUTILUS_FILE (selection->data)));
can_extract_files = selection_count != 0 &&
@@ -7436,7 +7519,8 @@ real_update_actions_state (NautilusFilesView *view)
action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
"new-folder-with-selection");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
- can_create_files && can_delete_files && (selection_count > 1) && !selection_contains_recent);
+ can_create_files && can_delete_files && (selection_count > 1) && !selection_contains_recent
+ && !selection_contains_starred);
action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
"rename");
@@ -7484,7 +7568,8 @@ real_update_actions_state (NautilusFilesView *view)
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
selection_count == 1 &&
- (selection_contains_recent || selection_contains_search));
+ (selection_contains_recent || selection_contains_search ||
+ selection_contains_starred));
action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
"new-folder");
@@ -7550,14 +7635,16 @@ real_update_actions_state (NautilusFilesView *view)
"delete-permanently-menu-item");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
can_delete_files && !can_trash_files &&
- !selection_all_in_trash && !selection_contains_recent);
+ !selection_all_in_trash && !selection_contains_recent &&
+ !selection_contains_starred);
action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
"permanent-delete-permanently-menu-item");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
can_delete_files && can_trash_files &&
settings_show_delete_permanently &&
- !selection_all_in_trash && !selection_contains_recent);
+ !selection_all_in_trash && !selection_contains_recent &&
+ !selection_contains_starred);
action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
"remove-from-recent");
@@ -7567,7 +7654,8 @@ real_update_actions_state (NautilusFilesView *view)
action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
"cut");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
- can_move_files && !selection_contains_recent);
+ can_move_files && !selection_contains_recent &&
+ !selection_contains_starred);
action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
"copy");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
@@ -7585,7 +7673,8 @@ real_update_actions_state (NautilusFilesView *view)
action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
"move-to");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
- can_move_files && !selection_contains_recent);
+ can_move_files && !selection_contains_recent &&
+ !selection_contains_starred);
/* Drive menu */
show_mount = (selection != NULL);
@@ -7662,13 +7751,14 @@ real_update_actions_state (NautilusFilesView *view)
action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
"paste");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
- !is_read_only && !selection_contains_recent);
+ !is_read_only && !selection_contains_recent &&
+ !selection_contains_starred);
action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
"paste-into");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
!selection_is_read_only && !selection_contains_recent &&
- can_paste_files_into);
+ can_paste_files_into && !selection_contains_starred);
action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
"properties");
@@ -7679,6 +7769,7 @@ real_update_actions_state (NautilusFilesView *view)
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
can_create_files &&
!selection_contains_recent &&
+ !selection_contains_starred &&
priv->templates_present);
/* Actions that are related to the clipboard need request, request the data
@@ -7722,6 +7813,38 @@ real_update_actions_state (NautilusFilesView *view)
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
!nautilus_files_view_is_empty (view));
+ show_star = (selection != NULL);
+ show_unstar = (selection != NULL);
+ for (l = selection; l != NULL; l = l->next)
+ {
+ file = NAUTILUS_FILE (l->data);
+ uri = nautilus_file_get_uri (file);
+
+ if (!show_star && !show_unstar)
+ {
+ break;
+ }
+
+ if (nautilus_tag_manager_file_is_favorite (priv->tag_manager, uri))
+ {
+ show_star = FALSE;
+ }
+ else
+ {
+ show_unstar = FALSE;
+ }
+
+ g_free (uri);
+ }
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
+ "star");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_star);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
+ "unstar");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_unstar);
+
nautilus_file_list_free (selection);
}
@@ -7996,7 +8119,8 @@ static void
update_background_menu (NautilusFilesView *view)
{
if (nautilus_files_view_supports_creating_files (view) &&
- !showing_recent_directory (view))
+ !showing_recent_directory (view) &&
+ !showing_starred_directory (view))
{
update_templates_menu (view);
}
@@ -9554,6 +9678,14 @@ nautilus_files_view_init (NautilusFilesView *view)
TRUE);
g_object_unref (builder);
+ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-starred-is-empty.ui");
+ priv->starred_is_empty_widget = GTK_WIDGET (gtk_builder_get_object (builder, "starred_is_empty"));
+ gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay), priv->starred_is_empty_widget);
+ gtk_overlay_set_overlay_pass_through (GTK_OVERLAY (priv->overlay),
+ priv->starred_is_empty_widget,
+ TRUE);
+ g_object_unref (builder);
+
builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-trash-is-empty.ui");
priv->trash_is_empty_widget = GTK_WIDGET (gtk_builder_get_object (builder, "trash_is_empty"));
gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay), priv->trash_is_empty_widget);
@@ -9695,6 +9827,9 @@ nautilus_files_view_init (NautilusFilesView *view)
* changed */
nautilus_application_set_accelerator (app, "view.show-move-to-trash-shortcut-changed-dialog", "<control>Delete");
+ priv->favorite_cancellable = g_cancellable_new ();
+ priv->tag_manager = nautilus_tag_manager_get ();
+
nautilus_profile_end (NULL);
}
diff --git a/src/nautilus-list-view-private.h b/src/nautilus-list-view-private.h
index 2638c36ec..ac0919f62 100644
--- a/src/nautilus-list-view-private.h
+++ b/src/nautilus-list-view-private.h
@@ -21,6 +21,7 @@
#include "nautilus-list-model.h"
#include "nautilus-tree-view-drag-dest.h"
#include "nautilus-dnd.h"
+#include "nautilus-tag-manager.h"
struct NautilusListViewDetails {
GtkTreeView *tree_view;
@@ -66,5 +67,8 @@ struct NautilusListViewDetails {
GQuark last_sort_attr;
GRegex *regex;
+
+ NautilusTagManager *tag_manager;
+ GCancellable *favorite_cancellable;
};
diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c
index 759e3b158..aa2c0e56b 100644
--- a/src/nautilus-list-view.c
+++ b/src/nautilus-list-view.c
@@ -32,6 +32,7 @@
#include "nautilus-toolbar.h"
#include "nautilus-list-view-dnd.h"
#include "nautilus-view.h"
+#include "nautilus-tag-manager.h"
#include <string.h>
#include <eel/eel-vfs-extensions.h>
@@ -456,6 +457,90 @@ row_activated_callback (GtkTreeView *treeview,
activate_selected_items (view);
}
+gboolean
+check_starred_status (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ NautilusFile *file;
+ GList *l;
+ GList *changed_files;
+
+ changed_files = data;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (model),
+ iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ if (!file)
+ {
+ return FALSE;
+ }
+
+ for (l = changed_files; l != NULL; l = l->next)
+ {
+ if (nautilus_file_compare_location (NAUTILUS_FILE (l->data), file) == 0)
+ {
+ gtk_tree_model_row_changed (model, path, iter);
+ }
+ }
+
+ nautilus_file_unref (file);
+
+ return FALSE;
+}
+
+static void
+on_favorites_files_changed (NautilusTagManager *tag_manager,
+ GList *changed_files,
+ gpointer user_data)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (user_data);
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (list_view->details->model),
+ check_starred_status,
+ changed_files);
+}
+
+static void
+on_star_cell_renderer_clicked (GtkTreePath *path,
+ NautilusListView *list_view)
+{
+ NautilusListModel *list_model;
+ NautilusFile *file;
+ g_autofree gchar *uri = NULL;
+ GList *selection;
+
+ list_model = list_view->details->model;
+
+ file = nautilus_list_model_file_for_path (list_model, path);
+ uri = nautilus_file_get_uri (file);
+ selection = g_list_prepend (NULL, file);
+
+ if (nautilus_tag_manager_file_is_favorite (list_view->details->tag_manager, uri))
+ {
+ nautilus_tag_manager_unstar_files (list_view->details->tag_manager,
+ G_OBJECT (list_view),
+ selection,
+ NULL,
+ list_view->details->favorite_cancellable);
+ }
+ else
+ {
+ nautilus_tag_manager_star_files (list_view->details->tag_manager,
+ G_OBJECT (list_view),
+ selection,
+ NULL,
+ list_view->details->favorite_cancellable);
+ }
+
+ nautilus_file_list_free (selection);
+}
+
static gboolean
button_press_callback (GtkWidget *widget,
GdkEventButton *event,
@@ -702,6 +787,32 @@ button_press_callback (GtkWidget *widget,
}
}
+ if (is_simple_click)
+ {
+ GtkTreeViewColumn *column = NULL;
+ gdouble cell_middle_x;
+
+ gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view->details->tree_view),
+ event->x,
+ event->y,
+ NULL,
+ &column,
+ NULL,
+ NULL);
+
+ if (g_strcmp0 (gtk_tree_view_column_get_title (column), "Star") == 0)
+ {
+ cell_middle_x = gtk_tree_view_column_get_width (column) / 2 +
+ gtk_tree_view_column_get_x_offset (column);
+
+ if (event->x > cell_middle_x - 10 &&
+ event->x < cell_middle_x + 10)
+ {
+ on_star_cell_renderer_clicked (path, view);
+ }
+ }
+ }
+
gtk_tree_path_free (path);
/* We chained to the default handler in this method, so never
@@ -1546,6 +1657,45 @@ apply_columns_settings (NautilusListView *list_view,
}
static void
+favorite_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ NautilusListView *view)
+{
+ g_autofree gchar *text = NULL;
+ g_autofree gchar *uri = NULL;
+ NautilusFile *file;
+
+ gtk_tree_model_get (model, iter,
+ view->details->file_name_column_num, &text,
+ -1);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (model),
+ iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ uri = nautilus_file_get_uri (file);
+
+ if (nautilus_tag_manager_file_is_favorite (view->details->tag_manager, uri))
+ {
+ g_object_set (renderer,
+ "icon-name", "starred-symbolic",
+ NULL);
+ }
+ else
+ {
+ g_object_set (renderer,
+ "icon-name", "non-starred-symbolic",
+ NULL);
+
+ }
+
+ nautilus_file_unref (file);
+}
+
+static void
filename_cell_data_func (GtkTreeViewColumn *column,
GtkCellRenderer *renderer,
GtkTreeModel *model,
@@ -2039,13 +2189,34 @@ create_and_set_up_tree_view (NautilusListView *view)
}
else
{
- /* We need to use libgd */
- cell = gd_styled_text_renderer_new ();
- /* FIXME: should be just dim-label.
- * See https://bugzilla.gnome.org/show_bug.cgi?id=744397
- */
- gd_styled_text_renderer_add_class (GD_STYLED_TEXT_RENDERER (cell),
- "nautilus-list-dim-label");
+ if (g_strcmp0 (name, "favorite") == 0)
+ {
+ cell = gtk_cell_renderer_pixbuf_new ();
+ g_object_set (cell,
+ "icon-name", "non-starred-symbolic",
+ "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE,
+ NULL);
+
+ column = gtk_tree_view_column_new_with_attributes (label,
+ cell,
+ NULL);
+ }
+ else
+ {
+ /* We need to use libgd */
+ cell = gd_styled_text_renderer_new ();
+ /* FIXME: should be just dim-label.
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=744397
+ */
+ gd_styled_text_renderer_add_class (GD_STYLED_TEXT_RENDERER (cell),
+ "nautilus-list-dim-label");
+
+ column = gtk_tree_view_column_new_with_attributes (label,
+ cell,
+ "text", column_num,
+ NULL);
+ }
+
g_object_set (cell,
"xalign", xalign,
@@ -2059,10 +2230,7 @@ create_and_set_up_tree_view (NautilusListView *view)
}
view->details->cells = g_list_append (view->details->cells,
cell);
- column = gtk_tree_view_column_new_with_attributes (label,
- cell,
- "text", column_num,
- NULL);
+
gtk_tree_view_append_column (view->details->tree_view, column);
gtk_tree_view_column_set_sort_column_id (column, column_num);
g_hash_table_insert (view->details->columns,
@@ -2089,6 +2257,12 @@ create_and_set_up_tree_view (NautilusListView *view)
(GtkTreeCellDataFunc) trash_orig_path_cell_data_func,
view, NULL);
}
+ else if (!strcmp (name, "favorite"))
+ {
+ gtk_tree_view_column_set_cell_data_func (column, cell,
+ (GtkTreeCellDataFunc) favorite_cell_data_func,
+ view, NULL);
+ }
}
g_free (name);
g_free (label);
@@ -3376,6 +3550,13 @@ nautilus_list_view_finalize (GObject *object)
g_regex_unref (list_view->details->regex);
+ g_cancellable_cancel (list_view->details->favorite_cancellable);
+ g_clear_object (&list_view->details->favorite_cancellable);
+
+ g_signal_handlers_disconnect_by_func (list_view->details->tag_manager,
+ on_favorites_files_changed,
+ list_view);
+
g_free (list_view->details);
G_OBJECT_CLASS (nautilus_list_view_parent_class)->finalize (object);
@@ -3663,6 +3844,14 @@ nautilus_list_view_init (NautilusListView *list_view)
"zoom-to-level", g_variant_new_int32 (get_default_zoom_level ()));
list_view->details->regex = g_regex_new ("\\R+", 0, G_REGEX_MATCH_NEWLINE_ANY, NULL);
+
+ list_view->details->tag_manager = nautilus_tag_manager_get ();
+ list_view->details->favorite_cancellable = g_cancellable_new ();
+
+ g_signal_connect (list_view->details->tag_manager,
+ "favorites-changed",
+ (GCallback) on_favorites_files_changed,
+ list_view);
}
NautilusFilesView *
diff --git a/src/nautilus-pathbar.c b/src/nautilus-pathbar.c
index 4217913d8..6250a8ec6 100644
--- a/src/nautilus-pathbar.c
+++ b/src/nautilus-pathbar.c
@@ -47,7 +47,8 @@ typedef enum
OTHER_LOCATIONS_BUTTON,
ROOT_BUTTON,
HOME_BUTTON,
- MOUNT_BUTTON
+ MOUNT_BUTTON,
+ FAVORITE_LOCATION_BUTTON
} ButtonType;
#define BUTTON_DATA(x) ((ButtonData *) (x))
@@ -434,6 +435,11 @@ get_dir_name (ButtonData *button_data)
return _("Other Locations");
}
+ case FAVORITE_LOCATION_BUTTON:
+ {
+ return _("Starred");
+ }
+
default:
return button_data->dir_name;
}
@@ -1826,6 +1832,10 @@ setup_button_type (ButtonData *button_data,
g_object_unref (mount);
}
+ else if (nautilus_is_favorite_directory (location))
+ {
+ button_data->type = FAVORITE_LOCATION_BUTTON;
+ }
else
{
button_data->type = NORMAL_BUTTON;
diff --git a/src/nautilus-query.c b/src/nautilus-query.c
index 59600d195..62f7badbf 100644
--- a/src/nautilus-query.c
+++ b/src/nautilus-query.c
@@ -46,6 +46,7 @@ struct _NautilusQuery
GPtrArray *date_range;
NautilusQuerySearchType search_type;
NautilusQuerySearchContent search_content;
+ gboolean search_favorite;
gboolean searching;
gboolean recursive;
@@ -343,6 +344,7 @@ nautilus_query_init (NautilusQuery *query)
query->location = g_file_new_for_path (g_get_home_dir ());
query->search_type = g_settings_get_enum (nautilus_preferences, "search-filter-time-type");
query->search_content = NAUTILUS_QUERY_SEARCH_CONTENT_SIMPLE;
+ query->search_favorite = FALSE;
g_mutex_init (&query->prepared_words_mutex);
}
@@ -550,6 +552,23 @@ nautilus_query_set_search_content (NautilusQuery *query,
}
}
+gboolean
+nautilus_query_get_search_favorite (NautilusQuery *query)
+{
+ g_return_val_if_fail (NAUTILUS_IS_QUERY (query), FALSE);
+
+ return query->search_favorite;
+}
+
+void
+nautilus_query_set_search_favorite (NautilusQuery *query,
+ gboolean search_favorite)
+{
+ g_return_if_fail (NAUTILUS_IS_QUERY (query));
+
+ query->search_favorite = search_favorite;
+}
+
NautilusQuerySearchType
nautilus_query_get_search_type (NautilusQuery *query)
{
diff --git a/src/nautilus-query.h b/src/nautilus-query.h
index 2264f505f..5367844b2 100644
--- a/src/nautilus-query.h
+++ b/src/nautilus-query.h
@@ -59,6 +59,10 @@ NautilusQuerySearchContent nautilus_query_get_search_content (NautilusQuery *que
void nautilus_query_set_search_content (NautilusQuery *query,
NautilusQuerySearchContent content);
+gboolean nautilus_query_get_search_favorite (NautilusQuery *query);
+void nautilus_query_set_search_favorite (NautilusQuery *query,
+ gboolean search_favorite);
+
NautilusQuerySearchType nautilus_query_get_search_type (NautilusQuery *query);
void nautilus_query_set_search_type (NautilusQuery *query,
NautilusQuerySearchType type);
diff --git a/src/nautilus-search-engine-model.c b/src/nautilus-search-engine-model.c
index b00f3f7c5..f6330daaf 100644
--- a/src/nautilus-search-engine-model.c
+++ b/src/nautilus-search-engine-model.c
@@ -27,6 +27,7 @@
#include "nautilus-directory-private.h"
#include "nautilus-file.h"
#include "nautilus-ui-utilities.h"
+#include "nautilus-tag-manager.h"
#define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH
#include "nautilus-debug.h"
@@ -139,6 +140,7 @@ model_directory_ready_cb (NautilusDirectory *directory,
GDateTime *initial_date;
GDateTime *end_date;
GPtrArray *date_range;
+ NautilusTagManager *tag_manager;
files = nautilus_directory_get_file_list (directory);
mime_types = nautilus_query_get_mime_types (model->query);
@@ -191,12 +193,27 @@ model_directory_ready_cb (NautilusDirectory *directory,
g_ptr_array_unref (date_range);
}
+ if (nautilus_query_get_search_favorite (model->query))
+ {
+ tag_manager = nautilus_tag_manager_get ();
+
+ uri = nautilus_file_get_uri (file);
+
+ if (!nautilus_tag_manager_file_is_favorite (tag_manager, uri))
+ {
+ found = FALSE;
+ }
+
+ g_free (uri);
+ }
+
if (found)
{
uri = nautilus_file_get_uri (file);
hit = nautilus_search_hit_new (uri);
nautilus_search_hit_set_fts_rank (hit, match);
hits = g_list_prepend (hits, hit);
+
g_free (uri);
}
diff --git a/src/nautilus-search-engine-simple.c b/src/nautilus-search-engine-simple.c
index ea2dbe364..fee7d2633 100644
--- a/src/nautilus-search-engine-simple.c
+++ b/src/nautilus-search-engine-simple.c
@@ -24,6 +24,7 @@
#include "nautilus-search-provider.h"
#include "nautilus-search-engine-simple.h"
#include "nautilus-ui-utilities.h"
+#include "nautilus-tag-manager.h"
#define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH
#include "nautilus-debug.h"
@@ -222,7 +223,8 @@ visit_directory (GFile *dir,
GPtrArray *date_range;
GDateTime *initial_date;
GDateTime *end_date;
-
+ NautilusTagManager *tag_manager;
+ gchar *uri;
enumerator = g_file_enumerate_children (dir,
data->mime_types != NULL ?
@@ -299,11 +301,24 @@ visit_directory (GFile *dir,
g_ptr_array_unref (date_range);
}
+ if (nautilus_query_get_search_favorite (data->query))
+ {
+ tag_manager = nautilus_tag_manager_get ();
+
+ uri = g_file_get_uri (child);
+
+ if (!nautilus_tag_manager_file_is_favorite (tag_manager, uri))
+ {
+ found = FALSE;
+ }
+
+ g_free (uri);
+ }
+
if (found)
{
NautilusSearchHit *hit;
GDateTime *date;
- char *uri;
uri = g_file_get_uri (child);
hit = nautilus_search_hit_new (uri);
diff --git a/src/nautilus-search-engine-tracker.c b/src/nautilus-search-engine-tracker.c
index 4c917ddf5..5954c1fa6 100644
--- a/src/nautilus-search-engine-tracker.c
+++ b/src/nautilus-search-engine-tracker.c
@@ -353,6 +353,11 @@ nautilus_search_engine_tracker_start (NautilusSearchProvider *provider)
g_string_append_printf (sparql, "; fts:match '\"%s\"*'", search_text);
}
+ if (nautilus_query_get_search_favorite (tracker->query))
+ {
+ g_string_append_printf (sparql, "; nao:hasTag nao:predefined-tag-favorite");
+ }
+
if (mime_count > 0)
{
g_string_append (sparql, "; nie:mimeType ?mime");
diff --git a/src/nautilus-tag-manager.c b/src/nautilus-tag-manager.c
new file mode 100644
index 000000000..9fdad5b52
--- /dev/null
+++ b/src/nautilus-tag-manager.c
@@ -0,0 +1,887 @@
+/* nautilus-tag-manager.c
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru.pandelea@gmail.com>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nautilus-tag-manager.h"
+#include "nautilus-file.h"
+#include "nautilus-file-undo-operations.h"
+#include "nautilus-file-undo-manager.h"
+
+#include <tracker-sparql.h>
+
+struct _NautilusTagManager
+{
+ GObject object;
+
+ TrackerNotifier *notifier;
+ GError *notifier_error;
+
+ GHashTable *favorite_files;
+ GCancellable *cancellable;
+};
+
+G_DEFINE_TYPE (NautilusTagManager, nautilus_tag_manager, G_TYPE_OBJECT);
+
+static NautilusTagManager *tag_manager = NULL;
+
+typedef enum
+{
+ GET_FAVORITE_FILES,
+ GET_IDS_FOR_URLS
+} OperationType;
+
+typedef struct
+{
+ GTask *task;
+ GList *selection;
+ GHashTable *ids;
+ GObject *object;
+ GAsyncReadyCallback callback;
+ GCancellable *cancellable;
+} InsertTaskData;
+
+typedef struct
+{
+ NautilusTagManager *tag_manager;
+ GTask *task;
+ GList *selection;
+ gboolean star;
+ GHashTable *ids;
+} UpdateData;
+
+enum
+{
+ FAVORITES_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static const gchar*
+nautilus_tag_manager_file_with_id_changed_url (GHashTable *hash_table,
+ gint64 id,
+ const gchar *url)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, hash_table);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ if ((gint64) value == id && g_strcmp0 (url, key) != 0)
+ {
+ return key;
+ }
+ }
+
+ return NULL;
+}
+
+void
+destroy_insert_task_data (gpointer data)
+{
+ InsertTaskData *task_data;
+
+ task_data = data;
+
+ nautilus_file_list_free (task_data->selection);
+ g_free (data);
+}
+
+static GString*
+add_selection_filter (GList *selection,
+ GString *query)
+{
+ NautilusFile *file;
+ GList *l;
+ gchar *uri;
+
+ g_string_append (query, " FILTER(?url IN (");
+
+ for (l = selection; l != NULL; l = l->next)
+ {
+ file = l->data;
+
+ uri = nautilus_file_get_uri (file);
+
+ g_string_append_printf (query, "'%s'", uri);
+
+ if (l->next != NULL)
+ {
+ g_string_append (query, ", ");
+ }
+
+ g_free (uri);
+ }
+
+ g_string_append (query, "))");
+
+ return query;
+}
+
+static void
+start_query_or_update (GString *query,
+ GAsyncReadyCallback callback,
+ gpointer user_data,
+ gboolean is_query,
+ GCancellable *cancellable)
+{
+ g_autoptr (GError) error = NULL;
+ TrackerSparqlConnection *connection;
+
+ connection = tracker_sparql_connection_get (cancellable, &error);
+ if (!connection)
+ {
+ if (error)
+ {
+ g_warning ("Error on getting connection: %s", error->message);
+ }
+
+ return;
+ }
+
+ if (is_query)
+ {
+ tracker_sparql_connection_query_async (connection,
+ query->str,
+ cancellable,
+ callback,
+ user_data);
+ }
+ else
+ {
+ tracker_sparql_connection_update_async (connection,
+ query->str,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data);
+ }
+
+ g_object_unref (connection);
+}
+
+static void
+on_query_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data,
+ GAsyncReadyCallback callback,
+ OperationType op_type,
+ GCancellable *cancellable)
+{
+ TrackerSparqlCursor *cursor;
+ g_autoptr (GError) error = NULL;
+ TrackerSparqlConnection *connection;
+ GTask *task;
+
+ task = user_data;
+
+ connection = TRACKER_SPARQL_CONNECTION (object);
+
+ cursor = tracker_sparql_connection_query_finish (connection,
+ result,
+ &error);
+
+ if (error != NULL)
+ {
+ if (error->code != G_IO_ERROR_CANCELLED)
+ {
+ if (op_type == GET_FAVORITE_FILES)
+ {
+ g_warning ("Error on getting favorite files: %s", error->message);
+ }
+ else if (op_type == GET_IDS_FOR_URLS)
+ {
+ g_warning ("Error on getting id for url: %s", error->message);
+ g_task_return_pointer (task, g_task_get_task_data (task), NULL);
+ g_object_unref (task);
+ }
+ else
+ {
+ g_warning ("Error on getting query callback: %s", error->message);
+ }
+ }
+ }
+ else
+ {
+ tracker_sparql_cursor_next_async (cursor,
+ cancellable,
+ callback,
+ user_data);
+ }
+}
+
+static void
+on_update_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlConnection *connection;
+ GError *error;
+ UpdateData *data;
+ gint64 *id;
+ GList *l;
+ gchar *uri;
+
+ data = user_data;
+
+ error = NULL;
+
+ connection = TRACKER_SPARQL_CONNECTION (object);
+
+ tracker_sparql_connection_update_finish (connection, result, &error);
+
+ if (error == NULL ||
+ (error != NULL && error->code != G_IO_ERROR_CANCELLED))
+ {
+ for (l = data->selection; l != NULL; l = l->next)
+ {
+ uri = nautilus_file_get_uri (NAUTILUS_FILE (l->data));
+
+ if (data->star)
+ {
+ if (g_hash_table_contains (data->ids, uri))
+ {
+ id = g_new0 (gint64, 1);
+
+ *id = (gint64) g_hash_table_lookup (data->ids, uri);
+ g_hash_table_insert (data->tag_manager->favorite_files,
+ nautilus_file_get_uri (NAUTILUS_FILE (l->data)),
+ id);
+ }
+ }
+ else
+ {
+ g_hash_table_remove (data->tag_manager->favorite_files, uri);
+ }
+
+ g_free (uri);
+ }
+
+ if (!nautilus_file_undo_manager_is_operating ())
+ {
+ NautilusFileUndoInfo *undo_info;
+
+ undo_info = nautilus_file_undo_info_favorites_new (data->selection, data->star);
+ nautilus_file_undo_manager_set_action (undo_info);
+
+ g_object_unref (undo_info);
+ }
+
+ g_signal_emit_by_name (data->tag_manager, "favorites-changed", nautilus_file_list_copy (data->selection));
+
+ g_task_return_boolean (data->task, TRUE);
+ g_object_unref (data->task);
+ }
+ else if (error && error->code == G_IO_ERROR_CANCELLED)
+ {
+ g_error_free (error);
+ }
+ else
+ {
+ g_task_return_error (data->task, error);
+ g_object_unref (data->task);
+ g_warning ("error updating tags: %s", error->message);
+ }
+
+ if (data->ids)
+ {
+ g_hash_table_destroy (data->ids);
+ }
+ nautilus_file_list_free (data->selection);
+ g_free (data);
+}
+
+static gboolean
+get_query_status (TrackerSparqlCursor *cursor,
+ GAsyncResult *result,
+ OperationType op_type,
+ gpointer user_data)
+{
+ gboolean success;
+ GTask *task;
+ g_autoptr (GError) error = NULL;
+
+ task = user_data;
+
+ success = tracker_sparql_cursor_next_finish (cursor, result, &error);
+
+ if (!success)
+ {
+ if (error)
+ {
+ g_warning ("Error on getting all tags cursor callback: %s", error->message);
+ }
+
+ g_clear_object (&cursor);
+
+ if (error == NULL ||
+ (error != NULL && error->code != G_IO_ERROR_CANCELLED))
+ {
+ if (op_type == GET_IDS_FOR_URLS)
+ {
+ g_task_return_pointer (task, g_task_get_task_data (task), NULL);
+ g_object_unref (task);
+ }
+ }
+ }
+
+ return success;
+}
+
+GList*
+nautilus_tag_manager_get_favorite_files (NautilusTagManager *self)
+{
+ return g_hash_table_get_keys (self->favorite_files);
+}
+
+static void
+on_get_favorite_files_cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlCursor *cursor;
+ const gchar *url;
+ gint64 *id;
+ gboolean success;
+ NautilusTagManager *self;
+ GList *changed_files;
+ NautilusFile *file;
+
+ cursor = TRACKER_SPARQL_CURSOR (object);
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ success = get_query_status (cursor, result, GET_FAVORITE_FILES, NULL);
+ if (!success)
+ {
+ return;
+ }
+
+ id = g_new0 (gint64, 1);
+
+ url = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+ *id = tracker_sparql_cursor_get_integer (cursor, 1);
+
+ g_hash_table_insert (self->favorite_files,
+ g_strdup (url),
+ id);
+
+ file = nautilus_file_get_by_uri (url);
+ changed_files = g_list_prepend (NULL, file);
+
+ g_signal_emit_by_name (self, "favorites-changed", changed_files);
+
+ nautilus_file_list_free (changed_files);
+
+ tracker_sparql_cursor_next_async (cursor,
+ self->cancellable,
+ on_get_favorite_files_cursor_callback,
+ self);
+}
+
+static void
+on_get_favorite_files_query_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ NautilusTagManager *self;
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ on_query_callback (object,
+ result,
+ user_data,
+ on_get_favorite_files_cursor_callback,
+ GET_FAVORITE_FILES,
+ self->cancellable);
+}
+
+static void
+nautilus_tag_manager_query_favorite_files (NautilusTagManager *self,
+ GCancellable *cancellable)
+{
+ GString *query;
+
+ self->cancellable = cancellable;
+
+ query = g_string_new ("SELECT ?url tracker:id(?urn) WHERE { ?urn nie:url ?url ; nao:hasTag nao:predefined-tag-favorite}");
+
+ start_query_or_update (query,
+ on_get_favorite_files_query_callback,
+ self,
+ TRUE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+static gpointer
+nautilus_tag_manager_gpointer_task_finish (GObject *source_object,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (res, source_object), FALSE);
+
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static GString*
+nautilus_tag_manager_delete_tag (NautilusTagManager *self,
+ GList *selection,
+ GString *query)
+{
+
+ g_string_append (query,
+ "DELETE { ?urn nao:hasTag nao:predefined-tag-favorite }"
+ "WHERE { ?urn a nfo:FileDataObject ; nie:url ?url .");
+
+ query = add_selection_filter (selection, query);
+
+ g_string_append (query, "}\n");
+
+ return query;
+}
+
+static GString*
+nautilus_tag_manager_insert_tag (NautilusTagManager *self,
+ GList *selection,
+ GString *query)
+{
+ g_string_append (query,
+ "INSERT { ?urn nao:hasTag nao:predefined-tag-favorite }"
+ "WHERE { ?urn a nfo:FileDataObject ; nie:url ?url .");
+
+ query = add_selection_filter (selection, query);
+
+ g_string_append (query, "}\n");
+
+ return query;
+}
+
+gboolean
+nautilus_tag_manager_file_is_favorite (NautilusTagManager *self,
+ const gchar *file_name)
+{
+ return g_hash_table_contains (self->favorite_files, file_name);
+}
+
+static void
+on_get_file_ids_for_urls_cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlCursor *cursor;
+ GTask *task;
+ gint64 *id;
+ const gchar *url;
+ gboolean success;
+ GList *l;
+ gchar *file_url;
+ InsertTaskData *data;
+
+ task = user_data;
+ data = g_task_get_task_data (task);
+
+ cursor = TRACKER_SPARQL_CURSOR (object);
+
+ success = get_query_status (cursor, result, GET_IDS_FOR_URLS, task);
+ if (!success)
+ {
+ return;
+ }
+
+ id = g_new0 (gint64, 1);
+
+ url = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+ *id = tracker_sparql_cursor_get_integer (cursor, 1);
+
+ for (l = data->selection; l != NULL; l = l->next)
+ {
+ file_url = nautilus_file_get_uri (NAUTILUS_FILE (l->data));
+
+ if (g_strcmp0 (file_url, url) == 0)
+ {
+ g_hash_table_insert (data->ids,
+ g_strdup (url),
+ id);
+
+ g_free (file_url);
+
+ break;
+ }
+
+ g_free (file_url);
+ }
+
+ tracker_sparql_cursor_next_async (cursor,
+ g_task_get_cancellable (task),
+ on_get_file_ids_for_urls_cursor_callback,
+ task);
+
+}
+
+
+static void
+on_get_file_ids_for_urls_query_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = user_data;
+
+ on_query_callback (object,
+ result,
+ user_data,
+ on_get_file_ids_for_urls_cursor_callback,
+ GET_IDS_FOR_URLS,
+ g_task_get_cancellable (task));
+}
+
+static void
+nautilus_tag_manager_get_file_ids_for_urls (GObject *object,
+ GList *selection,
+ GTask *task)
+{
+ GString *query;
+
+ query = g_string_new ("SELECT ?url tracker:id(?urn) WHERE { ?urn nie:url ?url; .");
+
+ query = add_selection_filter (selection, query);
+
+ g_string_append (query, "}\n");
+
+ start_query_or_update (query,
+ on_get_file_ids_for_urls_query_callback,
+ task,
+ TRUE,
+ g_task_get_cancellable (task));
+
+ g_string_free (query, TRUE);
+}
+
+static void
+on_star_files_callback (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusTagManager *self;
+ GString *query;
+ InsertTaskData *data;
+ g_autoptr (GError) error = NULL;
+ GTask *task;
+ UpdateData *update_data;
+
+ self = NAUTILUS_TAG_MANAGER (object);
+
+ data = nautilus_tag_manager_gpointer_task_finish (object, res, &error);
+
+ task = g_task_new (data->object, data->cancellable, data->callback, NULL);
+
+ query = g_string_new ("");
+
+ query = nautilus_tag_manager_insert_tag (self,
+ data->selection,
+ query);
+
+ update_data = g_new0 (UpdateData, 1);
+ update_data->task = task;
+ update_data->tag_manager = self;
+ update_data->selection = nautilus_file_list_copy (data->selection);
+ update_data->star = TRUE;
+ update_data->ids = data->ids;
+
+ /* the ids hash table is now owned by the update_data,
+ * so it will be freed by it.
+ */
+ destroy_insert_task_data (data);
+
+ start_query_or_update (query,
+ on_update_callback,
+ update_data,
+ FALSE,
+ g_task_get_cancellable (task));
+
+ g_string_free (query, TRUE);
+}
+
+void
+nautilus_tag_manager_star_files (NautilusTagManager *self,
+ GObject *object,
+ GList *selection,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable)
+{
+ GTask *task;
+ InsertTaskData *data;
+
+ data = g_new0 (InsertTaskData, 1);
+ data->selection = nautilus_file_list_copy (selection);
+ data->ids = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+ data->callback = callback;
+ data->object = object;
+ data->cancellable = cancellable;
+
+ task = g_task_new (self, cancellable, on_star_files_callback, NULL);
+ g_task_set_task_data (task,
+ data,
+ NULL);
+
+ nautilus_tag_manager_get_file_ids_for_urls (G_OBJECT (self), selection, task);
+}
+
+void
+nautilus_tag_manager_unstar_files (NautilusTagManager *self,
+ GObject *object,
+ GList *selection,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable)
+{
+ GString *query;
+ GTask *task;
+ NautilusFile *file;
+ UpdateData *update_data;
+
+ file = NAUTILUS_FILE (selection->data);
+
+ task = g_task_new (object, cancellable, callback, NULL);
+
+ query = g_string_new ("");
+
+ query = nautilus_tag_manager_delete_tag (self,
+ selection,
+ query);
+
+ update_data = g_new0 (UpdateData, 1);
+ update_data->task = task;
+ update_data->tag_manager = self;
+ update_data->selection = nautilus_file_list_copy (selection);
+ update_data->star = FALSE;
+
+ start_query_or_update (query,
+ on_update_callback,
+ update_data,
+ FALSE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+void
+on_tracker_notifier_events(TrackerNotifier *notifier,
+ GPtrArray *events,
+ gpointer user_data)
+{
+ TrackerNotifierEvent *event;
+ NautilusTagManager *self;
+ int i;
+ const gchar *location_uri;
+ const gchar *new_location_uri;
+ GError *error = NULL;
+ TrackerSparqlConnection *connection;
+ TrackerSparqlCursor *cursor;
+ GString *query;
+ gboolean query_has_results;
+ gint64 *id;
+ GList *changed_files;
+ NautilusFile *file;
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ for (i = 0; i < events->len; i++)
+ {
+ event = g_ptr_array_index (events, i);
+
+ location_uri = tracker_notifier_event_get_location (event);
+
+ query = g_string_new ("");
+ g_string_append_printf (query,
+ "SELECT ?url WHERE { ?urn nie:url ?url; nao:hasTag nao:predefined-tag-favorite . FILTER (tracker:id(?urn) = %ld)}",
+ tracker_notifier_event_get_id (event));
+
+ /* check if the file changed it's location and update hash table if so */
+ new_location_uri = nautilus_tag_manager_file_with_id_changed_url (self->favorite_files,
+ tracker_notifier_event_get_id (event),
+ location_uri);
+ if (new_location_uri)
+ {
+ id = g_new0 (gint64, 1);
+ *id = tracker_notifier_event_get_id (event);
+
+ g_hash_table_remove (self->favorite_files, new_location_uri);
+ g_hash_table_insert (self->favorite_files,
+ g_strdup (location_uri),
+ id);
+
+ file = nautilus_file_get_by_uri (location_uri);
+ changed_files = g_list_prepend (NULL, file);
+
+ g_signal_emit_by_name (self, "favorites-changed", changed_files);
+
+ nautilus_file_list_free (changed_files);
+ }
+
+ connection = tracker_sparql_connection_get (NULL, &error);
+
+ if (!connection)
+ {
+ g_printerr ("Couldn't obtain a direct connection to the Tracker store: %s",
+ error ? error->message : "unknown error");
+ g_clear_error (&error);
+
+ return;
+ }
+
+ cursor = tracker_sparql_connection_query (connection,
+ query->str,
+ NULL,
+ &error);
+
+ if (error)
+ {
+ g_printerr ("Couldn't query the Tracker Store: '%s'", error->message);
+
+ g_clear_error (&error);
+
+ return;
+ }
+
+ if (cursor)
+ {
+ query_has_results = tracker_sparql_cursor_next (cursor, NULL, &error);
+
+ /* if no results are found, then the file isn't marked as favorite.
+ * If needed, update the hashtable.
+ */
+ if (!query_has_results && location_uri && g_hash_table_contains (self->favorite_files, location_uri))
+ {
+ g_hash_table_remove (self->favorite_files, location_uri);
+
+ file = nautilus_file_get_by_uri (location_uri);
+ changed_files = g_list_prepend (NULL, file);
+
+ g_signal_emit_by_name (self, "favorites-changed", changed_files);
+
+ nautilus_file_list_free (changed_files);
+ }
+ else if (query_has_results && location_uri && !g_hash_table_contains (self->favorite_files, location_uri))
+ {
+ id = g_new0 (gint64, 1);
+ *id = tracker_notifier_event_get_id (event);
+
+ g_hash_table_insert (self->favorite_files,
+ g_strdup (location_uri),
+ id);
+
+ file = nautilus_file_get_by_uri (location_uri);
+ changed_files = g_list_prepend (NULL, file);
+
+ g_signal_emit_by_name (self, "favorites-changed", changed_files);
+
+ nautilus_file_list_free (changed_files);
+ }
+
+ g_object_unref (cursor);
+ }
+
+ g_object_unref (connection);
+
+ g_string_free (query, TRUE);
+ }
+
+}
+
+static void
+nautilus_tag_manager_finalize (GObject *object)
+{
+ NautilusTagManager *self;
+
+ self = NAUTILUS_TAG_MANAGER (object);
+
+ g_signal_handlers_disconnect_by_func (self->notifier,
+ G_CALLBACK (on_tracker_notifier_events),
+ self);
+ g_clear_object (&self->notifier);
+
+ g_hash_table_destroy (self->favorite_files);
+
+ G_OBJECT_CLASS (nautilus_tag_manager_parent_class)->finalize (object);
+}
+
+static void
+nautilus_tag_manager_class_init (NautilusTagManagerClass *klass)
+{
+ GObjectClass *oclass;
+
+ oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = nautilus_tag_manager_finalize;
+
+ signals[FAVORITES_CHANGED] = g_signal_new ("favorites-changed",
+ NAUTILUS_TYPE_TAG_MANAGER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_POINTER);
+}
+
+NautilusTagManager* nautilus_tag_manager_get ()
+{
+ if (tag_manager != NULL)
+ {
+ return g_object_ref (tag_manager);
+ }
+
+ tag_manager = g_object_new (NAUTILUS_TYPE_TAG_MANAGER, NULL);
+ g_object_add_weak_pointer (G_OBJECT (tag_manager), (gpointer)&tag_manager);
+
+ return tag_manager;
+}
+
+void nautilus_tag_manager_set_cancellable (NautilusTagManager *tag_manager,
+ GCancellable *cancellable)
+{
+ nautilus_tag_manager_query_favorite_files (tag_manager, cancellable);
+
+ tag_manager->notifier = tracker_notifier_new (NULL,
+ TRACKER_NOTIFIER_FLAG_QUERY_LOCATION,
+ cancellable,
+ &tag_manager->notifier_error);
+
+ g_signal_connect (tag_manager->notifier,
+ "events",
+ G_CALLBACK (on_tracker_notifier_events),
+ tag_manager);
+
+}
+
+static void
+nautilus_tag_manager_init (NautilusTagManager *self)
+{
+ self->favorite_files = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+}
diff --git a/src/nautilus-tag-manager.h b/src/nautilus-tag-manager.h
new file mode 100644
index 000000000..4072dd083
--- /dev/null
+++ b/src/nautilus-tag-manager.h
@@ -0,0 +1,57 @@
+/* nautilus-tag-manager.h
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru.pandelea@gmail.com>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NAUTILUS_TAG_MANAGER_H
+#define NAUTILUS_TAG_MANAGER_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_TAG_MANAGER (nautilus_tag_manager_get_type ())
+
+G_DECLARE_FINAL_TYPE (NautilusTagManager, nautilus_tag_manager, NAUTILUS, TAG_MANAGER, GObject);
+
+NautilusTagManager* nautilus_tag_manager_get ();
+
+void nautilus_tag_manager_set_cancellable (NautilusTagManager *tag_manager,
+ GCancellable *cancellable);
+
+GList* nautilus_tag_manager_get_favorite_files (NautilusTagManager *self);
+
+void nautilus_tag_manager_star_files (NautilusTagManager *self,
+ GObject *object,
+ GList *selection,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable);
+
+void nautilus_tag_manager_unstar_files (NautilusTagManager *self,
+ GObject *object,
+ GList *selection,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable);
+
+
+gboolean nautilus_tag_manager_file_is_favorite (NautilusTagManager *self,
+ const gchar *file_name);
+
+G_END_DECLS
+
+#endif \ No newline at end of file
diff --git a/src/nautilus-window.c b/src/nautilus-window.c
index 653b9cfc1..df669f81a 100644
--- a/src/nautilus-window.c
+++ b/src/nautilus-window.c
@@ -1140,6 +1140,19 @@ places_sidebar_show_other_locations_with_flags (NautilusWindow *window,
g_object_unref (location);
}
+static void
+places_sidebar_show_starred_location (NautilusWindow *window,
+ GtkPlacesOpenFlags open_flags)
+{
+ GFile *location;
+
+ location = g_file_new_for_uri ("favorites:///");
+
+ open_location_cb (window, location, open_flags);
+
+ g_object_unref (location);
+}
+
static GList *
build_selection_list_from_gfile_list (GList *gfile_list)
{
@@ -2947,6 +2960,7 @@ nautilus_window_class_init (NautilusWindowClass *class)
gtk_widget_class_bind_template_child_private (wclass, NautilusWindow, notification_operation_close);
gtk_widget_class_bind_template_callback (wclass, places_sidebar_show_other_locations_with_flags);
+ gtk_widget_class_bind_template_callback (wclass, places_sidebar_show_starred_location);
properties[PROP_DISABLE_CHROME] =
g_param_spec_boolean ("disable-chrome",
diff --git a/src/resources/nautilus.gresource.xml b/src/resources/nautilus.gresource.xml
index 3dd60ba6d..de42f6e9d 100644
--- a/src/resources/nautilus.gresource.xml
+++ b/src/resources/nautilus.gresource.xml
@@ -18,8 +18,10 @@
<file>ui/nautilus-no-search-results.ui</file>
<file>ui/nautilus-folder-is-empty.ui</file>
<file>ui/nautilus-trash-is-empty.ui</file>
+ <file>ui/nautilus-starred-is-empty.ui</file>
<file>gtk/help-overlay.ui</file>
<file>ui/nautilus-batch-rename-dialog.ui</file>
+ <file>ui/nautilus-tags-dialog.ui</file>
<file alias="gtk/ui/nautilusgtkplacesview.ui">../gtk/nautilusgtkplacesview.ui</file>
<file alias="gtk/ui/nautilusgtkplacesviewrow.ui">../gtk/nautilusgtkplacesviewrow.ui</file>
<file alias="icons/thumbnail_frame.png">../../icons/thumbnail_frame.png</file>
diff --git a/src/resources/ui/nautilus-files-view-context-menus.ui b/src/resources/ui/nautilus-files-view-context-menus.ui
index 4a53de439..377b71f57 100644
--- a/src/resources/ui/nautilus-files-view-context-menus.ui
+++ b/src/resources/ui/nautilus-files-view-context-menus.ui
@@ -253,6 +253,23 @@
</section>
<section>
<item>
+ <attribute name="label" translatable="yes">Tags</attribute>
+ <attribute name="action">view.edit-tags</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Star</attribute>
+ <attribute name="action">view.star</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Unstar</attribute>
+ <attribute name="action">view.unstar</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
<attribute name="label" translatable="yes">P_roperties</attribute>
<attribute name="action">view.properties</attribute>
</item>
diff --git a/src/resources/ui/nautilus-starred-is-empty.ui b/src/resources/ui/nautilus-starred-is-empty.ui
new file mode 100644
index 000000000..a0482a05a
--- /dev/null
+++ b/src/resources/ui/nautilus-starred-is-empty.ui
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <object class="GtkGrid" id="starred_is_empty">
+ <property name="visible">False</property>
+ <property name="row_spacing">12</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">starred-symbolic</property>
+ <property name="pixel-size">72</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Starred files will appear here</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.44"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/src/resources/ui/nautilus-tags-dialog.ui b/src/resources/ui/nautilus-tags-dialog.ui
new file mode 100644
index 000000000..5139c7ced
--- /dev/null
+++ b/src/resources/ui/nautilus-tags-dialog.ui
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="NautilusTagsDialog" parent="GtkDialog">
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <signal name="response" handler="nautilus_tags_dialog_on_response"/>
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="update_tags_button">
+ <property name="label" translatable="yes">_Update Tags</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="ok">update_tags_button</action-widget>
+ <action-widget response="cancel">cancel_button</action-widget>
+ </action-widgets>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="vbox">
+ <property name="border-width">0</property>
+ <child>
+ <object class="GtkGrid" id="grid">
+ <property name="visible">True</property>
+ <property name="margin">10</property>
+ <property name="row-spacing">6</property>
+ <property name="column-spacing">6</property>
+ <property name="hexpand">True</property>
+ <property name="row-homogeneous">False</property>
+ <property name="column-homogeneous">False</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled_tags">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">False</property>
+ <property name="vexpand">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="selection_tags_box">
+ <property name="orientation">horizontal</property>
+ <property name="visible">True</property>
+ <property name="spacing">5</property>
+ <property name="halign">start</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">0</property>
+ <property name="visible">True</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkEntry" id="tags_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="width_request">250</property>
+ <property name="hexpand">True</property>
+ <property name="activates-default">True</property>
+ <signal name="activate" handler="on_tags_entry_activate" swapped="yes" />
+ <signal name="changed" handler="on_tags_entry_changed" swapped="yes" />
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="add_tag_button">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="label" translatable="yes">Add</property>
+ <signal name="clicked" handler="on_add_button_clicked" swapped="no" />
+ </object>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">horizontal</property>
+ <child>
+ <object class="GtkLabel" id="color_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">New Tag Color: </property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="color_button">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled_window">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">False</property>
+ <property name="vexpand">True</property>
+ <property name="shadow_type">in</property>
+ <property name="max-content-height">200</property>
+ <property name="min-content-height">200</property>
+ <property name="max-content-width">150</property>
+ <property name="min-content-width">150</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkListBox" id="tags_listbox">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface> \ No newline at end of file
diff --git a/src/resources/ui/nautilus-window.ui b/src/resources/ui/nautilus-window.ui
index 2fc5ffcc1..74e8c6293 100644
--- a/src/resources/ui/nautilus-window.ui
+++ b/src/resources/ui/nautilus-window.ui
@@ -34,7 +34,9 @@
<property name="visible">True</property>
<property name="populate-all">True</property>
<property name="show-other-locations">True</property>
+ <property name="show-starred-location">True</property>
<signal name="show-other-locations-with-flags" handler="places_sidebar_show_other_locations_with_flags" object="NautilusWindow" swapped="yes" />
+ <signal name="show-starred-location" handler="places_sidebar_show_starred_location" object="NautilusWindow" swapped="yes" />
</object>
<packing>
<property name="pack_type">start</property>