[nautilus/wip/alexpandelea/tags] implement tags



commit 48b32979bfad3c5a55edf08e04b496ab9b6c2c90
Author: Alexandru Pandelea <alexandru pandelea gmail com>
Date:   Wed Jun 28 17:47:41 2017 +0100

    implement tags

 src/meson.build                                    |    8 +-
 src/nautilus-files-view.c                          |   22 +
 src/nautilus-tag-manager.c                         |  839 ++++++++++++++++++++
 src/nautilus-tag-manager.h                         |   77 ++
 src/nautilus-tag-widget.c                          |  304 +++++++
 src/nautilus-tag-widget.h                          |   51 ++
 src/nautilus-tags-dialog.c                         |  698 ++++++++++++++++
 src/nautilus-tags-dialog.h                         |   39 +
 src/resources/nautilus.gresource.xml               |    1 +
 .../ui/nautilus-files-view-context-menus.ui        |    7 +
 src/resources/ui/nautilus-tags-dialog.ui           |  157 ++++
 11 files changed, 2202 insertions(+), 1 deletions(-)
---
diff --git a/src/meson.build b/src/meson.build
index 83723b9..9abe3bd 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -264,7 +264,13 @@ 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']
 endif
 
 nautilus_deps = [glib,
diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c
index 1874d8b..571628b 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"
@@ -1572,6 +1573,26 @@ 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 (NAUTILUS_FILES_VIEW (user_data));
+
+    selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+    dialog = nautilus_tags_dialog_new (selection, window);
+    gtk_widget_show (GTK_WIDGET (dialog));
+}
+
+static void
 action_restore_from_trash (GSimpleAction *action,
                            GVariant      *state,
                            gpointer       user_data)
@@ -7001,6 +7022,7 @@ 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},
     /* 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
diff --git a/src/nautilus-tag-manager.c b/src/nautilus-tag-manager.c
new file mode 100644
index 0000000..fca7583
--- /dev/null
+++ b/src/nautilus-tag-manager.c
@@ -0,0 +1,839 @@
+/* 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>
+
+struct _NautilusTagManager
+{
+    GObject object;
+
+    NautilusTagManager *manager;
+
+    GList *selection;
+
+    GQueue *all_tags;
+    GQueue *selection_tags;
+    GQueue *files_with_tag;
+
+    GCancellable *cancellable_all;
+    GCancellable *cancellable_selection;
+    GCancellable *cancellable_update;
+    GCancellable *cancellable_get_files;
+};
+
+G_DEFINE_TYPE (NautilusTagManager, nautilus_tag_manager, G_TYPE_OBJECT);
+
+enum
+{
+    ALL_TAGS,
+    SELECTION_TAGS,
+    TAGS_UPDATED,
+    FILES_WITH_TAG,
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+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
+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);
+}
+
+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 (NautilusTagManager *self,
+                      GString            *query)
+{
+    NautilusFile *file;
+    GList *l;
+    gchar *uri;
+
+    g_string_append (query, " FILTER(?url IN (");
+
+    for (l = self->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 (NautilusTagManager         *self,
+                       GString             *query,
+                       GAsyncReadyCallback  callback,
+                       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,
+                                               self);
+    }
+    else
+    {
+        tracker_sparql_connection_update_async (connection,
+                                                query->str,
+                                                G_PRIORITY_DEFAULT,
+                                                cancellable,
+                                                callback,
+                                                self);
+    }
+
+    g_object_unref (connection);
+}
+
+static void
+on_query_callback (GObject              *object,
+                   GAsyncResult         *result,
+                   NautilusTagManager          *self,
+                   GAsyncReadyCallback   callback,
+                   const gchar          *signal_name,
+                   gpointer              signal_data,
+                   GCancellable         *cancellable)
+{
+    TrackerSparqlCursor *cursor;
+    g_autoptr (GError) error = NULL;
+    TrackerSparqlConnection *connection;
+
+    connection = TRACKER_SPARQL_CONNECTION (object);
+
+    cursor = tracker_sparql_connection_query_finish (connection,
+                                                     result,
+                                                     &error);
+
+    if (error != NULL)
+    {
+        g_warning ("Error on getting query callback: %s", error->message);
+
+        if (error->code != G_IO_ERROR_CANCELLED)
+        {
+            g_signal_emit_by_name (self, signal_name, signal_data);
+        }
+    }
+    else
+    {
+        tracker_sparql_cursor_next_async (cursor,
+                                          cancellable,
+                                          callback,
+                                          self);
+    }
+}
+
+static void
+on_update_callback (GObject      *object,
+                    GAsyncResult *result,
+                    NautilusTagManager  *self)
+{
+    TrackerSparqlConnection *connection;
+    GError *error;
+
+    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_signal_emit_by_name (self, "tags-updated", error);
+    }
+    else if (error && error->code == G_IO_ERROR_CANCELLED)
+    {
+        g_error_free (error);
+    }
+}
+
+static gboolean
+get_query_status (NautilusTagManager         *self,
+                  TrackerSparqlCursor *cursor,
+                  GAsyncResult        *result,
+                  const gchar         *signal_name,
+                  gpointer             signal_data)
+{
+    gboolean success;
+    g_autoptr (GError) error = NULL;
+
+    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))
+        {
+            g_signal_emit_by_name (self, signal_name, signal_data);
+        }
+    }
+
+    return success;
+}
+
+static void
+on_get_all_tags_cursor_callback (GObject      *object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data)
+{
+    TrackerSparqlCursor *cursor;
+    cursor = TRACKER_SPARQL_CURSOR (object);
+    const gchar *id;
+    const gchar *name;
+    gboolean success;
+    NautilusTagManager *self;
+    TagData *tag_data;
+
+    self = NAUTILUS_TAG_MANAGER (user_data);
+
+    success = get_query_status (self, cursor, result, "all-tags", self->all_tags);
+    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->cancellable_all,
+                                      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,
+                       self,
+                       on_get_all_tags_cursor_callback,
+                       "all-tags",
+                       self->all_tags,
+                       self->cancellable_all);
+}
+
+void
+nautilus_tag_manager_get_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->cancellable_all = cancellable;
+
+    query = g_string_new ("SELECT ?urn ?label WHERE { ?urn a nao:Tag ; nao:prefLabel ?label . } ORDER BY 
?label");
+
+    g_print("\n\"%s\" \n", query->str);
+
+    start_query_or_update (self,
+                           query,
+                           on_get_all_tags_query_callback,
+                           TRUE,
+                           cancellable);
+
+    g_string_free (query, TRUE);
+}
+
+static void
+on_get_selection_tags_cursor_callback (GObject      *object,
+                                       GAsyncResult *result,
+                                       gpointer      user_data)
+{
+    TrackerSparqlCursor *cursor;
+    cursor = TRACKER_SPARQL_CURSOR (object);
+    const gchar *id;
+    const gchar *name;
+    const gchar *url;
+    gboolean success;
+    NautilusTagManager *self;
+    TagData *tag_data;
+
+    self = NAUTILUS_TAG_MANAGER (user_data);
+
+    success = get_query_status (self, cursor, result, "selection-tags", self->selection_tags);
+    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 (self->selection_tags, tag_data);
+
+    tracker_sparql_cursor_next_async (cursor,
+                                      self->cancellable_selection,
+                                      on_get_selection_tags_cursor_callback,
+                                      self);
+}
+
+static void
+on_get_selection_tags_query_callback (GObject      *object,
+                                      GAsyncResult *result,
+                                      gpointer      user_data)
+{
+    NautilusTagManager *self;
+
+    self = NAUTILUS_TAG_MANAGER (user_data);
+
+    on_query_callback (object,
+                       result,
+                       self,
+                       on_get_selection_tags_cursor_callback,
+                       "selection-tags",
+                       self->selection_tags,
+                       self->cancellable_selection);
+}
+
+void
+nautilus_tag_manager_get_selection_tags (NautilusTagManager  *self,
+                                         GCancellable *cancellable)
+{
+    GString *query;
+
+    if (self->selection_tags)
+    {
+        g_queue_free_full (self->selection_tags, nautilus_tag_data_free);
+    }
+    self->selection_tags = g_queue_new ();
+
+    self->cancellable_selection = cancellable;
+
+    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 (self, query);
+
+    g_string_append (query, "} ORDER BY (?tag)");
+    g_print("\n\"%s\" \n", query->str);
+
+    start_query_or_update (self,
+                           query,
+                           on_get_selection_tags_query_callback,
+                           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;
+    NautilusTagManager *self;
+
+    self = NAUTILUS_TAG_MANAGER (user_data);
+
+    success = get_query_status (self, cursor, result, "files-with-tag", self->files_with_tag);
+    if (!success)
+    {
+        return;
+    }
+
+    url = g_strdup (tracker_sparql_cursor_get_string (cursor, 0, NULL));
+    g_queue_push_tail (self->files_with_tag, url);
+
+    tracker_sparql_cursor_next_async (cursor,
+                                      self->cancellable_get_files,
+                                      on_get_files_with_tag_cursor_callback,
+                                      self);
+}
+
+static void
+on_get_files_with_tag_query_callback (GObject      *object,
+                                      GAsyncResult *result,
+                                      gpointer      user_data)
+{
+    NautilusTagManager *self;
+
+    self = NAUTILUS_TAG_MANAGER (user_data);
+
+    on_query_callback (object,
+                       result,
+                       self,
+                       on_get_files_with_tag_cursor_callback,
+                       "files-with-tag",
+                       self->files_with_tag,
+                       self->cancellable_get_files);
+}
+
+void
+nautilus_tag_manager_get_files_with_tag (NautilusTagManager  *self,
+                                         const gchar  *tag_name,
+                                         GCancellable *cancellable)
+{
+    GString *query;
+
+    self->cancellable_get_files = cancellable;
+
+    if (self->files_with_tag)
+    {
+        g_queue_free_full (self->files_with_tag, g_free);
+    }
+    self->files_with_tag = g_queue_new ();
+
+    if (!nautilus_tag_queue_has_tag (self->all_tags, tag_name))
+    {
+        g_signal_emit_by_name (self, "files-with-tag", NULL);
+
+        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);
+
+    g_print ("\n\"%s\"\n", query->str);
+
+    start_query_or_update (self,
+                           query,
+                           on_get_files_with_tag_query_callback,
+                           TRUE,
+                           cancellable);
+
+    g_string_free (query, TRUE);
+}
+
+static void
+on_update_tag_query_callback (GObject      *object,
+                              GAsyncResult *result,
+                              gpointer      user_data)
+{
+    NautilusTagManager *self;
+
+    self = NAUTILUS_TAG_MANAGER (user_data);
+
+    on_update_callback (object,
+                        result,
+                        self);
+}
+
+void
+nautilus_tag_manager_remove_tag (NautilusTagManager  *self,
+                                 const gchar  *tag_name,
+                                 GCancellable *cancellable)
+{
+    GString *query;
+    GError *error;
+    GList *l;
+    TagData *tag_data;
+
+    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_signal_emit_by_name (self, "tags-updated", 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;
+        }
+    }
+
+    g_print ("\n\"%s\"\n", query->str);
+
+    start_query_or_update (self,
+                           query,
+                           on_update_tag_query_callback,
+                           FALSE,
+                           cancellable);
+
+    g_string_free (query, TRUE);
+}
+
+static GString*
+nautilus_tag_manager_delete_tag (NautilusTagManager  *self,
+                                 GString             *query,
+                                 TagData             *tag_data)
+{
+    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);
+
+    query = add_selection_filter (self, query);
+
+    g_string_append (query, "}\n");
+
+    return query;
+}
+
+static GString*
+nautilus_tag_manager_insert_tag (NautilusTagManager  *self,
+                                 GString             *query,
+                                 TagData             *tag_data)
+{
+    g_autofree gchar *tag_color = NULL;
+
+    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);
+
+    query = add_selection_filter (self, query);
+
+    g_string_append (query, "}\n");
+
+    //g_print ("\n\"%s\"\n", query->str);
+
+    return query;
+}
+
+void
+nautilus_tag_manager_update_tags (NautilusTagManager *self,
+                                  GList              *selection,
+                                  GQueue             *selection_tags,
+                                  GQueue             *all_tags,
+                                  GQueue             *new_selection_tags,
+                                  GCancellable       *cancellable)
+{
+    GString *query;
+    GList *l;
+    TagData *tag_data;
+    gchar *current_tag_name;
+
+    query = g_string_new ("");
+
+    g_print ("\nTAGS TO INSERT:\n");
+    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))
+        {
+            g_print ("%s\n", tag_data->name);
+            query = nautilus_tag_manager_insert_tag (self, query, tag_data);
+        }
+    }
+
+    g_print ("\nTAGS TO DELETE:\n");
+    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)
+        {
+            g_print ("%s\n", tag_data->name);
+            query = nautilus_tag_manager_delete_tag (self, query, tag_data);
+        }
+
+        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;
+            }
+        }
+    }
+
+    g_print ("\n%s\n", query->str);
+
+    start_query_or_update (self,
+                           query,
+                           on_update_tag_query_callback,
+                           FALSE,
+                           cancellable);
+
+    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);
+    }
+    if (self->selection_tags)
+    {
+        g_queue_free_full (self->selection_tags, nautilus_tag_data_free);
+    }
+    if (self->files_with_tag)
+    {
+        g_queue_free_full (self->files_with_tag, g_free);
+    }
+
+    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[ALL_TAGS] = g_signal_new ("all-tags",
+                                      NAUTILUS_TYPE_TAG_MANAGER,
+                                      G_SIGNAL_RUN_LAST,
+                                      0,
+                                      NULL,
+                                      NULL,
+                                      g_cclosure_marshal_VOID__POINTER,
+                                      G_TYPE_NONE,
+                                      1,
+                                      G_TYPE_POINTER);
+
+    signals[SELECTION_TAGS] = g_signal_new ("selection-tags",
+                                            NAUTILUS_TYPE_TAG_MANAGER,
+                                            G_SIGNAL_RUN_LAST,
+                                            0,
+                                            NULL,
+                                            NULL,
+                                            g_cclosure_marshal_VOID__POINTER,
+                                            G_TYPE_NONE,
+                                            1,
+                                            G_TYPE_POINTER);
+
+    signals[TAGS_UPDATED] = g_signal_new ("tags-updated",
+                                           NAUTILUS_TYPE_TAG_MANAGER,
+                                           G_SIGNAL_RUN_LAST,
+                                           0,
+                                           NULL,
+                                           NULL,
+                                           g_cclosure_marshal_VOID__POINTER,
+                                           G_TYPE_NONE,
+                                           1,
+                                           G_TYPE_POINTER);
+
+    signals[FILES_WITH_TAG] = g_signal_new ("files-with-tag",
+                                           NAUTILUS_TYPE_TAG_MANAGER,
+                                           G_SIGNAL_RUN_LAST,
+                                           0,
+                                           NULL,
+                                           NULL,
+                                           g_cclosure_marshal_VOID__POINTER,
+                                           G_TYPE_NONE,
+                                           1,
+                                           G_TYPE_POINTER);
+}
+
+NautilusTagManager* nautilus_tag_manager_new (GList *selection)
+{
+    NautilusTagManager *self;
+
+    self = g_object_new (NAUTILUS_TYPE_TAG_MANAGER, NULL);
+
+    self->selection = selection;
+
+    return self;
+}
+
+static void
+nautilus_tag_manager_init (NautilusTagManager *self)
+{
+}
diff --git a/src/nautilus-tag-manager.h b/src/nautilus-tag-manager.h
new file mode 100644
index 0000000..67a2124
--- /dev/null
+++ b/src/nautilus-tag-manager.h
@@ -0,0 +1,77 @@
+/* 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         (GList *selection);
+
+void            nautilus_tag_manager_get_all_tags       (NautilusTagManager  *self,
+                                                         GCancellable *cancellable);
+
+void            nautilus_tag_manager_get_selection_tags (NautilusTagManager *self,
+                                                         GCancellable *cancellable);
+
+void            nautilus_tag_manager_get_files_with_tag (NautilusTagManager  *self,
+                                                         const gchar  *tag_name,
+                                                         GCancellable *cancellable);
+
+void            nautilus_tag_manager_remove_tag         (NautilusTagManager  *self,
+                                                         const gchar  *tag_name,
+                                                         GCancellable *cancellable);
+
+void            nautilus_tag_manager_update_tags        (NautilusTagManager *self,
+                                                         GList              *selection,
+                                                         GQueue             *selection_tags,
+                                                         GQueue             *all_tags,
+                                                         GQueue             *new_selection_tags,
+                                                         GCancellable       *cancellable);
+
+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 0000000..73893ce
--- /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 0000000..01ea881
--- /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 0000000..861fb5c
--- /dev/null
+++ b/src/nautilus-tags-dialog.c
@@ -0,0 +1,698 @@
+/* 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_all;
+    GCancellable *cancellable_selection;
+    GCancellable *cancellable_update;
+    GCancellable *cancellable_get_files;
+};
+
+G_DEFINE_TYPE (NautilusTagsDialog, nautilus_tags_dialog, GTK_TYPE_DIALOG);
+
+static void
+update_tags (NautilusTagsDialog *dialog)
+{
+    nautilus_tag_manager_get_all_tags (dialog->tag_manager, dialog->cancellable_all);
+}
+
+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++)
+    {
+       // g_print ("compare %s with %s\n", tag_name, nautilus_tag_widget_get_tag_name (NAUTILUS_TAG_WIDGET 
(l->data)));
+
+        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));
+
+    g_print ("activated row\n");
+}
+
+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 (NautilusTagManager *tag,
+                            gpointer            user_data,
+                            NautilusTagsDialog *dialog)
+{
+    GList *l;
+    TagData *tag_data;
+    GQueue *selection_tags_check;
+    GList *l_check;
+    TagData *tag_data_check;
+    g_autofree gchar *color_rgb = NULL;
+    NautilusTagWidget *tag_widget;
+
+    if (dialog->action == NO_ACTION)
+    {
+        dialog->selection_tags = nautilus_tag_copy_tag_queue (user_data);
+        g_print("\nselection tags:\n");
+
+        for (l = g_queue_peek_head_link (dialog->selection_tags); l != NULL; l = l->next)
+        {
+            tag_data = l->data;
+
+            g_print("%s %s %s\n", tag_data->id, tag_data->name, tag_data->url);
+        }
+
+        fill_selection_box (dialog);
+    }
+    else
+    {
+        selection_tags_check = user_data;
+
+        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)
+            {
+                g_print ("something changed with selection tags\n");
+
+                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,
+                                          dialog->selection,
+                                          dialog->selection_tags,
+                                          dialog->all_tags,
+                                          dialog->new_selection_tags,
+                                          dialog->cancellable_update);
+
+        /*if (dialog->action == ADD_TAG)
+        {
+            gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (dialog->color_button), &color);
+            color_rgb = gdk_rgba_to_string (&color);
+
+            nautilus_tag_manager_insert_tag (dialog->tag_manager,
+                                             gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)),
+                                             color_rgb,
+                                             dialog->cancellable_update);
+        }
+        else if (dialog->action == DELETE_TAG)
+        {
+            nautilus_tag_manager_delete_tag (dialog->tag_manager,
+                                             gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)),
+                                             dialog->cancellable_update);
+        }
+        else if (dialog->action == REMOVE_TAG)
+        {
+            nautilus_tag_manager_remove_tag (dialog->tag_manager,
+                                             gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)),
+                                             dialog->cancellable_update);
+        }*/
+    }
+}
+
+static void
+on_tags_updated (NautilusTagManager *tag,
+                 gpointer            user_data,
+                 NautilusTagsDialog *dialog)
+{
+    GError *error;
+
+    error = user_data;
+
+    if (error != NULL)
+    {
+        g_print ("something went wrong: %s\n", error->message);
+
+        g_error_free (error);
+    }
+    else
+    {
+        g_print ("tags updated succesfully!\n");
+    }
+
+    gtk_widget_destroy (GTK_WIDGET (dialog));
+
+}
+
+static void
+on_all_tags_obtained (NautilusTagManager *tag,
+                      gpointer            user_data,
+                      NautilusTagsDialog *dialog)
+{
+    GList *l;
+    TagData *tag_data;
+    GQueue *all_tags_check;
+    GList *l_check;
+    TagData *tag_data_check;
+
+     /* only for testing */
+    nautilus_tag_manager_get_files_with_tag (dialog->tag_manager, "demo", dialog->cancellable_get_files);
+
+    if (dialog->action == NO_ACTION)
+    {
+        dialog->all_tags = nautilus_tag_copy_tag_queue (user_data);
+
+        g_print("\n\nall tags\n");
+
+        for (l = g_queue_peek_head_link (dialog->all_tags); l != NULL; l = l->next)
+        {
+            tag_data = l->data;
+
+            g_print("%s %s\n", tag_data->id, tag_data->name);
+        }
+
+        fill_list_box (dialog);
+
+        nautilus_tag_manager_get_selection_tags (dialog->tag_manager, dialog->cancellable_selection);
+    }
+    else
+    {
+        all_tags_check = user_data;
+
+        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)
+            {
+                g_print ("something changed with all tags\n");
+
+                return;
+            }
+        }
+
+        nautilus_tag_manager_get_selection_tags (dialog->tag_manager, dialog->cancellable_selection);
+    }
+}
+
+static void
+on_files_with_tag_obtained (NautilusTagManager *tag,
+                            gpointer            user_data,
+                            NautilusTagsDialog *dialog)
+{
+    GList *l;
+    GQueue *files_with_tag;
+
+    files_with_tag = user_data;
+
+    g_print ("\nthe following files have the tag 'demo':\n");
+    for (l = g_queue_peek_head_link (files_with_tag); l != NULL; l = l->next)
+    {
+        g_print ("%s\n", (char*)l->data);
+    }
+}
+
+static void
+on_tags_entry_activate (NautilusTagsDialog *dialog)
+{
+    g_print ("\n%s\n", gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)));
+
+    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);
+    g_print ("add button clicked\n");
+
+    //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_all);
+    g_clear_object (&dialog->cancellable_all);
+
+    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_signal_handlers_disconnect_by_func (dialog->tag_manager, on_selection_tags_obtained, dialog);
+    g_signal_handlers_disconnect_by_func (dialog->tag_manager, on_all_tags_obtained, dialog);
+    g_signal_handlers_disconnect_by_func (dialog->tag_manager, on_tags_updated, dialog);
+    g_signal_handlers_disconnect_by_func (dialog->tag_manager, on_files_with_tag_obtained, dialog);
+
+    g_object_unref (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));
+
+    dialog->tag_manager = nautilus_tag_manager_new (dialog->selection);
+
+    g_signal_connect (dialog->tag_manager, "selection-tags", G_CALLBACK (on_selection_tags_obtained), 
dialog);
+    g_signal_connect (dialog->tag_manager, "all-tags", G_CALLBACK (on_all_tags_obtained), dialog);
+    g_signal_connect (dialog->tag_manager, "tags-updated", G_CALLBACK (on_tags_updated), dialog);
+    /*the signal below is not for this dialog, it's here for testing */
+    g_signal_connect (dialog->tag_manager, "files-with-tag", G_CALLBACK (on_files_with_tag_obtained), 
dialog);
+
+    g_signal_connect (dialog->tags_listbox, "row-activated", G_CALLBACK (on_row_activated), dialog);
+
+    dialog->cancellable_all = g_cancellable_new ();
+    dialog->cancellable_selection = g_cancellable_new ();
+    dialog->cancellable_update = g_cancellable_new ();
+
+    nautilus_tag_manager_get_all_tags (dialog->tag_manager, dialog->cancellable_all);
+
+    dialog->action = NO_ACTION;
+
+    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 0000000..1968bc6
--- /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/resources/nautilus.gresource.xml b/src/resources/nautilus.gresource.xml
index 720bfa5..7f3a5e9 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 4a53de4..409db20 100644
--- a/src/resources/ui/nautilus-files-view-context-menus.ui
+++ b/src/resources/ui/nautilus-files-view-context-menus.ui
@@ -253,6 +253,13 @@
     </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>
+    </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 0000000..5139c7c
--- /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


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]