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-09 10:50:57 +0100
commit5d05180eba0adbabc84b2d9a33f4a3e97f01274b (patch)
treea7fc39f4a88c17d279e7223dabca7a794daa98b3
parentc1eb1d2a70d82e38094c0cf2360cfeb976531cfc (diff)
downloadnautilus-wip/alexpandelea/tags.tar.gz
implement tagswip/alexpandelea/tags
-rw-r--r--src/meson.build10
-rw-r--r--src/nautilus-application.c25
-rw-r--r--src/nautilus-bookmark-list.c5
-rw-r--r--src/nautilus-column-utilities.c12
-rw-r--r--src/nautilus-directory.c1
-rw-r--r--src/nautilus-favorite-directory.c577
-rw-r--r--src/nautilus-favorite-directory.h35
-rw-r--r--src/nautilus-file-utilities.c18
-rw-r--r--src/nautilus-file-utilities.h1
-rw-r--r--src/nautilus-file.c86
-rw-r--r--src/nautilus-file.h2
-rw-r--r--src/nautilus-files-view.c128
-rw-r--r--src/nautilus-files-view.h4
-rw-r--r--src/nautilus-list-view-private.h4
-rw-r--r--src/nautilus-list-view.c186
-rw-r--r--src/nautilus-list-view.h1
-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-directory.c1
-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-search-engine.c1
-rw-r--r--src/nautilus-tag-manager.c1399
-rw-r--r--src/nautilus-tag-manager.h113
-rw-r--r--src/nautilus-tag-widget.c304
-rw-r--r--src/nautilus-tag-widget.h51
-rw-r--r--src/nautilus-tags-dialog.c620
-rw-r--r--src/nautilus-tags-dialog.h39
-rw-r--r--src/nautilus-window.c14
-rw-r--r--src/resources/nautilus.gresource.xml1
-rw-r--r--src/resources/ui/nautilus-files-view-context-menus.ui17
-rw-r--r--src/resources/ui/nautilus-tags-dialog.ui157
-rw-r--r--src/resources/ui/nautilus-window.ui2
35 files changed, 3870 insertions, 20 deletions
diff --git a/src/meson.build b/src/meson.build
index 83723b94f..2ca2e84ba 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -264,7 +264,15 @@ if get_option ('enable-tracker')
'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-tags-dialog.c',
+ 'nautilus-tags-dialog.h',
+ 'nautilus-tag-manager.c',
+ 'nautilus-tag-manager.h',
+ 'nautilus-tag-widget.c',
+ 'nautilus-tag-widget.h',
+ 'nautilus-favorite-directory.c',
+ 'nautilus-favorite-directory.h']
endif
nautilus_deps = [glib,
diff --git a/src/nautilus-application.c b/src/nautilus-application.c
index 77443f5f6..108319fc6 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,11 @@ typedef struct
GHashTable *notifications;
NautilusFileUndoManager *undo_manager;
+
+ NautilusTagManager *tag_manager;
+ GCancellable *tag_manager_tags_cancellable;
+ GCancellable *tag_manager_notifier_cancellable;
+ GCancellable *tag_manager_favorite_cancellable;
} NautilusApplicationPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (NautilusApplication, nautilus_application, GTK_TYPE_APPLICATION);
@@ -612,6 +618,17 @@ nautilus_application_finalize (GObject *object)
g_clear_object (&priv->undo_manager);
+ g_clear_object (&priv->tag_manager);
+
+ g_cancellable_cancel (priv->tag_manager_tags_cancellable);
+ g_clear_object (&priv->tag_manager_tags_cancellable);
+
+ g_cancellable_cancel (priv->tag_manager_notifier_cancellable);
+ g_clear_object (&priv->tag_manager_notifier_cancellable);
+
+ g_cancellable_cancel (priv->tag_manager_favorite_cancellable);
+ g_clear_object (&priv->tag_manager_favorite_cancellable);
+
G_OBJECT_CLASS (nautilus_application_parent_class)->finalize (object);
}
@@ -1077,6 +1094,14 @@ nautilus_application_init (NautilusApplication *self)
priv->undo_manager = nautilus_file_undo_manager_new ();
+ priv->tag_manager_tags_cancellable = g_cancellable_new ();
+ priv->tag_manager_notifier_cancellable = g_cancellable_new ();
+ priv->tag_manager_favorite_cancellable = g_cancellable_new ();
+
+ priv->tag_manager = nautilus_tag_manager_new (priv->tag_manager_tags_cancellable,
+ priv->tag_manager_notifier_cancellable,
+ priv->tag_manager_favorite_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..4a2572224 100644
--- a/src/nautilus-column-utilities.c
+++ b/src/nautilus-column-utilities.c
@@ -41,6 +41,7 @@ static const char *default_column_order[] =
"date_modified_with_time",
"date_modified",
"date_accessed",
+ "favorite",
"recency",
NULL
};
@@ -71,6 +72,17 @@ get_builtin_columns (void)
"label", _("Type"),
"description", _("The type of the file."),
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));
+
columns = g_list_append (columns,
g_object_new (NAUTILUS_TYPE_COLUMN,
"name", "date_modified",
diff --git a/src/nautilus-directory.c b/src/nautilus-directory.c
index c2ff9111c..647cee8d7 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"
diff --git a/src/nautilus-favorite-directory.c b/src/nautilus-favorite-directory.c
new file mode 100644
index 000000000..dd76db335
--- /dev/null
+++ b/src/nautilus-favorite-directory.c
@@ -0,0 +1,577 @@
+/* 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 _NautilusFavoriteDirectory
+{
+ NautilusDirectory parent;
+
+ 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->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->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->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->files;
+ while (l != NULL)
+ {
+ uri = nautilus_file_get_uri (NAUTILUS_FILE (l->data));
+
+ if (!nautilus_tag_manager_file_is_favorite (self->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->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->files)
+ {
+ self->files = g_list_delete_link (self->files, l);
+ l = self->files;
+ }
+ else
+ {
+ tmp_l = l->prev;
+ self->files = g_list_delete_link (self->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->files = g_list_prepend (self->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,
+ NautilusFavoriteDirectory *self)
+{
+ 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->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->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->monitor_list = g_list_prepend (favorite->monitor_list, monitor);
+
+ if (callback != NULL)
+ {
+ (*callback)(directory, favorite->files, callback_data);
+ }
+
+ for (list = favorite->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->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->monitor_list; list != NULL; list = list->next)
+ {
+ monitor = list->data;
+
+ if (monitor->client == client)
+ {
+ favorite->monitor_list = g_list_delete_link (favorite->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 (g_strcmp0 (uri, "favorites:///") == 0)
+ {
+ 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->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->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->callback_list = g_list_remove (favorite->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->pending_callback_list = g_list_remove (favorite->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->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->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->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->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->tag_manager,
+ on_favorites_files_changed,
+ self);
+
+ g_object_unref (self->tag_manager);
+ nautilus_file_list_free (self->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->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->monitor_list; monitor_list;
+ monitor_list = monitor_list->next)
+ {
+ monitor = monitor_list->data;
+ nautilus_file_monitor_remove (file, monitor);
+ }
+ }
+
+ /* Remove search monitors */
+ if (favorite->monitor_list)
+ {
+ for (l = favorite->monitor_list; l != NULL; l = l->next)
+ {
+ favorite_monitor_destroy ((FavoriteMonitor*) l->data, favorite);
+ }
+
+ g_list_free (favorite->monitor_list);
+ favorite->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;
+}
+
+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;
+
+ tag_manager = nautilus_tag_manager_new (NULL, NULL, NULL);
+
+ g_signal_connect (tag_manager,
+ "favorites-changed",
+ (GCallback) on_favorites_files_changed,
+ self);
+
+ self->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..3da343d96
--- /dev/null
+++ b/src/nautilus-favorite-directory.h
@@ -0,0 +1,35 @@
+/* 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 ())
+
+G_DECLARE_FINAL_TYPE (NautilusFavoriteDirectory, nautilus_favorite_directory, NAUTILUS, FAVORITE_DIRECTORY, NautilusDirectory);
+NautilusFavoriteDirectory* nautilus_favorite_directory_new ();
+
+G_END_DECLS
+
+#endif
diff --git a/src/nautilus-file-utilities.c b/src/nautilus-file-utilities.c
index 59add50e4..ee60430d7 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 (_("Favorite Files"));
+ }
{
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 (g_strcmp0 (uri, "favorites:///") == 0)
+ 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 29ad7b3c8..c0d5529c2 100644
--- a/src/nautilus-file-utilities.h
+++ b/src/nautilus-file-utilities.h
@@ -46,6 +46,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 daa4009dd..df2b5433d 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>
@@ -129,6 +130,7 @@ static NautilusSpeedTradeoffValue show_directory_item_count;
static GQuark attribute_name_q,
attribute_size_q,
attribute_type_q,
+ attribute_favorite_q,
attribute_detailed_type_q,
attribute_modification_date_q,
attribute_date_modified_q,
@@ -3659,6 +3661,42 @@ 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_new (NULL, NULL, NULL);
+
+ 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) ||
+ (!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)
@@ -3862,6 +3900,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);
@@ -3966,6 +4014,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,
@@ -4557,6 +4612,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 (_("Favorite Files"));
+
return g_strdup (nautilus_file_peek_display_name (file));
}
@@ -8337,6 +8397,31 @@ 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)
+{
+ gboolean is_favorite_location;
+ gchar *uri;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ uri = nautilus_file_get_uri (file);
+ is_favorite_location = g_strcmp0 (uri, "favorites:///") == 0;
+
+ g_free (uri);
+
+ return is_favorite_location;
+}
+
+/**
* nautilus_file_is_in_admin
*
* Check if this file is using admin backend.
@@ -9317,6 +9402,7 @@ nautilus_file_class_init (NautilusFileClass *class)
attribute_name_q = g_quark_from_static_string ("name");
attribute_size_q = g_quark_from_static_string ("size");
attribute_type_q = g_quark_from_static_string ("type");
+ attribute_favorite_q = g_quark_from_static_string ("favorite");
attribute_detailed_type_q = g_quark_from_static_string ("detailed_type");
attribute_modification_date_q = g_quark_from_static_string ("modification_date");
attribute_date_modified_q = g_quark_from_static_string ("date_modified");
diff --git a/src/nautilus-file.h b/src/nautilus-file.h
index c4d17801e..210be647f 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,
@@ -218,6 +219,7 @@ gboolean nautilus_file_is_in_recent (Nautilu
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 3d569131a..60360abaf 100644
--- a/src/nautilus-files-view.c
+++ b/src/nautilus-files-view.c
@@ -31,6 +31,7 @@
#ifdef ENABLE_TRACKER
#include "nautilus-batch-rename-dialog.h"
#include "nautilus-batch-rename-utilities.h"
+#include "nautilus-tags-dialog.h"
#endif
#include "nautilus-error-reporting.h"
#include "nautilus-file-undo-manager.h"
@@ -44,6 +45,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>
@@ -72,6 +74,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"
@@ -274,6 +277,9 @@ typedef struct
gulong stop_signal_handler;
gulong reload_signal_handler;
+
+ GCancellable *favorite_cancellable;
+ NautilusTagManager *tag_manager;
} NautilusFilesViewPrivate;
typedef struct
@@ -1580,6 +1586,85 @@ action_delete (GSimpleAction *action,
}
static void
+action_edit_tags (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ GtkWidget *dialog;
+ NautilusWindow *window;
+ GList *selection;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+
+ window = nautilus_files_view_get_window (view);
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+ dialog = nautilus_tags_dialog_new (selection, window);
+ gtk_widget_show (GTK_WIDGET (dialog));
+}
+
+void
+on_favorite_tags_updated (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GTask *task;
+ NautilusFilesView *view;
+
+ view = NAUTILUS_FILES_VIEW (object);
+
+ if (NAUTILUS_IS_LIST_VIEW (view))
+ {
+ nautilus_list_view_redraw_tree_view (view);
+ }
+
+ task = user_data;
+ g_clear_object (&task);
+}
+
+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,
+ on_favorite_tags_updated,
+ 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,
+ on_favorite_tags_updated,
+ priv->favorite_cancellable);
+}
+
+static void
action_restore_from_trash (GSimpleAction *action,
GVariant *state,
gpointer user_data)
@@ -3236,6 +3321,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);
}
@@ -3484,6 +3572,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);
@@ -7009,6 +7101,9 @@ const GActionEntry view_entries[] =
{ "copy-to", action_copy_to},
{ "move-to-trash", action_move_to_trash},
{ "delete-from-trash", action_delete },
+ { "edit-tags", action_edit_tags},
+ { "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
@@ -7401,6 +7496,9 @@ real_update_actions_state (NautilusFilesView *view)
gboolean settings_show_create_link;
gboolean settings_automatic_decompression;
GDriveStartStopType start_stop_type;
+ gboolean show_star;
+ gboolean show_unstar;
+ gchar *uri;
priv = nautilus_files_view_get_instance_private (view);
@@ -7741,6 +7839,33 @@ 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 (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);
}
@@ -9714,6 +9839,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_new (NULL, NULL, NULL);
+
nautilus_profile_end (NULL);
}
diff --git a/src/nautilus-files-view.h b/src/nautilus-files-view.h
index cc3340ae5..4c3ebfe4e 100644
--- a/src/nautilus-files-view.h
+++ b/src/nautilus-files-view.h
@@ -338,6 +338,10 @@ void nautilus_files_view_action_show_hidden_files (NautilusFilesV
GActionGroup * nautilus_files_view_get_action_group (NautilusFilesView *view);
GtkWidget* nautilus_files_view_get_content_widget (NautilusFilesView *view);
+void on_favorite_tags_updated (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data);
+
G_END_DECLS
#endif /* NAUTILUS_FILES_VIEW_H */
diff --git a/src/nautilus-list-view-private.h b/src/nautilus-list-view-private.h
index e2a09a1e6..f0d17164a 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;
@@ -64,5 +65,8 @@ struct NautilusListViewDetails {
gulong clipboard_handler_id;
GQuark last_sort_attr;
+
+ NautilusTagManager *tag_manager;
+ GCancellable *favorite_cancellable;
};
diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c
index 14a074a05..13beaf550 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>
@@ -106,22 +107,22 @@ G_DEFINE_TYPE (NautilusListView, nautilus_list_view, NAUTILUS_TYPE_FILES_VIEW);
static const char *default_search_visible_columns[] =
{
- "name", "size", "where", NULL
+ "name", "size", "where", "favorite", NULL
};
static const char *default_search_columns_order[] =
{
- "name", "size", "where", NULL
+ "name", "size", "where", "favorite", NULL
};
static const char *default_recent_visible_columns[] =
{
- "name", "size", "where", NULL
+ "name", "size", "where", "favorite", NULL
};
static const char *default_recent_columns_order[] =
{
- "name", "size", "where", NULL
+ "name", "size", "where", "favorite", NULL
};
static const char *default_trash_visible_columns[] =
@@ -149,6 +150,7 @@ get_default_sort_order (NautilusFile *file,
"type",
"date_modified",
"date_accessed",
+ "favorite",
"trashed_on",
NULL
};
@@ -456,6 +458,51 @@ row_activated_callback (GtkTreeView *treeview,
activate_selected_items (view);
}
+void
+nautilus_list_view_redraw_tree_view (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+
+ gtk_widget_queue_draw (GTK_WIDGET (list_view->details->tree_view));
+}
+
+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,
+ on_favorite_tags_updated,
+ list_view->details->favorite_cancellable);
+ }
+ else
+ {
+ nautilus_tag_manager_star_files (list_view->details->tag_manager,
+ G_OBJECT (list_view),
+ selection,
+ on_favorite_tags_updated,
+ list_view->details->favorite_cancellable);
+ }
+
+ nautilus_file_list_free (selection);
+}
+
static gboolean
button_press_callback (GtkWidget *widget,
GdkEventButton *event,
@@ -702,6 +749,33 @@ button_press_callback (GtkWidget *widget,
}
}
+ if (is_simple_click)
+ {
+ GtkTreeViewColumn *column;
+ gchar **visible_columns;
+ gint i;
+ gdouble m;
+
+ visible_columns = get_visible_columns (view);
+
+ for (i = 0; visible_columns[i] != NULL; i++)
+ {
+ column = g_hash_table_lookup (view->details->columns, visible_columns[i]);
+
+ if (g_strcmp0 (visible_columns[i], "favorite") == 0)
+ {
+ m = gtk_tree_view_column_get_width (column) / 2 +
+ gtk_tree_view_column_get_x_offset (column);
+
+ if (event->x > m - 10 && event->x < m + 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 +1620,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,
@@ -2029,13 +2142,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"))
+ {
+ 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,
@@ -2049,10 +2183,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,
@@ -2079,6 +2210,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);
@@ -3170,6 +3307,18 @@ nautilus_list_view_get_zoom_level_percentage (NautilusFilesView *view)
return (gfloat) icon_size / NAUTILUS_LIST_ICON_SIZE_STANDARD;
}
+static gboolean
+nautilus_list_view_is_zoom_level_default (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+ guint icon_size;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ icon_size = nautilus_list_model_get_icon_size_for_zoom_level (list_view->details->zoom_level);
+
+ return icon_size == NAUTILUS_LIST_ICON_SIZE_STANDARD;
+}
+
static void
nautilus_list_view_click_policy_changed (NautilusFilesView *directory_view)
{
@@ -3355,6 +3504,9 @@ nautilus_list_view_finalize (GObject *object)
g_free (list_view->details);
+ g_cancellable_cancel (list_view->details->favorite_cancellable);
+ g_clear_object (&list_view->details->favorite_cancellable);
+
G_OBJECT_CLASS (nautilus_list_view_parent_class)->finalize (object);
}
@@ -3560,6 +3712,7 @@ nautilus_list_view_class_init (NautilusListViewClass *class)
nautilus_files_view_class->can_zoom_in = nautilus_list_view_can_zoom_in;
nautilus_files_view_class->can_zoom_out = nautilus_list_view_can_zoom_out;
nautilus_files_view_class->get_zoom_level_percentage = nautilus_list_view_get_zoom_level_percentage;
+ nautilus_files_view_class->is_zoom_level_default = nautilus_list_view_is_zoom_level_default;
nautilus_files_view_class->click_policy_changed = nautilus_list_view_click_policy_changed;
nautilus_files_view_class->clear = nautilus_list_view_clear;
nautilus_files_view_class->file_changed = nautilus_list_view_file_changed;
@@ -3637,6 +3790,9 @@ nautilus_list_view_init (NautilusListView *list_view)
/* Keep the action synced with the actual value, so the toolbar can poll it */
g_action_group_change_action_state (nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (list_view)),
"zoom-to-level", g_variant_new_int32 (get_default_zoom_level ()));
+
+ list_view->details->tag_manager = nautilus_tag_manager_new (NULL, NULL, NULL);
+ list_view->details->favorite_cancellable = g_cancellable_new ();
}
NautilusFilesView *
diff --git a/src/nautilus-list-view.h b/src/nautilus-list-view.h
index f77543100..cedeb024f 100644
--- a/src/nautilus-list-view.h
+++ b/src/nautilus-list-view.h
@@ -53,4 +53,5 @@ typedef struct {
GType nautilus_list_view_get_type (void);
NautilusFilesView * nautilus_list_view_new (NautilusWindowSlot *slot);
+void nautilus_list_view_redraw_tree_view (NautilusFilesView *view);
#endif /* NAUTILUS_LIST_VIEW_H */
diff --git a/src/nautilus-pathbar.c b/src/nautilus-pathbar.c
index 60817b442..24b1685e9 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))
@@ -331,6 +332,11 @@ get_dir_name (ButtonData *button_data)
return _("Other Locations");
}
+ case FAVORITE_LOCATION_BUTTON:
+ {
+ return _("Favorite Files");
+ }
+
default:
return button_data->dir_name;
}
@@ -1723,6 +1729,10 @@ setup_button_type (ButtonData *button_data,
g_object_unref (mount);
}
+ else if (g_strcmp0 (uri, "favorites:///") == 0)
+ {
+ 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 53771ce66..15a01bfd1 100644
--- a/src/nautilus-query.c
+++ b/src/nautilus-query.c
@@ -42,6 +42,7 @@ struct _NautilusQuery
GPtrArray *date_range;
NautilusQuerySearchType search_type;
NautilusQuerySearchContent search_content;
+ gboolean search_favorite;
gboolean searching;
gboolean recursive;
@@ -339,6 +340,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);
}
@@ -541,6 +543,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-directory.c b/src/nautilus-search-directory.c
index d51f4f861..e04d5cbf4 100644
--- a/src/nautilus-search-directory.c
+++ b/src/nautilus-search-directory.c
@@ -641,6 +641,7 @@ search_engine_hits_added (NautilusSearchEngine *engine,
g_signal_connect (file, "changed", G_CALLBACK (file_changed), search),
file_list = g_list_prepend (file_list, file);
+
g_hash_table_add (search->details->files_hash, file);
}
diff --git a/src/nautilus-search-engine-model.c b/src/nautilus-search-engine-model.c
index b8f2a940f..aa6e47e65 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"
@@ -137,6 +138,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->details->query);
@@ -189,12 +191,27 @@ model_directory_ready_cb (NautilusDirectory *directory,
g_ptr_array_unref (date_range);
}
+ if (nautilus_query_get_search_favorite (model->details->query))
+ {
+ tag_manager = nautilus_tag_manager_new (NULL, NULL, NULL);
+
+ 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..f0d74eaf6 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_new (NULL, NULL, NULL);
+
+ 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 b868dbf89..54bdeeb34 100644
--- a/src/nautilus-search-engine-tracker.c
+++ b/src/nautilus-search-engine-tracker.c
@@ -351,6 +351,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->details->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-search-engine.c b/src/nautilus-search-engine.c
index f2bc0126b..cc0bc3220 100644
--- a/src/nautilus-search-engine.c
+++ b/src/nautilus-search-engine.c
@@ -106,6 +106,7 @@ search_engine_start_real (NautilusSearchEngine *engine)
nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (priv->tracker));
priv->providers_running++;
#endif
+
if (nautilus_search_engine_model_get_model (priv->model))
{
nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (priv->model));
diff --git a/src/nautilus-tag-manager.c b/src/nautilus-tag-manager.c
new file mode 100644
index 000000000..6825aae8f
--- /dev/null
+++ b/src/nautilus-tag-manager.c
@@ -0,0 +1,1399 @@
+/* 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 <tracker-sparql.h>
+
+#define GINT64_TO_POINTER(i) ((gpointer) (gint64) (i))
+#define GPOINTER_TO_GINT64(p) ((gint64) (p))
+
+struct _NautilusTagManager
+{
+ GObject object;
+
+ TrackerNotifier *notifier;
+ GError *notifier_error;
+
+ GQueue *all_tags;
+ GCancellable *all_tags_cancellable;
+
+ GHashTable *favorite_files;
+ GCancellable *favorite_files_cancellable;
+};
+
+G_DEFINE_TYPE (NautilusTagManager, nautilus_tag_manager, G_TYPE_OBJECT);
+
+static NautilusTagManager *tag_manager = NULL;
+
+typedef enum
+{
+ GET_ALL_TAGS,
+ GET_FAVORITE_FILES,
+ GET_SELECTION_TAGS,
+ GET_FILES_WITH_TAG,
+ GET_IDS_FOR_URLS
+} OperationType;
+
+typedef struct
+{
+ GTask *task;
+ GList *selection;
+ GHashTable *ids;
+ GObject *object;
+ GAsyncReadyCallback callback;
+ GCancellable *cancellable;
+} InsertTaskData;
+
+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;
+}
+
+gchar*
+parse_color_from_tag_id (const gchar *tag_id)
+{
+ gchar *color;
+
+ if (g_strrstr (tag_id, "org:gnome:nautilus:tag"))
+ {
+ color = g_strdup (g_strrstr (tag_id, ":") + 1);
+ }
+ else
+ {
+ color = g_strdup ("rgb(220,220,220)");
+ }
+
+ return color;
+}
+
+gboolean
+nautilus_tag_queue_has_tag (GQueue *tag_queue,
+ const gchar *tag_name)
+{
+ GList *l;
+ TagData *tag_data;
+
+ for (l = g_queue_peek_head_link (tag_queue); l != NULL; l = l->next)
+ {
+ tag_data = l->data;
+
+ if (g_strcmp0 (tag_name, tag_data->name) == 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+GQueue*
+nautilus_tag_copy_tag_queue (GQueue *queue)
+{
+ GQueue *result_queue;
+ GList *l;
+ TagData *tag_data, *new_tag_data;
+
+ result_queue = g_queue_new ();
+
+ for (l = g_queue_peek_head_link (queue); l != NULL; l = l->next)
+ {
+ tag_data = l->data;
+
+ new_tag_data = g_new0 (TagData, 1);
+ new_tag_data->id = g_strdup (tag_data->id);
+ new_tag_data->name = g_strdup (tag_data->name);
+ new_tag_data->url = g_strdup (tag_data->url);
+
+ g_queue_push_tail (result_queue, new_tag_data);
+ }
+
+ return result_queue;
+}
+
+void
+destroy_insert_task_data (gpointer data)
+{
+ InsertTaskData *task_data;
+
+ task_data = data;
+
+ nautilus_file_list_free (task_data->selection);
+ g_hash_table_destroy (task_data->ids);
+ g_free (data);
+}
+
+void
+nautilus_tag_data_free (gpointer data)
+{
+ TagData *tag_data;
+
+ tag_data = data;
+
+ g_free (tag_data->id);
+ g_free (tag_data->name);
+ g_free (tag_data->url);
+ g_free (tag_data);
+}
+
+static void
+nautilus_tag_data_queue_free (gpointer data)
+{
+ GQueue *queue;
+
+ queue = data;
+
+ g_queue_free_full (queue, nautilus_tag_data_free);
+}
+
+static void
+destroy_url_queue (gpointer data)
+{
+ GQueue *queue;
+
+ queue = data;
+
+ g_queue_free_full (queue, g_free);
+}
+
+TagData*
+nautilus_tag_data_new (const gchar *id,
+ const gchar *name,
+ const gchar *url)
+{
+ TagData *data;
+
+ data = g_new0 (TagData, 1);
+
+ data->id = g_strdup (id);
+ data->name = g_strdup (name);
+ data->url = g_strdup (url);
+
+ return 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_ALL_TAGS)
+ {
+ g_warning ("error while getting tags: %s\n", error->message);
+ }
+ else if (op_type == GET_SELECTION_TAGS)
+ {
+ g_task_return_pointer (task, g_task_get_task_data (task), nautilus_tag_data_queue_free);
+ }
+ else 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), destroy_insert_task_data);
+ }
+ 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;
+ GTask *task;
+
+ task = 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))
+ {
+ g_task_return_boolean (task, TRUE);
+ }
+ else if (error && error->code == G_IO_ERROR_CANCELLED)
+ {
+ g_error_free (error);
+ }
+ else
+ {
+ g_task_return_error (task, error);
+ g_warning ("error updating tags: %s", error->message);
+ }
+}
+
+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_SELECTION_TAGS)
+ {
+ g_task_return_pointer (task, g_task_get_task_data (task), nautilus_tag_data_queue_free);
+
+ }
+ else if (op_type == GET_FILES_WITH_TAG)
+ {
+ g_task_return_pointer (task, g_task_get_task_data (task), destroy_url_queue);
+ }
+ else if (op_type == GET_IDS_FOR_URLS)
+ {
+ g_task_return_pointer (task, g_task_get_task_data (task), destroy_insert_task_data);
+ }
+ }
+ }
+
+ return success;
+}
+
+static void
+on_get_all_tags_cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlCursor *cursor;
+ const gchar *id;
+ const gchar *name;
+ gboolean success;
+ NautilusTagManager *self;
+ TagData *tag_data;
+
+ cursor = TRACKER_SPARQL_CURSOR (object);
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ success = get_query_status (cursor, result, GET_ALL_TAGS, NULL);
+ if (!success)
+ {
+ return;
+ }
+
+ id = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+ name = tracker_sparql_cursor_get_string (cursor, 1, NULL);
+
+ tag_data = nautilus_tag_data_new (id, name, NULL);
+
+ g_queue_push_tail (self->all_tags, tag_data);
+
+ tracker_sparql_cursor_next_async (cursor,
+ self->all_tags_cancellable,
+ on_get_all_tags_cursor_callback,
+ self);
+}
+
+static void
+on_get_all_tags_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_all_tags_cursor_callback,
+ GET_ALL_TAGS,
+ self->all_tags_cancellable);
+}
+
+GQueue*
+nautilus_tag_manager_get_all_tags (NautilusTagManager *self)
+{
+ return self->all_tags;
+}
+
+GList*
+nautilus_tag_manager_get_favorite_files (NautilusTagManager *self)
+{
+ return g_hash_table_get_keys (self->favorite_files);
+}
+
+static void
+nautilus_tag_manager_query_all_tags (NautilusTagManager *self,
+ GCancellable *cancellable)
+{
+ GString *query;
+
+ if (self->all_tags)
+ {
+ g_queue_free_full (self->all_tags, nautilus_tag_data_free);
+ }
+ self->all_tags = g_queue_new ();
+
+ self->all_tags_cancellable = cancellable;
+
+ query = g_string_new ("SELECT ?urn ?label WHERE { ?urn a nao:Tag ; nao:prefLabel ?label . } ORDER BY ?label");
+
+ start_query_or_update (query,
+ on_get_all_tags_query_callback,
+ self,
+ TRUE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+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;
+
+ cursor = TRACKER_SPARQL_CURSOR (object);
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ success = get_query_status (cursor, result, GET_FAVORITE_FILES, NULL);
+ if (!success)
+ {
+ return;
+ }
+
+ 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),
+ GINT64_TO_POINTER (id));
+ g_signal_emit_by_name (self, "favorites-changed", NULL);
+
+ tracker_sparql_cursor_next_async (cursor,
+ self->favorite_files_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->favorite_files_cancellable);
+}
+
+
+static void
+nautilus_tag_manager_query_favorite_files (NautilusTagManager *self,
+ GCancellable *cancellable)
+{
+ GString *query;
+
+ self->favorite_files_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 void
+on_get_selection_tags_cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlCursor *cursor;
+ const gchar *id;
+ const gchar *name;
+ const gchar *url;
+ gboolean success;
+ TagData *tag_data;
+ GTask *task;
+ GQueue *selection_tags;
+
+ cursor = TRACKER_SPARQL_CURSOR (object);
+
+ task = user_data;
+
+ selection_tags = g_task_get_task_data (task);
+
+ success = get_query_status (cursor, result, GET_SELECTION_TAGS, user_data);
+ if (!success)
+ {
+ return;
+ }
+
+ id = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+ name = tracker_sparql_cursor_get_string (cursor, 1, NULL);
+ url = tracker_sparql_cursor_get_string (cursor, 2, NULL);
+
+ tag_data = nautilus_tag_data_new (id, name, url);
+
+ g_queue_push_tail (selection_tags, tag_data);
+
+ tracker_sparql_cursor_next_async (cursor,
+ g_task_get_cancellable (task),
+ on_get_selection_tags_cursor_callback,
+ task);
+}
+
+static void
+on_get_selection_tags_query_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = user_data;
+
+ on_query_callback (object,
+ result,
+ task,
+ on_get_selection_tags_cursor_callback,
+ GET_SELECTION_TAGS,
+ g_task_get_cancellable (task));
+}
+
+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);
+}
+
+void
+nautilus_tag_manager_get_selection_tags (GObject *object,
+ GList *selection,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable)
+{
+ GString *query;
+ GQueue *selection_tags;
+ GTask *task;
+
+ selection_tags = g_queue_new ();
+
+ task = g_task_new (object, cancellable, callback, NULL);
+ g_task_set_task_data (task,
+ selection_tags,
+ nautilus_tag_data_queue_free);
+
+ query = g_string_new ("SELECT ?tag nao:prefLabel(?tag) ?url WHERE"
+ "{ ?urn a nfo:FileDataObject ; nao:hasTag ?tag ; nie:url ?url .");
+
+ query = add_selection_filter (selection, query);
+
+ g_string_append (query, "} ORDER BY (?tag)");
+
+ start_query_or_update (query,
+ on_get_selection_tags_query_callback,
+ task,
+ TRUE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+static void
+on_get_files_with_tag_cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlCursor *cursor;
+ cursor = TRACKER_SPARQL_CURSOR (object);
+ gchar *url;
+ gboolean success;
+ GTask *task;
+ GQueue *files_with_tag;
+
+ task = user_data;
+ files_with_tag = g_task_get_task_data (task);
+
+ success = get_query_status (cursor, result, GET_FILES_WITH_TAG, task);
+ if (!success)
+ {
+ return;
+ }
+
+ url = g_strdup (tracker_sparql_cursor_get_string (cursor, 0, NULL));
+ g_queue_push_tail (files_with_tag, url);
+
+ tracker_sparql_cursor_next_async (cursor,
+ g_task_get_cancellable (task),
+ on_get_files_with_tag_cursor_callback,
+ task);
+}
+
+static void
+on_get_files_with_tag_query_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = user_data;
+
+ on_query_callback (object,
+ result,
+ user_data,
+ on_get_files_with_tag_cursor_callback,
+ GET_FILES_WITH_TAG,
+ g_task_get_cancellable (task));
+}
+
+void
+nautilus_tag_manager_get_files_with_tag (NautilusTagManager *self,
+ GObject *object,
+ const gchar *tag_name,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable)
+{
+ GString *query;
+ GQueue *files_with_tag;
+ GError *error;
+ GTask *task;
+
+ files_with_tag = g_queue_new ();
+
+ task = g_task_new (object, cancellable, callback, NULL);
+ g_task_set_task_data (task,
+ files_with_tag,
+ nautilus_tag_data_queue_free);
+
+ if (!nautilus_tag_queue_has_tag (self->all_tags, tag_name))
+ {
+ error = g_error_new (G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "this tag doesn't exist");
+
+ g_task_return_error (task, error);
+
+ return;
+ }
+
+ query = g_string_new ("SELECT ?url WHERE { ?urn a nfo:FileDataObject ;"
+ " nie:url ?url ; nao:hasTag ?tag . ");
+
+ g_string_append_printf (query, "?tag nao:prefLabel '%s' }", tag_name);
+
+ start_query_or_update (query,
+ on_get_files_with_tag_query_callback,
+ task,
+ TRUE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+void
+nautilus_tag_manager_remove_tag (NautilusTagManager *self,
+ GObject *object,
+ const gchar *tag_name,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable)
+{
+ GString *query;
+ GError *error;
+ GList *l;
+ TagData *tag_data;
+ GTask *task;
+
+ task = g_task_new (object, cancellable, callback, NULL);
+
+ if (!nautilus_tag_queue_has_tag (self->all_tags, tag_name))
+ {
+ error = g_error_new (G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "this tag doesn't exist");
+
+ g_task_return_error (task, error);
+
+ return;
+ }
+
+ query = g_string_new ("");
+ g_string_append_printf (query,
+ "DELETE { ?urn nao:hasTag ?label } "
+ "WHERE { ?urn nie:url ?f . "
+ "?label nao:prefLabel '%s' }\n",
+ tag_name);
+
+ for (l = g_queue_peek_head_link (self->all_tags); l != NULL; l = l->next)
+ {
+ tag_data = l->data;
+
+ if (g_strcmp0 (tag_name, tag_data->name) == 0)
+ {
+ g_string_append_printf (query,
+ "DELETE { <%s> a rdfs:Resource }",
+ tag_data->id);
+
+ break;
+ }
+ }
+
+ start_query_or_update (query,
+ on_update_callback,
+ task,
+ FALSE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+static GString*
+nautilus_tag_manager_delete_tag (NautilusTagManager *self,
+ GList *selection,
+ GString *query,
+ TagData *tag_data,
+ gboolean favorite_tag)
+{
+
+ if (!favorite_tag)
+ {
+ if (!nautilus_tag_queue_has_tag (self->all_tags, tag_data->name))
+ {
+ return query;
+ }
+
+ g_string_append (query,
+ "DELETE { ?urn nao:hasTag ?label } "
+ "WHERE { ?urn a nfo:FileDataObject ; nie:url ?url . ");
+
+ g_string_append_printf (query,
+ "?label nao:prefLabel '%s' . ",
+ tag_data->name);
+ }
+ else
+ {
+ 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,
+ TagData *tag_data,
+ gboolean favorite_tag)
+{
+ g_autofree gchar *tag_color = NULL;
+
+ if (!favorite_tag)
+ {
+ tag_color = parse_color_from_tag_id (tag_data->id);
+
+ if (!nautilus_tag_queue_has_tag (self->all_tags, tag_data->name))
+ {
+ g_string_append_printf (query,
+ "INSERT DATA { <org:gnome:nautilus:tag:%s:%s> a nao:Tag ; nao:prefLabel '%s' }\n",
+ tag_data->name,
+ tag_color,
+ tag_data->name);
+ }
+
+ query = g_string_append (query,
+ "INSERT { ?urn nao:hasTag ?label }"
+ "WHERE { ?urn a nfo:FileDataObject ; nie:url ?url . ");
+
+ g_string_append_printf (query, "?label nao:prefLabel '%s'", tag_data->name);
+ }
+ else
+ {
+ 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_update_tags_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_boolean (G_TASK (res), error);
+}
+
+void
+nautilus_tag_manager_update_tags (NautilusTagManager *self,
+ GObject *object,
+ GList *selection,
+ GQueue *selection_tags,
+ GQueue *new_selection_tags,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable)
+{
+ GString *query;
+ GList *l;
+ TagData *tag_data;
+ gchar *current_tag_name;
+ GTask *task;
+
+ task = g_task_new (object, cancellable, callback, NULL);
+
+ query = g_string_new ("");
+
+ for (l = g_queue_peek_head_link (new_selection_tags); l != NULL; l = l->next)
+ {
+ tag_data = l->data;
+
+ if (!nautilus_tag_queue_has_tag (selection_tags, tag_data->name))
+ {
+ query = nautilus_tag_manager_insert_tag (self,
+ selection,
+ query,
+ tag_data,
+ FALSE);
+ }
+ }
+
+ for (l = g_queue_peek_head_link (selection_tags); l != NULL; l = l->next)
+ {
+ tag_data = l->data;
+
+ if (!nautilus_tag_queue_has_tag (new_selection_tags, tag_data->name) &&
+ tag_data->name != NULL)
+ {
+ query = nautilus_tag_manager_delete_tag (self,
+ selection,
+ query,
+ tag_data,
+ FALSE);
+ }
+
+ current_tag_name = tag_data->name;
+ while (TRUE)
+ {
+ if (l->next == NULL)
+ {
+ break;
+ }
+
+ tag_data = l->next->data;
+ if (g_strcmp0 (tag_data->name, current_tag_name) == 0)
+ {
+ l = l->next;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ start_query_or_update (query,
+ on_update_callback,
+ task,
+ FALSE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+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;
+ }
+
+ 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),
+ GINT64_TO_POINTER (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_file_callback (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusTagManager *self;
+ GString *query;
+ InsertTaskData *data;
+ g_autoptr (GError) error = NULL;
+ GTask *task;
+ gchar* uri;
+ gint64 id;
+ GList *l;
+
+ self = NAUTILUS_TAG_MANAGER (object);
+
+ data = nautilus_tag_manager_gpointer_task_finish (object, res, &error);
+ task = user_data;
+ g_clear_object (&task);
+
+ 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,
+ NULL,
+ TRUE);
+
+ for (l = data->selection; l != NULL; l = l->next)
+ {
+ uri = nautilus_file_get_uri (NAUTILUS_FILE (l->data));
+
+ if (g_hash_table_contains (data->ids, uri))
+ {
+ id = GPOINTER_TO_GINT64 (g_hash_table_lookup (data->ids, uri));
+ g_hash_table_insert (self->favorite_files,
+ nautilus_file_get_uri (NAUTILUS_FILE (l->data)),
+ GINT64_TO_POINTER (id));
+ }
+
+ g_free (uri);
+ }
+ g_signal_emit_by_name (self, "favorites-changed", NULL);
+
+ start_query_or_update (query,
+ on_update_callback,
+ task,
+ 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,
+ NULL);
+ data->callback = callback;
+ data->object = object;
+ data->cancellable = cancellable;
+
+ task = g_task_new (self, cancellable, on_star_file_callback, NULL);
+ g_task_set_task_data (task,
+ data,
+ destroy_insert_task_data);
+
+ 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;
+ gchar *uri = NULL;
+ GList *l;
+
+ file = NAUTILUS_FILE (selection->data);
+ uri = nautilus_file_get_uri (file);
+
+ task = g_task_new (object, cancellable, callback, NULL);
+
+ query = g_string_new ("");
+
+ query = nautilus_tag_manager_delete_tag (self,
+ selection,
+ query,
+ NULL,
+ TRUE);
+
+ for (l = selection; l != NULL; l = l->next)
+ {
+ uri = nautilus_file_get_uri (NAUTILUS_FILE (l->data));
+
+ g_hash_table_remove (self->favorite_files, uri);
+
+ g_free (uri);
+ }
+ g_signal_emit_by_name (self, "favorites-changed", NULL);
+
+ start_query_or_update (query,
+ on_update_callback,
+ task,
+ 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;
+
+ 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)
+ {
+ g_hash_table_remove (self->favorite_files, new_location_uri);
+ g_hash_table_insert (self->favorite_files,
+ g_strdup (location_uri),
+ GINT64_TO_POINTER (tracker_notifier_event_get_id (event)));
+ g_signal_emit_by_name (self, "favorites-changed", NULL);
+ }
+
+ 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);
+ g_signal_emit_by_name (self, "favorites-changed", NULL);
+ }
+ else if (query_has_results && location_uri && !g_hash_table_contains (self->favorite_files, location_uri))
+ {
+ g_hash_table_insert (self->favorite_files,
+ g_strdup (location_uri),
+ GINT64_TO_POINTER (tracker_notifier_event_get_id (event)));
+ g_signal_emit_by_name (self, "favorites-changed", NULL);
+ }
+
+ 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);
+
+ if (self->all_tags)
+ {
+ g_queue_free_full (self->all_tags, nautilus_tag_data_free);
+ }
+
+ 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__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+NautilusTagManager* nautilus_tag_manager_new (GCancellable *cancellable_tags,
+ GCancellable *cancellable_notifier,
+ GCancellable *cancellable_favorite)
+{
+ //gchar *classes[] = { "nao:hasTag", NULL };
+
+ 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);
+
+ nautilus_tag_manager_query_all_tags (tag_manager, cancellable_tags);
+
+ nautilus_tag_manager_query_favorite_files (tag_manager, cancellable_favorite);
+
+ tag_manager->notifier = tracker_notifier_new (NULL,
+ TRACKER_NOTIFIER_FLAG_QUERY_LOCATION,
+ cancellable_notifier,
+ &tag_manager->notifier_error);
+
+ g_signal_connect (tag_manager->notifier,
+ "events",
+ G_CALLBACK (on_tracker_notifier_events),
+ tag_manager);
+
+ return 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,
+ NULL);
+}
diff --git a/src/nautilus-tag-manager.h b/src/nautilus-tag-manager.h
new file mode 100644
index 000000000..3aa06abf5
--- /dev/null
+++ b/src/nautilus-tag-manager.h
@@ -0,0 +1,113 @@
+/* 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);
+
+typedef struct
+{
+ gchar *id;
+ gchar *name;
+ gchar *url;
+} TagData;
+
+NautilusTagManager* nautilus_tag_manager_new (GCancellable *cancellable_tags,
+ GCancellable *cancellable_notifier,
+ GCancellable *cancellable_favorite);
+
+GList* nautilus_tag_manager_get_favorite_files (NautilusTagManager *self);
+
+GQueue* nautilus_tag_manager_get_all_tags (NautilusTagManager *self);
+
+gpointer nautilus_tag_manager_gpointer_task_finish (GObject *source_object,
+ GAsyncResult *res,
+ GError **error);
+
+void nautilus_tag_manager_get_selection_tags (GObject *object,
+ GList *selection,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable);
+
+void nautilus_tag_manager_get_files_with_tag (NautilusTagManager *self,
+ GObject *object,
+ const gchar *tag_name,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable);
+
+void nautilus_tag_manager_remove_tag (NautilusTagManager *self,
+ GObject *object,
+ const gchar *tag_name,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable);
+
+gboolean nautilus_tag_manager_update_tags_finish (GObject *source_object,
+ GAsyncResult *res,
+ GError **error);
+
+void nautilus_tag_manager_update_tags (NautilusTagManager *self,
+ GObject *object,
+ GList *selection,
+ GQueue *selection_tags,
+ GQueue *new_selection_tags,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable);
+
+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);
+
+GQueue* nautilus_tag_copy_tag_queue (GQueue *queue);
+
+gboolean nautilus_tag_queue_has_tag (GQueue *selection_tags,
+ const gchar *tag_name);
+
+void nautilus_tag_data_free (gpointer data);
+
+gchar* parse_color_from_tag_id (const gchar *tag_id);
+
+TagData* nautilus_tag_data_new (const gchar *id,
+ const gchar *name,
+ const gchar *url);
+
+
+
+G_END_DECLS
+
+#endif \ No newline at end of file
diff --git a/src/nautilus-tag-widget.c b/src/nautilus-tag-widget.c
new file mode 100644
index 000000000..73893ce96
--- /dev/null
+++ b/src/nautilus-tag-widget.c
@@ -0,0 +1,304 @@
+/* nautilus-tag-widget.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-widget.h"
+#include "nautilus-tag-manager.h"
+
+struct _NautilusTagWidget
+{
+ GtkEventBox parent;
+
+ GtkWidget *box;
+ GtkWidget *label;
+ GtkWidget *circle;
+
+ gboolean cursor_over;
+
+ gchar *color;
+ TagData *tag_data;
+};
+
+G_DEFINE_TYPE (NautilusTagWidget, nautilus_tag_widget, GTK_TYPE_EVENT_BOX);
+
+GtkWidget*
+nautilus_tag_widget_queue_get_tag_with_name (GQueue *queue,
+ const gchar *tag_name)
+{
+ GList *l;
+ NautilusTagWidget *tag_widget;
+
+ for (l = g_queue_peek_head_link (queue); l != NULL; l = l->next)
+ {
+ tag_widget = NAUTILUS_TAG_WIDGET (l->data);
+
+ if (g_strcmp0 (tag_name, tag_widget->tag_data->name) == 0)
+ {
+ return GTK_WIDGET (tag_widget);
+ }
+ }
+
+ return NULL;
+}
+
+gboolean
+nautilus_tag_widget_queue_contains_tag (GQueue *queue,
+ const gchar *tag_name)
+{
+ GList *l;
+ NautilusTagWidget *tag_widget;
+
+ for (l = g_queue_peek_head_link (queue); l != NULL; l = l->next)
+ {
+ tag_widget = NAUTILUS_TAG_WIDGET (l->data);
+
+ if (g_strcmp0 (tag_name, tag_widget->tag_data->name) == 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+const gchar*
+nautilus_tag_widget_get_tag_name (NautilusTagWidget *self)
+{
+ return self->tag_data->name;
+}
+
+const gchar*
+nautilus_tag_widget_get_tag_id (NautilusTagWidget *self)
+{
+ return self->tag_data->id;
+}
+
+static gboolean
+paint_circle (GtkWidget *widget,
+ cairo_t *cr,
+ gpointer data)
+{
+ guint width, height;
+ GdkRGBA color;
+ GtkStyleContext *context;
+ NautilusTagWidget *tag_widget;
+ /*GtkIconInfo *info;
+ GdkPixbuf *pixbuf;
+ gint icon_size;
+ gint scale_factor;*/
+
+ tag_widget = NAUTILUS_TAG_WIDGET (data);
+
+ context = gtk_widget_get_style_context (widget);
+
+ width = gtk_widget_get_allocated_width (widget);
+ height = gtk_widget_get_allocated_height (widget);
+
+ gtk_render_background (context, cr, 0, 0, width, height);
+
+ cairo_arc (cr,
+ width / 2.0, height / 2.0,
+ MIN (width, height) / 2.0,
+ 0, 2 * G_PI);
+
+ gtk_style_context_get_color (context,
+ gtk_style_context_get_state (context),
+ &color);
+
+
+ gdk_rgba_parse (&color, tag_widget->color);
+
+ gdk_cairo_set_source_rgba (cr, &color);
+
+ cairo_fill (cr);
+
+ if (tag_widget->cursor_over)
+ {
+
+ /*gtk_icon_size_lookup (GTK_ICON_SIZE_MENU,
+ &icon_size, NULL);
+ scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (tag_widget->label));
+
+ info = gtk_icon_theme_lookup_icon_for_scale (gtk_icon_theme_get_default (),
+ "window-close-symbolic",
+ icon_size, scale_factor,
+ GTK_ICON_LOOKUP_GENERIC_FALLBACK);
+
+ pixbuf = gtk_icon_info_load_symbolic_for_context (info, context, NULL, NULL);
+ g_print("%d %d\n", width, height);
+ width = 0;
+ height = 0;
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, width / 2, height / 2);
+
+ cairo_paint (cr);
+
+ g_object_unref (info);
+ g_object_unref (pixbuf);*/
+
+ gtk_style_context_get_color (context,
+ gtk_style_context_get_state (context),
+ &color);
+
+ gdk_cairo_set_source_rgba (cr, &color);
+
+ cairo_move_to (cr,
+ (MIN (width, height) - MIN (width, height) * 0.25) / 2.0 * G_SQRT2 / 2 * -1 + width / 2.0,
+ (MIN (width, height) - MIN (width, height) * 0.25) / 2.0 * G_SQRT2 / 2 + height / 2.0);
+ cairo_line_to (cr,
+ (MIN (width, height) - MIN (width, height) * 0.25) / 2.0 * G_SQRT2 / 2 + width / 2.0,
+ (MIN (width, height) - MIN (width, height) * 0.25) / 2.0 * G_SQRT2 / 2 * -1 + height / 2.0);
+
+ cairo_stroke (cr);
+
+ cairo_move_to (cr,
+ (MIN (width, height) - MIN (width, height) * 0.25) / 2.0 * G_SQRT2 / 2 + width / 2.0,
+ (MIN (width, height) - MIN (width, height) * 0.25) / 2.0 * G_SQRT2 / 2 + height / 2.0);
+ cairo_line_to (cr,
+ (MIN (width, height) - MIN (width, height) * 0.25) / 2.0 * G_SQRT2 / 2 * -1 + width / 2.0,
+ (MIN (width, height) - MIN (width, height) * 0.25) / 2.0 * G_SQRT2 / 2 * -1 + height / 2.0);
+
+ cairo_stroke (cr);
+ }
+
+ return FALSE;
+}
+
+gboolean
+on_leave_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ NautilusTagWidget *self;
+
+ self = NAUTILUS_TAG_WIDGET (widget);
+
+ if (self->cursor_over == TRUE)
+ {
+ gtk_widget_queue_draw (self->circle);
+
+ gtk_label_set_label (GTK_LABEL (self->label), self->tag_data->name);
+ }
+
+ self->cursor_over = FALSE;
+
+ return FALSE;
+}
+
+gboolean
+on_motion_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ NautilusTagWidget *self;
+ g_autofree gchar *markup = NULL;
+
+ self = NAUTILUS_TAG_WIDGET (widget);
+
+ if (self->cursor_over == FALSE)
+ {
+ gtk_widget_queue_draw (self->circle);
+
+ markup = g_markup_printf_escaped ("<u>%s</u>", self->tag_data->name);
+ gtk_label_set_label (GTK_LABEL (self->label), markup);
+ }
+
+ self->cursor_over = TRUE;
+
+ return FALSE;
+}
+
+
+static void
+nautilus_tag_widget_finalize (GObject *object)
+{
+ NautilusTagWidget *self;
+
+ self = NAUTILUS_TAG_WIDGET (object);
+
+ g_free (self->color);
+ nautilus_tag_data_free (self->tag_data);
+
+ G_OBJECT_CLASS (nautilus_tag_widget_parent_class)->finalize (object);
+}
+
+static void
+nautilus_tag_widget_class_init (NautilusTagWidgetClass *klass)
+{
+ //GtkWidgetClass *widget_class;
+ GObjectClass *oclass;
+
+ //widget_class = GTK_WIDGET_CLASS (klass);
+ oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = nautilus_tag_widget_finalize;
+}
+
+GtkWidget* nautilus_tag_widget_new (const gchar *tag_label,
+ const gchar *tag_id,
+ gboolean can_close)
+{
+ NautilusTagWidget *self;
+ self = g_object_new (NAUTILUS_TYPE_TAG_WIDGET,
+ NULL);
+
+ self->color = parse_color_from_tag_id (tag_id);
+
+ self->tag_data = nautilus_tag_data_new (tag_id, tag_label, NULL);
+
+ self->label = gtk_label_new (tag_label);
+ gtk_label_set_use_markup (GTK_LABEL (self->label), TRUE);
+
+ self->box = g_object_new (GTK_TYPE_BOX,
+ "orientation",
+ GTK_ORIENTATION_HORIZONTAL,
+ "spacing",
+ 5,
+ NULL);
+
+ gtk_widget_add_events (GTK_WIDGET (self), GDK_POINTER_MOTION_MASK);
+
+ if (can_close)
+ {
+ g_signal_connect (self,
+ "motion-notify-event",
+ G_CALLBACK (on_motion_event),
+ NULL);
+
+ g_signal_connect (self,
+ "leave-notify-event",
+ G_CALLBACK (on_leave_event),
+ NULL);
+ }
+
+ self->circle = gtk_drawing_area_new ();
+ gtk_widget_set_size_request (self->circle, 15, 15);
+ g_signal_connect (self->circle, "draw",
+ G_CALLBACK (paint_circle), self);
+
+ gtk_box_pack_start (GTK_BOX (self->box), self->circle, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (self->box), self->label, FALSE, FALSE, 0);
+
+ gtk_container_add (GTK_CONTAINER (self), self->box);
+
+ return GTK_WIDGET (self);
+}
+
+static void
+nautilus_tag_widget_init (NautilusTagWidget *self)
+{
+
+}
diff --git a/src/nautilus-tag-widget.h b/src/nautilus-tag-widget.h
new file mode 100644
index 000000000..01ea88185
--- /dev/null
+++ b/src/nautilus-tag-widget.h
@@ -0,0 +1,51 @@
+/* nautilus-tag-widget.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_WIDGET_H
+#define NAUTILUS_TAG_WIDGET_H
+
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "nautilus-files-view.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_TAG_WIDGET (nautilus_tag_widget_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusTagWidget, nautilus_tag_widget, NAUTILUS, TAG_WIDGET, GtkEventBox);
+
+GtkWidget* nautilus_tag_widget_new (const gchar *tag_label,
+ const gchar *tag_id,
+ gboolean can_close);
+
+const gchar* nautilus_tag_widget_get_tag_name (NautilusTagWidget *self);
+
+const gchar* nautilus_tag_widget_get_tag_id (NautilusTagWidget *self);
+
+GtkWidget* nautilus_tag_widget_queue_get_tag_with_name (GQueue *queue,
+ const gchar *tag_name);
+
+gboolean nautilus_tag_widget_queue_contains_tag (GQueue *queue,
+ const gchar *tag_name);
+
+
+G_END_DECLS
+
+#endif
diff --git a/src/nautilus-tags-dialog.c b/src/nautilus-tags-dialog.c
new file mode 100644
index 000000000..db194aa71
--- /dev/null
+++ b/src/nautilus-tags-dialog.c
@@ -0,0 +1,620 @@
+/* nautilus-tags-dialog.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-tags-dialog.h"
+#include "nautilus-tag-manager.h"
+#include "nautilus-tag-widget.h"
+
+typedef enum
+{
+ NO_ACTION,
+ UPDATE_TAGS
+} ChosenAction;
+
+struct _NautilusTagsDialog
+{
+ GtkDialog parent;
+
+ NautilusWindow *window;
+ GList *selection;
+
+ GtkWidget *cancel_button;
+ GtkWidget *update_tags_button;
+ GtkWidget *tags_entry;
+ GtkWidget *color_button;
+ GtkWidget *tags_listbox;
+ GtkWidget *selection_tags_box;
+ GtkWidget *add_tag_button;
+
+ NautilusTagManager *tag_manager;
+ GQueue *all_tags;
+ GQueue *selection_tags;
+ GQueue *new_selection_tags;
+
+ /* queues that represent all the tags */
+ GQueue *list_box_rows;
+ GQueue *all_tags_widgets;
+ /* initilay it has tags widgets that the selection
+ * has, but updates when a tag is removed/added */
+ GQueue *selection_tags_widgets;
+
+ ChosenAction action;
+
+ GCancellable *cancellable_selection;
+ GCancellable *cancellable_update;
+ GCancellable *cancellable_get_files;
+};
+
+G_DEFINE_TYPE (NautilusTagsDialog, nautilus_tags_dialog, GTK_TYPE_DIALOG);
+
+static void on_selection_tags_obtained (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data);
+
+static void on_tags_updated (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data);
+
+static void
+update_tags (NautilusTagsDialog *dialog)
+{
+ GQueue *all_tags_check;
+ GList *l;
+ GList *l_check;
+ TagData *tag_data;
+ TagData *tag_data_check;
+
+ all_tags_check = nautilus_tag_manager_get_all_tags (dialog->tag_manager);
+
+ for (l = g_queue_peek_head_link (dialog->all_tags), l_check = g_queue_peek_head_link (all_tags_check);
+ l != NULL && l_check != NULL; l = l->next, l_check = l_check->next)
+ {
+ tag_data = l->data;
+ tag_data_check = l_check->data;
+
+ if (g_strcmp0 (tag_data->id, tag_data_check->id) != 0 ||
+ g_strcmp0 (tag_data->name, tag_data_check->name) != 0)
+ {
+ return;
+ }
+ }
+
+ nautilus_tag_manager_get_selection_tags (G_OBJECT (dialog),
+ dialog->selection,
+ on_selection_tags_obtained,
+ dialog->cancellable_selection);
+}
+
+static gint
+get_tag_position_in_listbox (NautilusTagsDialog *dialog,
+ const gchar *tag_name)
+{
+ GList *l;
+ gint index = 0;
+ g_autofree gchar *casefold_tag_name = NULL;
+ gchar *casefold_widget_tag_name;
+
+ casefold_tag_name = g_utf8_casefold (tag_name, -1);
+
+ for (l = g_queue_peek_head_link (dialog->all_tags_widgets), index = 0;
+ l != NULL;
+ l = l->next, index++)
+ {
+ casefold_widget_tag_name = g_utf8_casefold (nautilus_tag_widget_get_tag_name (NAUTILUS_TAG_WIDGET (l->data)), -1);
+
+ if (g_strcmp0 (casefold_tag_name, casefold_widget_tag_name) < 0)
+ {
+ g_free (casefold_widget_tag_name);
+
+ return index;
+ }
+
+ g_free (casefold_widget_tag_name);
+ }
+
+ return index;
+}
+
+static gboolean
+on_tag_widget_button_press_event (GtkWidget *old_tag_widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ NautilusTagsDialog *dialog;
+ gboolean got_button;
+ guint button;
+ GtkWidget *row;
+ GtkWidget *tag_widget;
+ gint index;
+
+ dialog = NAUTILUS_TAGS_DIALOG (user_data);
+
+ got_button = gdk_event_get_button (event, &button);
+
+ if (got_button && button == GDK_BUTTON_PRIMARY)
+ {
+ if (nautilus_tag_queue_has_tag (dialog->all_tags,
+ nautilus_tag_widget_get_tag_name (NAUTILUS_TAG_WIDGET (old_tag_widget))))
+ {
+ row = gtk_list_box_row_new ();
+
+ tag_widget = nautilus_tag_widget_new (nautilus_tag_widget_get_tag_name (NAUTILUS_TAG_WIDGET (old_tag_widget)),
+ nautilus_tag_widget_get_tag_id (NAUTILUS_TAG_WIDGET (old_tag_widget)),
+ FALSE);
+
+ g_queue_push_tail (dialog->list_box_rows, row);
+
+ gtk_container_add (GTK_CONTAINER (row), tag_widget);
+ gtk_widget_show_all (row);
+
+ index = get_tag_position_in_listbox (dialog,
+ nautilus_tag_widget_get_tag_name (NAUTILUS_TAG_WIDGET (old_tag_widget)));
+
+ gtk_list_box_insert (GTK_LIST_BOX (dialog->tags_listbox),
+ row,
+ index);
+
+ g_queue_push_nth (dialog->all_tags_widgets, tag_widget, index);
+ }
+
+ g_queue_remove (dialog->selection_tags_widgets, old_tag_widget);
+
+ gtk_widget_destroy (old_tag_widget);
+ }
+
+ return FALSE;
+}
+
+static void
+on_row_activated (GtkListBox *box,
+ GtkListBoxRow *row,
+ gpointer user_data)
+{
+ NautilusTagsDialog *dialog;
+
+ dialog = NAUTILUS_TAGS_DIALOG (user_data);
+
+ GtkWidget *old_tag_widget;
+ GtkWidget *tag_widget;
+
+ old_tag_widget = gtk_bin_get_child (GTK_BIN (row));
+
+ tag_widget = nautilus_tag_widget_new (nautilus_tag_widget_get_tag_name (NAUTILUS_TAG_WIDGET (old_tag_widget)),
+ nautilus_tag_widget_get_tag_id (NAUTILUS_TAG_WIDGET (old_tag_widget)),
+ TRUE);
+
+ g_signal_connect (tag_widget,
+ "button-press-event",
+ G_CALLBACK (on_tag_widget_button_press_event),
+ dialog);
+
+ gtk_container_add (GTK_CONTAINER (dialog->selection_tags_box), tag_widget);
+ gtk_widget_show_all (dialog->selection_tags_box);
+
+ g_queue_push_tail (dialog->selection_tags_widgets, tag_widget);
+
+ g_queue_remove (dialog->all_tags_widgets, old_tag_widget);
+ g_queue_remove (dialog->list_box_rows, row);
+ gtk_widget_destroy (GTK_WIDGET (row));
+}
+
+static void
+fill_selection_box (NautilusTagsDialog *dialog)
+{
+ GList *l;
+ GtkWidget *tag_widget;
+ TagData *tag_data;
+ gint index;
+ GtkListBoxRow *row;
+
+ dialog->selection_tags_widgets = g_queue_new ();
+
+ for (l = g_queue_peek_head_link (dialog->selection_tags); l != NULL; l = l->next)
+ {
+ tag_data = l->data;
+
+ if (nautilus_tag_widget_queue_contains_tag (dialog->selection_tags_widgets,
+ tag_data->name) || tag_data->name == NULL)
+ {
+ continue;
+ }
+
+ if (nautilus_tag_widget_queue_contains_tag (dialog->all_tags_widgets,
+ tag_data->name))
+ {
+ tag_widget = nautilus_tag_widget_queue_get_tag_with_name (dialog->all_tags_widgets,
+ tag_data->name);
+
+ index = g_queue_index (dialog->all_tags_widgets, tag_widget);
+ g_queue_remove (dialog->all_tags_widgets, tag_widget);
+
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX(dialog->tags_listbox), index);
+ g_queue_remove (dialog->list_box_rows, row);
+ gtk_widget_destroy (GTK_WIDGET (row));
+ }
+
+ tag_widget = nautilus_tag_widget_new (tag_data->name, tag_data->id, TRUE);
+
+ g_signal_connect (tag_widget,
+ "button-press-event",
+ G_CALLBACK (on_tag_widget_button_press_event),
+ dialog);
+
+ gtk_container_add (GTK_CONTAINER (dialog->selection_tags_box), tag_widget);
+
+ g_queue_push_tail (dialog->selection_tags_widgets, tag_widget);
+ }
+
+ gtk_widget_show_all (dialog->selection_tags_box);
+
+
+ if (!nautilus_tag_widget_queue_contains_tag (dialog->selection_tags_widgets,
+ gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry))) &&
+ g_strcmp0 (gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)), "") != 0)
+ {
+ gtk_widget_set_sensitive (dialog->add_tag_button, TRUE);
+ }
+}
+
+static void
+fill_list_box (NautilusTagsDialog *dialog)
+{
+ GList *l;
+ TagData *tag_data;
+ GtkWidget *row;
+ GtkWidget *tag_widget;
+
+ dialog->all_tags_widgets = g_queue_new ();
+
+ for (l = g_queue_peek_head_link (dialog->all_tags); l != NULL; l = l->next)
+ {
+ tag_data = l->data;
+
+ row = gtk_list_box_row_new ();
+
+ tag_widget = nautilus_tag_widget_new (tag_data->name, tag_data->id, FALSE);
+
+ g_queue_push_tail (dialog->list_box_rows, row);
+
+ gtk_container_add (GTK_CONTAINER (row), tag_widget);
+ gtk_widget_show_all (row);
+
+ gtk_container_add (GTK_CONTAINER (dialog->tags_listbox), row);
+
+ g_queue_push_tail (dialog->all_tags_widgets, tag_widget);
+ }
+}
+
+static void
+nautilus_tags_dialog_on_response (NautilusTagsDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ dialog->action = UPDATE_TAGS;
+
+ update_tags (dialog);
+ }
+ else
+ {
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ }
+}
+
+static void
+on_selection_tags_obtained (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusTagsDialog *dialog;
+ g_autoptr (GError) error = NULL;
+ TagData *tag_data;
+ GQueue *selection_tags_check;
+ GList *l_check;
+ GList *l;
+ TagData *tag_data_check;
+ NautilusTagWidget *tag_widget;
+ GTask *task;
+
+ dialog = NAUTILUS_TAGS_DIALOG (object);
+
+ if (dialog->action == NO_ACTION)
+ {
+ dialog->selection_tags = nautilus_tag_manager_gpointer_task_finish (object, res, &error);
+
+ fill_selection_box (dialog);
+ }
+ else
+ {
+ selection_tags_check = nautilus_tag_manager_gpointer_task_finish (object, res, &error);
+
+ for (l = g_queue_peek_head_link (dialog->selection_tags), l_check = g_queue_peek_head_link (selection_tags_check);
+ l != NULL && l_check != NULL; l = l->next, l_check = l_check->next)
+ {
+ tag_data = l->data;
+ tag_data_check = l_check->data;
+
+ if (g_strcmp0 (tag_data->id, tag_data_check->id) != 0 ||
+ g_strcmp0 (tag_data->name, tag_data_check->name) != 0)
+ {
+ return;
+ }
+ }
+
+ dialog->new_selection_tags = g_queue_new ();
+
+ for (l = g_queue_peek_head_link (dialog->selection_tags_widgets); l != NULL; l = l->next)
+ {
+ tag_widget = NAUTILUS_TAG_WIDGET (l->data);
+
+ tag_data = nautilus_tag_data_new (nautilus_tag_widget_get_tag_id (tag_widget),
+ nautilus_tag_widget_get_tag_name (tag_widget),
+ NULL);
+
+ g_queue_push_tail (dialog->new_selection_tags, tag_data);
+ }
+
+ nautilus_tag_manager_update_tags (dialog->tag_manager,
+ G_OBJECT (dialog),
+ dialog->selection,
+ dialog->selection_tags,
+ dialog->new_selection_tags,
+ on_tags_updated,
+ dialog->cancellable_update);
+ }
+
+ task = user_data;
+ g_clear_object (&task);
+}
+
+static void
+on_tags_updated (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusTagsDialog *dialog;
+ g_autoptr (GError) error = NULL;
+ gboolean result;
+ GTask *task;
+
+ dialog = NAUTILUS_TAGS_DIALOG (object);
+
+ result = nautilus_tag_manager_update_tags_finish (object, res, &error);
+
+ task = user_data;
+ g_clear_object (&task);
+
+ if (!result)
+ {
+ g_warning ("something went wrong while updating tags");
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+}
+
+static void
+on_tags_entry_activate (NautilusTagsDialog *dialog)
+{
+ if (gtk_widget_get_sensitive (dialog->add_tag_button))
+ {
+ g_signal_emit_by_name (dialog->add_tag_button, "clicked", dialog);
+ }
+}
+
+static void
+on_tags_entry_changed (NautilusTagsDialog *dialog)
+{
+ /* check if selection has this tag and update add button acordingly */
+
+ if (nautilus_tag_widget_queue_contains_tag (dialog->selection_tags_widgets,
+ gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry))) ||
+ g_strcmp0 (gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)), "") == 0 ||
+ dialog->selection_tags_widgets == NULL)
+ {
+ gtk_widget_set_sensitive (dialog->add_tag_button, FALSE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (dialog->add_tag_button, TRUE);
+ }
+}
+
+static void
+on_add_button_clicked (GtkButton *button,
+ gpointer user_data)
+{
+ NautilusTagsDialog *dialog;
+ g_autofree gchar *tag_id = NULL;
+ g_autofree gchar *color_rgb = NULL;
+ const gchar *const_tag_id;
+ GdkRGBA color;
+ GtkWidget *tag_widget;
+ GtkWidget *old_tag_widget;
+ GtkListBoxRow *row;
+ gint index;
+
+ dialog = NAUTILUS_TAGS_DIALOG (user_data);
+
+ /* if tag exists but it's not in selection: move from listbox to box */
+ if (nautilus_tag_queue_has_tag (dialog->all_tags,
+ gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry))))
+ {
+ old_tag_widget = nautilus_tag_widget_queue_get_tag_with_name (dialog->all_tags_widgets,
+ gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)));
+
+ const_tag_id = nautilus_tag_widget_get_tag_id (NAUTILUS_TAG_WIDGET (old_tag_widget));
+
+ tag_widget = nautilus_tag_widget_new (gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)),
+ const_tag_id,
+ TRUE);
+
+ index = g_queue_index (dialog->all_tags_widgets, old_tag_widget);
+ g_queue_remove (dialog->all_tags_widgets, old_tag_widget);
+
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX(dialog->tags_listbox), index);
+ g_queue_remove (dialog->list_box_rows, row);
+ gtk_widget_destroy (GTK_WIDGET (row));
+ }
+ else
+ {
+ /* if tag doesn't exist and it's not in selection: create new widget for box */
+ gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (dialog->color_button), &color);
+ color_rgb = gdk_rgba_to_string (&color);
+ tag_id = g_strdup_printf ("org:gnome:nautilus:tag:%s:%s",
+ gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)),
+ color_rgb);
+
+ tag_widget = nautilus_tag_widget_new (gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)), tag_id, TRUE);
+ }
+
+ g_signal_connect (tag_widget,
+ "button-press-event",
+ G_CALLBACK (on_tag_widget_button_press_event),
+ dialog);
+
+ gtk_container_add (GTK_CONTAINER (dialog->selection_tags_box), tag_widget);
+
+ g_queue_push_tail (dialog->selection_tags_widgets, tag_widget);
+
+ gtk_widget_show_all (dialog->selection_tags_box);
+
+ gtk_entry_set_text (GTK_ENTRY (dialog->tags_entry), "");
+}
+
+static void
+nautilus_tags_dialog_finalize (GObject *object)
+{
+ NautilusTagsDialog *dialog;
+
+ dialog = NAUTILUS_TAGS_DIALOG (object);
+
+ g_cancellable_cancel (dialog->cancellable_selection);
+ g_clear_object (&dialog->cancellable_selection);
+
+ g_cancellable_cancel (dialog->cancellable_update);
+ g_clear_object (&dialog->cancellable_update);
+
+ g_cancellable_cancel (dialog->cancellable_get_files);
+ g_clear_object (&dialog->cancellable_get_files);
+
+ if (dialog->all_tags)
+ {
+ g_queue_free_full (dialog->all_tags, nautilus_tag_data_free);
+ }
+ if (dialog->selection_tags)
+ {
+ g_queue_free_full (dialog->selection_tags, nautilus_tag_data_free);
+ }
+ if (dialog->new_selection_tags)
+ {
+ g_queue_free_full (dialog->new_selection_tags, nautilus_tag_data_free);
+ }
+ if (dialog->selection_tags_widgets)
+ {
+ g_queue_free (dialog->selection_tags_widgets);
+ }
+ if (dialog->all_tags_widgets)
+ {
+ g_queue_free (dialog->all_tags_widgets);
+ }
+ if (dialog->list_box_rows)
+ {
+ g_queue_free (dialog->list_box_rows);
+ }
+
+ g_clear_object (&dialog->tag_manager);
+
+ G_OBJECT_CLASS (nautilus_tags_dialog_parent_class)->finalize (object);
+}
+
+static void
+nautilus_tags_dialog_class_init (NautilusTagsDialogClass *klass)
+{
+ GtkWidgetClass *widget_class;
+ GObjectClass *oclass;
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+ oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = nautilus_tags_dialog_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-tags-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, cancel_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, update_tags_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, tags_entry);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, color_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, tags_listbox);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, selection_tags_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, add_tag_button);
+
+ gtk_widget_class_bind_template_callback (widget_class, nautilus_tags_dialog_on_response);
+ gtk_widget_class_bind_template_callback (widget_class, on_tags_entry_activate);
+ gtk_widget_class_bind_template_callback (widget_class, on_tags_entry_changed);
+ gtk_widget_class_bind_template_callback (widget_class, on_add_button_clicked);
+}
+
+GtkWidget* nautilus_tags_dialog_new (GList *selection,
+ NautilusWindow *window)
+{
+ NautilusTagsDialog *dialog;
+
+ dialog = g_object_new (NAUTILUS_TYPE_TAGS_DIALOG, "use-header-bar", TRUE, NULL);
+
+ dialog->window = window;
+ dialog->selection = selection;
+
+ gtk_window_set_transient_for (GTK_WINDOW (dialog),
+ GTK_WINDOW (window));
+
+ g_signal_connect (dialog->tags_listbox, "row-activated", G_CALLBACK (on_row_activated), dialog);
+
+ dialog->cancellable_selection = g_cancellable_new ();
+ dialog->cancellable_update = g_cancellable_new ();
+
+ dialog->tag_manager = nautilus_tag_manager_new (NULL, NULL, NULL);
+
+ dialog->all_tags = nautilus_tag_manager_get_all_tags (dialog->tag_manager);
+
+ fill_list_box (dialog);
+
+ dialog->action = NO_ACTION;
+
+ nautilus_tag_manager_get_selection_tags (G_OBJECT (dialog),
+ selection,
+ on_selection_tags_obtained,
+ dialog->cancellable_selection);
+
+ return GTK_WIDGET (dialog);
+}
+
+static void
+nautilus_tags_dialog_init (NautilusTagsDialog *self)
+{
+ GdkRGBA color;
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->list_box_rows = g_queue_new ();
+
+ gdk_rgba_parse (&color, "rgb(220,220,220)");
+ gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (self->color_button),
+ &color);
+} \ No newline at end of file
diff --git a/src/nautilus-tags-dialog.h b/src/nautilus-tags-dialog.h
new file mode 100644
index 000000000..1968bc682
--- /dev/null
+++ b/src/nautilus-tags-dialog.h
@@ -0,0 +1,39 @@
+/* nautilus-tags-dialog.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_TAGS_DIALOG_H
+#define NAUTILUS_TAGS_DIALOG_H
+
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "nautilus-files-view.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_TAGS_DIALOG (nautilus_tags_dialog_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusTagsDialog, nautilus_tags_dialog, NAUTILUS, TAGS_DIALOG, GtkDialog);
+
+GtkWidget* nautilus_tags_dialog_new (GList *selection,
+ NautilusWindow *window);
+
+G_END_DECLS
+
+#endif \ No newline at end of file
diff --git a/src/nautilus-window.c b/src/nautilus-window.c
index b2c6a8216..0e6367abe 100644
--- a/src/nautilus-window.c
+++ b/src/nautilus-window.c
@@ -1156,6 +1156,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)
{
@@ -2967,6 +2980,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 720bfa5d5..7f3a5e9f1 100644
--- a/src/resources/nautilus.gresource.xml
+++ b/src/resources/nautilus.gresource.xml
@@ -20,6 +20,7 @@
<file>ui/nautilus-trash-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-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>