[nautilus/wip/alexpandelea/tags] implement tags
- From: Alexandru-Ionut Pandelea <alexpandelea src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [nautilus/wip/alexpandelea/tags] implement tags
- Date: Tue, 4 Jul 2017 13:59:17 +0000 (UTC)
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]