[nautilus/wip/alexpandelea/tags] implement tags



commit 505bf11b0788df9ba0b1de99522d09b6498c9c1c
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-widget.c                          |  140 ++++
 src/nautilus-tag-widget.h                          |   39 +
 src/nautilus-tag.c                                 |  788 ++++++++++++++++++++
 src/nautilus-tag.h                                 |   76 ++
 src/nautilus-tags-dialog.c                         |  450 +++++++++++
 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           |  202 +++++
 11 files changed, 1771 insertions(+), 1 deletions(-)
---
diff --git a/src/meson.build b/src/meson.build
index 83723b9..103b661 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.c',
+                            'nautilus-tag.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-widget.c b/src/nautilus-tag-widget.c
new file mode 100644
index 0000000..95f951f
--- /dev/null
+++ b/src/nautilus-tag-widget.c
@@ -0,0 +1,140 @@
+/* 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"
+
+ struct _NautilusTagWidget
+{
+    GtkBox parent;
+
+    GtkWidget *label;
+    GtkWidget *circle;
+
+    gchar *color;
+};
+
+G_DEFINE_TYPE (NautilusTagWidget, nautilus_tag_widget, GTK_TYPE_BOX);
+
+static gboolean
+paint_circle (GtkWidget *widget,
+              cairo_t   *cr,
+              gpointer   data)
+{
+    guint width, height;
+    GdkRGBA color;
+    GtkStyleContext *context;
+    NautilusTagWidget *tag_widget;
+
+    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);
+
+    return FALSE;
+}
+
+static gchar*
+parse_color_from_tag_id (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;
+}
+
+static void
+nautilus_tag_widget_finalize (GObject *object)
+{
+    NautilusTagWidget *self;
+
+    self = NAUTILUS_TAG_WIDGET (object);
+
+    g_free (self->color);
+
+    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 (gchar *tag_label,
+                                    gchar *tag_id)
+{
+    NautilusTagWidget *self;
+    self = g_object_new (NAUTILUS_TYPE_TAG_WIDGET,
+                         "orientation",
+                         GTK_ORIENTATION_HORIZONTAL,
+                         "spacing",
+                         5,
+                         NULL);
+
+    self->color = parse_color_from_tag_id (tag_id);
+
+    self->label = gtk_label_new (tag_label);
+
+    //g_print ("%s\n", self->color);
+
+    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), self->circle, FALSE, FALSE, 0);
+    gtk_box_pack_start (GTK_BOX (self), self->label, FALSE, FALSE, 0);
+
+    return GTK_WIDGET (self);
+}
+
+static void
+nautilus_tag_widget_init (NautilusTagWidget *self)
+{
+}
\ No newline at end of file
diff --git a/src/nautilus-tag-widget.h b/src/nautilus-tag-widget.h
new file mode 100644
index 0000000..dd9b306
--- /dev/null
+++ b/src/nautilus-tag-widget.h
@@ -0,0 +1,39 @@
+/* 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, GtkBox);
+
+GtkWidget*    nautilus_tag_widget_new    (gchar *tag_label,
+                                          gchar *tag_id);
+
+G_END_DECLS
+
+#endif
\ No newline at end of file
diff --git a/src/nautilus-tag.c b/src/nautilus-tag.c
new file mode 100644
index 0000000..37a2f1f
--- /dev/null
+++ b/src/nautilus-tag.c
@@ -0,0 +1,788 @@
+/* nautilus-tag.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.h"
+#include "nautilus-file.h"
+#include <tracker-sparql.h>
+
+struct _NautilusTag
+{
+    GObject object;
+
+    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 (NautilusTag, nautilus_tag, G_TYPE_OBJECT);
+
+enum
+{
+    ALL_TAGS,
+    SELECTION_TAGS,
+    TAGS_UPDATED,
+    FILES_WITH_TAG,
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+gboolean
+nautilus_tag_selection_has_tag (NautilusTag *self,
+                               const gchar *tag_name)
+{
+    GList *l;
+    TagData *tag_data;
+
+    for (l = g_queue_peek_head_link (self->selection_tags); l != NULL; l = l->next)
+    {
+        tag_data = l->data;
+
+        if (g_strcmp0 (tag_name, tag_data->name) == 0)
+        {
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+gboolean
+nautilus_tag_exists (NautilusTag *self,
+                     const gchar *tag_name)
+{
+    GList *l;
+    TagData *tag_data;
+
+    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)
+        {
+            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);
+}
+
+static GString*
+add_selection_filter (NautilusTag *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 (NautilusTag         *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,
+                   NautilusTag          *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,
+                    NautilusTag  *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 (NautilusTag         *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;
+    NautilusTag *self;
+    TagData *tag_data;
+
+    self = NAUTILUS_TAG (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 = g_new0 (TagData, 1);
+    tag_data->id = g_strdup (id);
+    tag_data->name = g_strdup (name);
+
+    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)
+{
+    NautilusTag *self;
+
+    self = NAUTILUS_TAG (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_get_all_tags (NautilusTag  *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;
+    NautilusTag *self;
+    TagData *tag_data;
+
+    self = NAUTILUS_TAG (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 = g_new0 (TagData, 1);
+    tag_data->id = g_strdup (id);
+    tag_data->name = g_strdup (name);
+    tag_data->url = g_strdup (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)
+{
+    NautilusTag *self;
+
+    self = NAUTILUS_TAG (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_get_selection_tags (NautilusTag  *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;
+    NautilusTag *self;
+
+    self = NAUTILUS_TAG (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)
+{
+    NautilusTag *self;
+
+    self = NAUTILUS_TAG (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_get_files_with_tag (NautilusTag  *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_exists (self, 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)
+{
+    NautilusTag *self;
+
+    self = NAUTILUS_TAG (user_data);
+
+    on_update_callback (object,
+                        result,
+                        self);
+}
+
+void
+nautilus_tag_delete_tag (NautilusTag  *self,
+                         const gchar  *tag_name,
+                         GCancellable *cancellable)
+{
+    GString *query;
+    GError *error;
+
+    self->cancellable_update = cancellable;
+
+    if (!nautilus_tag_selection_has_tag (self, tag_name))
+    {
+        error = g_error_new (G_IO_ERROR,
+                             G_IO_ERROR_INVALID_ARGUMENT,
+                             "selection doesn't have this tag");
+
+        g_signal_emit_by_name (self, "tags-updated", error);
+
+        return;
+    }
+
+    query = g_string_new ("DELETE { ?urn nao:hasTag ?label } "
+                          "WHERE { ?urn a nfo:FileDataObject ; nie:url ?url . ");
+
+    g_string_append_printf (query,
+                            "?label nao:prefLabel '%s' . ",
+                            tag_name);
+
+    query = add_selection_filter (self, query);
+
+    g_string_append (query, "}");
+
+    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);
+}
+
+void
+nautilus_tag_remove_tag (NautilusTag  *self,
+                         const gchar  *tag_name,
+                         GCancellable *cancellable)
+{
+    GString *query;
+    GError *error;
+    GList *l;
+    TagData *tag_data;
+
+    if (!nautilus_tag_exists (self, 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);
+}
+
+void
+nautilus_tag_insert_tag (NautilusTag  *self,
+                         const gchar  *tag_name,
+                         const gchar  *tag_color,
+                         GCancellable *cancellable)
+{
+    GString *query;
+    self->cancellable_update = cancellable;
+
+    query = g_string_new ("");
+
+    if (!nautilus_tag_exists (self, tag_name))
+    {
+        g_string_append_printf (query,
+                                "INSERT DATA { <org:gnome:nautilus:tag:%s:%s> a nao:Tag ; nao:prefLabel '%s' 
}\n",
+                                tag_name,
+                                tag_color,
+                                tag_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_name);
+
+    query = add_selection_filter (self, query);
+
+    g_string_append (query, "}");
+
+    g_print ("\n\"%s\"\n", query->str);
+
+    start_query_or_update (self,
+                           query,
+                           on_update_tag_query_callback,
+                           FALSE,
+                           cancellable);
+}
+
+static void
+nautilus_tag_finalize (GObject *object)
+{
+    NautilusTag *self;
+
+    self = NAUTILUS_TAG (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_parent_class)->finalize (object);
+}
+
+static void
+nautilus_tag_class_init (NautilusTagClass *klass)
+{
+    GObjectClass *oclass;
+
+    oclass = G_OBJECT_CLASS (klass);
+
+    oclass->finalize = nautilus_tag_finalize;
+
+    signals[ALL_TAGS] = g_signal_new ("all-tags",
+                                      NAUTILUS_TYPE_TAG,
+                                      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,
+                                            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,
+                                           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,
+                                           G_SIGNAL_RUN_LAST,
+                                           0,
+                                           NULL,
+                                           NULL,
+                                           g_cclosure_marshal_VOID__POINTER,
+                                           G_TYPE_NONE,
+                                           1,
+                                           G_TYPE_POINTER);
+}
+
+NautilusTag* nautilus_tag_new (GList *selection)
+{
+    NautilusTag *self;
+
+    self = g_object_new (NAUTILUS_TYPE_TAG, NULL);
+
+    self->selection = selection;
+
+    return self;
+}
+
+static void
+nautilus_tag_init (NautilusTag *self)
+{
+}
diff --git a/src/nautilus-tag.h b/src/nautilus-tag.h
new file mode 100644
index 0000000..c2f1c5d
--- /dev/null
+++ b/src/nautilus-tag.h
@@ -0,0 +1,76 @@
+/* nautilus-tag.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_H
+#define NAUTILUS_TAG_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_TAG (nautilus_tag_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusTag, nautilus_tag, NAUTILUS, TAG, GObject);
+
+typedef struct
+{
+    gchar *id;
+    gchar *name;
+    gchar *url;
+} TagData;
+
+NautilusTag*    nautilus_tag_new                (GList *selection);
+
+void            nautilus_tag_get_all_tags       (NautilusTag  *self,
+                                                 GCancellable *cancellable);
+
+void            nautilus_tag_get_selection_tags (NautilusTag *self,
+                                                 GCancellable *cancellable);
+
+void            nautilus_tag_get_files_with_tag (NautilusTag  *self,
+                                                 const gchar  *tag_name,
+                                                 GCancellable *cancellable);
+
+void            nautilus_tag_delete_tag         (NautilusTag  *self,
+                                                 const gchar  *tag_name,
+                                                 GCancellable *cancellable);
+
+void            nautilus_tag_remove_tag         (NautilusTag  *self,
+                                                 const gchar  *tag_name,
+                                                 GCancellable *cancellable);
+
+void            nautilus_tag_insert_tag         (NautilusTag  *self,
+                                                 const gchar  *tag_name,
+                                                 const gchar  *tag_color,
+                                                 GCancellable *cancellable);
+
+void            nautilus_tag_data_free          (gpointer data);
+
+GQueue*         nautilus_tag_copy_tag_queue     (GQueue *queue);
+
+gboolean        nautilus_tag_exists             (NautilusTag *self,
+                                                 const gchar *tag_name);
+
+gboolean        nautilus_tagselection_has_tag   (NautilusTag *self,
+                                                 const gchar *tag_name);
+
+G_END_DECLS
+
+#endif
\ No newline at end of file
diff --git a/src/nautilus-tags-dialog.c b/src/nautilus-tags-dialog.c
new file mode 100644
index 0000000..c9c0a38
--- /dev/null
+++ b/src/nautilus-tags-dialog.c
@@ -0,0 +1,450 @@
+/* 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.h"
+#include "nautilus-tag-widget.h"
+
+typedef enum
+{
+    NO_ACTION,
+    ADD_TAG,
+    DELETE_TAG,
+    REMOVE_TAG
+} ChosenAction;
+
+struct _NautilusTagsDialog
+{
+    GtkDialog parent;
+
+    NautilusWindow *window;
+    GList *selection;
+
+    GtkWidget *cancel_button;
+    GtkWidget *update_tags_button;
+    GtkWidget *delete_tag_button;
+    GtkWidget *tags_entry;
+    GtkWidget *color_button;
+    GtkWidget *tags_listbox;
+    GtkWidget *selection_tags_box;
+    GtkWidget *add_tag_button;
+
+    NautilusTag *tag;
+    GQueue *all_tags;
+    GQueue *selection_tags;
+
+    GQueue *list_box_rows;
+    GQueue *all_tags_widgets;
+    GQueue *selection_tags_widgets;
+    GQueue *tags_to_delete;
+    GQueue *tags_to_insert;
+
+    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_get_all_tags (dialog->tag, dialog->cancellable_all);
+}
+
+static void
+on_row_activated (GtkListBox    *box,
+                  GtkListBoxRow *row,
+                  gpointer       user_data)
+{
+    //NautilusTagsDialog *dialog;
+
+    //dialog = NAUTILUS_TAGS_DIALOG (user_data);
+
+    g_print ("activated row\n");
+}
+
+static void
+fill_selection_box (NautilusTagsDialog *dialog)
+{
+    GList *l;
+    GtkWidget *tag_widget;
+    TagData *tag_data;
+
+    for (l = g_queue_peek_head_link (dialog->selection_tags); l != NULL; l = l->next)
+    {
+        tag_data = l->data;
+
+        tag_widget = nautilus_tag_widget_new (tag_data->name, tag_data->id);
+        gtk_container_add (GTK_CONTAINER (dialog->selection_tags_box), tag_widget);
+    }
+
+    gtk_widget_show_all (dialog->selection_tags_box);
+}
+
+static void
+fill_list_box (NautilusTagsDialog *dialog)
+{
+    GList *l;
+    TagData *tag_data;
+    GtkWidget *row;
+    GtkWidget *tag_widget;
+
+    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);
+
+        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);
+    }
+}
+
+static void
+nautilus_tags_dialog_on_response (NautilusTagsDialog *dialog,
+                                  gint                response_id,
+                                  gpointer            user_data)
+{
+    if (response_id == GTK_RESPONSE_YES)
+    {
+        dialog->action = ADD_TAG;
+
+        update_tags (dialog);
+    }
+    else if (response_id == GTK_RESPONSE_NO)
+    {
+        dialog->action = DELETE_TAG;
+
+        update_tags (dialog);
+    }
+    else if (response_id == GTK_RESPONSE_REJECT)
+    {
+        dialog->action = REMOVE_TAG;
+
+        update_tags (dialog);
+    }
+    else
+    {
+        gtk_widget_destroy (GTK_WIDGET (dialog));
+    }
+}
+
+static void
+on_selection_tags_obtained (NautilusTag        *tag,
+                            gpointer            user_data,
+                            NautilusTagsDialog *dialog)
+{
+    GList *l;
+    TagData *tag_data;
+    GQueue *selection_tags_check;
+    GList *l_check;
+    TagData *tag_data_check;
+    GdkRGBA color;
+    g_autofree gchar *color_rgb = NULL;
+
+    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;
+            }
+        }
+
+        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_insert_tag (dialog->tag,
+                                     gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)),
+                                     color_rgb,
+                                     dialog->cancellable_update);
+        }
+        else if (dialog->action == DELETE_TAG)
+        {
+            nautilus_tag_delete_tag (dialog->tag,
+                                     gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)),
+                                     dialog->cancellable_update);
+        }
+        else if (dialog->action == REMOVE_TAG)
+        {
+            nautilus_tag_remove_tag (dialog->tag,
+                                     gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)),
+                                     dialog->cancellable_update);
+        }
+    }
+}
+
+static void
+on_tags_updated (NautilusTag        *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 (NautilusTag        *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_get_files_with_tag (dialog->tag, "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);
+    }
+    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_get_selection_tags (dialog->tag, dialog->cancellable_selection);
+    }
+}
+
+static void
+on_files_with_tag_obtained (NautilusTag        *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)));
+}
+
+static void
+on_add_button_clicked (GtkButton *button,
+                          gpointer   user_data)
+{
+    g_print ("add button clicked\n");
+}
+
+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);
+    }
+
+    g_signal_handlers_disconnect_by_func (dialog->tag, on_selection_tags_obtained, dialog);
+    g_signal_handlers_disconnect_by_func (dialog->tag, on_all_tags_obtained, dialog);
+    g_signal_handlers_disconnect_by_func (dialog->tag, on_tags_updated, dialog);
+    g_signal_handlers_disconnect_by_func (dialog->tag, on_files_with_tag_obtained, dialog);
+
+    //g_signal_handlers_disconnect_by_func (dialog->tags_listbox, on_row_activated, dialog);
+
+    g_queue_free (dialog->list_box_rows);
+
+    g_object_unref (dialog->tag);
+
+    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, delete_tag_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_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 = nautilus_tag_new (dialog->selection);
+
+    g_signal_connect (dialog->tag, "selection-tags", G_CALLBACK (on_selection_tags_obtained), dialog);
+    g_signal_connect (dialog->tag, "all-tags", G_CALLBACK (on_all_tags_obtained), dialog);
+    g_signal_connect (dialog->tag, "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, "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_get_all_tags (dialog->tag, dialog->cancellable_all);
+    nautilus_tag_get_selection_tags (dialog->tag, dialog->cancellable_selection);
+
+    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..3ccf8f2
--- /dev/null
+++ b/src/resources/ui/nautilus-tags-dialog.ui
@@ -0,0 +1,202 @@
+<?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>
+        <property name="can_default">True</property>
+        <style>
+          <class name="suggested-action"/>
+        </style>
+      </object>
+    </child>
+    <child type="action">
+      <object class="GtkButton" id="delete_tag_button">
+        <property name="label" translatable="yes">_Delete Tag</property>
+        <property name="visible">True</property>
+        <property name="use_underline">True</property>
+        <property name="can_default">True</property>
+        <style>
+          <class name="destructive-action"/>
+        </style>
+      </object>
+    </child>
+    <child type="action">
+      <object class="GtkButton" id="remove_tag_button">
+        <property name="label" translatable="yes">_Remove Tag</property>
+        <property name="visible">True</property>
+        <property name="use_underline">True</property>
+        <property name="can_default">True</property>
+        <style>
+          <class name="destructive-action"/>
+        </style>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="yes">update_tags_button</action-widget>
+      <action-widget response="no">delete_tag_button</action-widget>
+      <action-widget response="reject">remove_tag_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="spacing">5</property>
+                        <property name="visible">True</property>
+                        <property name="halign">start</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">0</property>
+                      </packing>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </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">400</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>
+                    <signal name="clicked" handler="on_add_button_clicked" swapped="yes" />
+                    <child>
+                      <object class="GtkBox">
+                        <property name="visible">True</property>
+                        <property name="orientation">horizontal</property>
+                        <property name="spacing">0</property>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="visible">True</property>
+                            <property name="icon-name">list-add-symbolic</property>
+                            <property name="icon-size">1</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">Add</property>
+                            <property name="can_focus">False</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </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">100</property>
+            <property name="min-content-height">100</property>
+            <property name="max-content-width">300</property>
+            <property name="min-content-width">300</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]