[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: Thu, 6 Jul 2017 09:24:12 +0000 (UTC)
commit 48b32979bfad3c5a55edf08e04b496ab9b6c2c90
Author: Alexandru Pandelea <alexandru pandelea gmail com>
Date: Wed Jun 28 17:47:41 2017 +0100
implement tags
src/meson.build | 8 +-
src/nautilus-files-view.c | 22 +
src/nautilus-tag-manager.c | 839 ++++++++++++++++++++
src/nautilus-tag-manager.h | 77 ++
src/nautilus-tag-widget.c | 304 +++++++
src/nautilus-tag-widget.h | 51 ++
src/nautilus-tags-dialog.c | 698 ++++++++++++++++
src/nautilus-tags-dialog.h | 39 +
src/resources/nautilus.gresource.xml | 1 +
.../ui/nautilus-files-view-context-menus.ui | 7 +
src/resources/ui/nautilus-tags-dialog.ui | 157 ++++
11 files changed, 2202 insertions(+), 1 deletions(-)
---
diff --git a/src/meson.build b/src/meson.build
index 83723b9..9abe3bd 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -264,7 +264,13 @@ if get_option ('enable-tracker')
'nautilus-batch-rename-utilities.c',
'nautilus-batch-rename-utilities.h',
'nautilus-search-engine-tracker.c',
- 'nautilus-search-engine-tracker.h']
+ 'nautilus-search-engine-tracker.h',
+ 'nautilus-tags-dialog.c',
+ 'nautilus-tags-dialog.h',
+ 'nautilus-tag-manager.c',
+ 'nautilus-tag-manager.h',
+ 'nautilus-tag-widget.c',
+ 'nautilus-tag-widget.h']
endif
nautilus_deps = [glib,
diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c
index 1874d8b..571628b 100644
--- a/src/nautilus-files-view.c
+++ b/src/nautilus-files-view.c
@@ -31,6 +31,7 @@
#ifdef ENABLE_TRACKER
#include "nautilus-batch-rename-dialog.h"
#include "nautilus-batch-rename-utilities.h"
+#include "nautilus-tags-dialog.h"
#endif
#include "nautilus-error-reporting.h"
#include "nautilus-file-undo-manager.h"
@@ -1572,6 +1573,26 @@ action_delete (GSimpleAction *action,
}
static void
+action_edit_tags (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ GtkWidget *dialog;
+ NautilusWindow *window;
+ GList *selection;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+
+ window = nautilus_files_view_get_window (NAUTILUS_FILES_VIEW (user_data));
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+ dialog = nautilus_tags_dialog_new (selection, window);
+ gtk_widget_show (GTK_WIDGET (dialog));
+}
+
+static void
action_restore_from_trash (GSimpleAction *action,
GVariant *state,
gpointer user_data)
@@ -7001,6 +7022,7 @@ const GActionEntry view_entries[] =
{ "copy-to", action_copy_to},
{ "move-to-trash", action_move_to_trash},
{ "delete-from-trash", action_delete },
+ { "edit-tags", action_edit_tags},
/* We separate the shortcut and the menu item since we want the shortcut
* to always be available, but we don't want the menu item shown if not
* completely necesary. Since the visibility of the menu item is based on
diff --git a/src/nautilus-tag-manager.c b/src/nautilus-tag-manager.c
new file mode 100644
index 0000000..fca7583
--- /dev/null
+++ b/src/nautilus-tag-manager.c
@@ -0,0 +1,839 @@
+/* nautilus-tag-manager.c
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru pandelea gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nautilus-tag-manager.h"
+#include "nautilus-file.h"
+#include <tracker-sparql.h>
+
+struct _NautilusTagManager
+{
+ GObject object;
+
+ NautilusTagManager *manager;
+
+ GList *selection;
+
+ GQueue *all_tags;
+ GQueue *selection_tags;
+ GQueue *files_with_tag;
+
+ GCancellable *cancellable_all;
+ GCancellable *cancellable_selection;
+ GCancellable *cancellable_update;
+ GCancellable *cancellable_get_files;
+};
+
+G_DEFINE_TYPE (NautilusTagManager, nautilus_tag_manager, G_TYPE_OBJECT);
+
+enum
+{
+ ALL_TAGS,
+ SELECTION_TAGS,
+ TAGS_UPDATED,
+ FILES_WITH_TAG,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+gchar*
+parse_color_from_tag_id (const gchar *tag_id)
+{
+ gchar *color;
+
+ if (g_strrstr (tag_id, "org:gnome:nautilus:tag"))
+ {
+ color = g_strdup (g_strrstr (tag_id, ":") + 1);
+ }
+ else
+ {
+ color = g_strdup ("rgb(220,220,220)");
+ }
+
+ return color;
+}
+
+gboolean
+nautilus_tag_queue_has_tag (GQueue *tag_queue,
+ const gchar *tag_name)
+{
+ GList *l;
+ TagData *tag_data;
+
+ for (l = g_queue_peek_head_link (tag_queue); l != NULL; l = l->next)
+ {
+ tag_data = l->data;
+
+ if (g_strcmp0 (tag_name, tag_data->name) == 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+GQueue*
+nautilus_tag_copy_tag_queue (GQueue *queue)
+{
+ GQueue *result_queue;
+ GList *l;
+ TagData *tag_data, *new_tag_data;
+
+ result_queue = g_queue_new ();
+
+ for (l = g_queue_peek_head_link (queue); l != NULL; l = l->next)
+ {
+ tag_data = l->data;
+
+ new_tag_data = g_new0 (TagData, 1);
+ new_tag_data->id = g_strdup (tag_data->id);
+ new_tag_data->name = g_strdup (tag_data->name);
+ new_tag_data->url = g_strdup (tag_data->url);
+
+ g_queue_push_tail (result_queue, new_tag_data);
+ }
+
+ return result_queue;
+}
+
+void
+nautilus_tag_data_free (gpointer data)
+{
+ TagData *tag_data;
+
+ tag_data = data;
+
+ g_free (tag_data->id);
+ g_free (tag_data->name);
+ g_free (tag_data->url);
+ g_free (tag_data);
+}
+
+TagData*
+nautilus_tag_data_new (const gchar *id,
+ const gchar *name,
+ const gchar *url)
+{
+ TagData *data;
+
+ data = g_new0 (TagData, 1);
+
+ data->id = g_strdup (id);
+ data->name = g_strdup (name);
+ data->url = g_strdup (url);
+
+ return data;
+}
+
+static GString*
+add_selection_filter (NautilusTagManager *self,
+ GString *query)
+{
+ NautilusFile *file;
+ GList *l;
+ gchar *uri;
+
+ g_string_append (query, " FILTER(?url IN (");
+
+ for (l = self->selection; l != NULL; l = l->next)
+ {
+ file = l->data;
+
+ uri = nautilus_file_get_uri (file);
+
+ g_string_append_printf (query, "'%s'", uri);
+
+ if (l->next != NULL)
+ {
+ g_string_append (query, ", ");
+ }
+
+ g_free (uri);
+ }
+
+ g_string_append (query, "))");
+
+ return query;
+}
+
+static void
+start_query_or_update (NautilusTagManager *self,
+ GString *query,
+ GAsyncReadyCallback callback,
+ gboolean is_query,
+ GCancellable *cancellable)
+{
+ g_autoptr (GError) error = NULL;
+ TrackerSparqlConnection *connection;
+
+ connection = tracker_sparql_connection_get (cancellable, &error);
+ if (!connection)
+ {
+ if (error)
+ {
+ g_warning ("Error on getting connection: %s", error->message);
+ }
+
+ return;
+ }
+
+ if (is_query)
+ {
+ tracker_sparql_connection_query_async (connection,
+ query->str,
+ cancellable,
+ callback,
+ self);
+ }
+ else
+ {
+ tracker_sparql_connection_update_async (connection,
+ query->str,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ self);
+ }
+
+ g_object_unref (connection);
+}
+
+static void
+on_query_callback (GObject *object,
+ GAsyncResult *result,
+ NautilusTagManager *self,
+ GAsyncReadyCallback callback,
+ const gchar *signal_name,
+ gpointer signal_data,
+ GCancellable *cancellable)
+{
+ TrackerSparqlCursor *cursor;
+ g_autoptr (GError) error = NULL;
+ TrackerSparqlConnection *connection;
+
+ connection = TRACKER_SPARQL_CONNECTION (object);
+
+ cursor = tracker_sparql_connection_query_finish (connection,
+ result,
+ &error);
+
+ if (error != NULL)
+ {
+ g_warning ("Error on getting query callback: %s", error->message);
+
+ if (error->code != G_IO_ERROR_CANCELLED)
+ {
+ g_signal_emit_by_name (self, signal_name, signal_data);
+ }
+ }
+ else
+ {
+ tracker_sparql_cursor_next_async (cursor,
+ cancellable,
+ callback,
+ self);
+ }
+}
+
+static void
+on_update_callback (GObject *object,
+ GAsyncResult *result,
+ NautilusTagManager *self)
+{
+ TrackerSparqlConnection *connection;
+ GError *error;
+
+ error = NULL;
+
+ connection = TRACKER_SPARQL_CONNECTION (object);
+
+ tracker_sparql_connection_update_finish (connection, result, &error);
+
+ if (error == NULL ||
+ (error != NULL && error->code != G_IO_ERROR_CANCELLED))
+ {
+ g_signal_emit_by_name (self, "tags-updated", error);
+ }
+ else if (error && error->code == G_IO_ERROR_CANCELLED)
+ {
+ g_error_free (error);
+ }
+}
+
+static gboolean
+get_query_status (NautilusTagManager *self,
+ TrackerSparqlCursor *cursor,
+ GAsyncResult *result,
+ const gchar *signal_name,
+ gpointer signal_data)
+{
+ gboolean success;
+ g_autoptr (GError) error = NULL;
+
+ success = tracker_sparql_cursor_next_finish (cursor, result, &error);
+
+ if (!success)
+ {
+ if (error)
+ {
+ g_warning ("Error on getting all tags cursor callback: %s", error->message);
+ }
+
+ g_clear_object (&cursor);
+
+ if (error == NULL ||
+ (error != NULL && error->code != G_IO_ERROR_CANCELLED))
+ {
+ g_signal_emit_by_name (self, signal_name, signal_data);
+ }
+ }
+
+ return success;
+}
+
+static void
+on_get_all_tags_cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlCursor *cursor;
+ cursor = TRACKER_SPARQL_CURSOR (object);
+ const gchar *id;
+ const gchar *name;
+ gboolean success;
+ NautilusTagManager *self;
+ TagData *tag_data;
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ success = get_query_status (self, cursor, result, "all-tags", self->all_tags);
+ if (!success)
+ {
+ return;
+ }
+
+ id = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+ name = tracker_sparql_cursor_get_string (cursor, 1, NULL);
+
+ tag_data = nautilus_tag_data_new (id, name, NULL);
+
+ g_queue_push_tail (self->all_tags, tag_data);
+
+ tracker_sparql_cursor_next_async (cursor,
+ self->cancellable_all,
+ on_get_all_tags_cursor_callback,
+ self);
+}
+
+static void
+on_get_all_tags_query_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ NautilusTagManager *self;
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ on_query_callback (object,
+ result,
+ self,
+ on_get_all_tags_cursor_callback,
+ "all-tags",
+ self->all_tags,
+ self->cancellable_all);
+}
+
+void
+nautilus_tag_manager_get_all_tags (NautilusTagManager *self,
+ GCancellable *cancellable)
+{
+ GString *query;
+
+ if (self->all_tags)
+ {
+ g_queue_free_full (self->all_tags, nautilus_tag_data_free);
+ }
+ self->all_tags = g_queue_new ();
+
+ self->cancellable_all = cancellable;
+
+ query = g_string_new ("SELECT ?urn ?label WHERE { ?urn a nao:Tag ; nao:prefLabel ?label . } ORDER BY
?label");
+
+ g_print("\n\"%s\" \n", query->str);
+
+ start_query_or_update (self,
+ query,
+ on_get_all_tags_query_callback,
+ TRUE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+static void
+on_get_selection_tags_cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlCursor *cursor;
+ cursor = TRACKER_SPARQL_CURSOR (object);
+ const gchar *id;
+ const gchar *name;
+ const gchar *url;
+ gboolean success;
+ NautilusTagManager *self;
+ TagData *tag_data;
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ success = get_query_status (self, cursor, result, "selection-tags", self->selection_tags);
+ if (!success)
+ {
+ return;
+ }
+
+ id = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+ name = tracker_sparql_cursor_get_string (cursor, 1, NULL);
+ url = tracker_sparql_cursor_get_string (cursor, 2, NULL);
+
+ tag_data = nautilus_tag_data_new (id, name, url);
+
+ g_queue_push_tail (self->selection_tags, tag_data);
+
+ tracker_sparql_cursor_next_async (cursor,
+ self->cancellable_selection,
+ on_get_selection_tags_cursor_callback,
+ self);
+}
+
+static void
+on_get_selection_tags_query_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ NautilusTagManager *self;
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ on_query_callback (object,
+ result,
+ self,
+ on_get_selection_tags_cursor_callback,
+ "selection-tags",
+ self->selection_tags,
+ self->cancellable_selection);
+}
+
+void
+nautilus_tag_manager_get_selection_tags (NautilusTagManager *self,
+ GCancellable *cancellable)
+{
+ GString *query;
+
+ if (self->selection_tags)
+ {
+ g_queue_free_full (self->selection_tags, nautilus_tag_data_free);
+ }
+ self->selection_tags = g_queue_new ();
+
+ self->cancellable_selection = cancellable;
+
+ query = g_string_new ("SELECT ?tag nao:prefLabel(?tag) ?url WHERE"
+ "{ ?urn a nfo:FileDataObject ; nao:hasTag ?tag ; nie:url ?url .");
+
+ query = add_selection_filter (self, query);
+
+ g_string_append (query, "} ORDER BY (?tag)");
+ g_print("\n\"%s\" \n", query->str);
+
+ start_query_or_update (self,
+ query,
+ on_get_selection_tags_query_callback,
+ TRUE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+static void
+on_get_files_with_tag_cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlCursor *cursor;
+ cursor = TRACKER_SPARQL_CURSOR (object);
+ gchar *url;
+ gboolean success;
+ NautilusTagManager *self;
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ success = get_query_status (self, cursor, result, "files-with-tag", self->files_with_tag);
+ if (!success)
+ {
+ return;
+ }
+
+ url = g_strdup (tracker_sparql_cursor_get_string (cursor, 0, NULL));
+ g_queue_push_tail (self->files_with_tag, url);
+
+ tracker_sparql_cursor_next_async (cursor,
+ self->cancellable_get_files,
+ on_get_files_with_tag_cursor_callback,
+ self);
+}
+
+static void
+on_get_files_with_tag_query_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ NautilusTagManager *self;
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ on_query_callback (object,
+ result,
+ self,
+ on_get_files_with_tag_cursor_callback,
+ "files-with-tag",
+ self->files_with_tag,
+ self->cancellable_get_files);
+}
+
+void
+nautilus_tag_manager_get_files_with_tag (NautilusTagManager *self,
+ const gchar *tag_name,
+ GCancellable *cancellable)
+{
+ GString *query;
+
+ self->cancellable_get_files = cancellable;
+
+ if (self->files_with_tag)
+ {
+ g_queue_free_full (self->files_with_tag, g_free);
+ }
+ self->files_with_tag = g_queue_new ();
+
+ if (!nautilus_tag_queue_has_tag (self->all_tags, tag_name))
+ {
+ g_signal_emit_by_name (self, "files-with-tag", NULL);
+
+ return;
+ }
+
+ query = g_string_new ("SELECT ?url WHERE { ?urn a nfo:FileDataObject ;"
+ " nie:url ?url ; nao:hasTag ?tag . ");
+
+ g_string_append_printf (query, "?tag nao:prefLabel '%s' }", tag_name);
+
+ g_print ("\n\"%s\"\n", query->str);
+
+ start_query_or_update (self,
+ query,
+ on_get_files_with_tag_query_callback,
+ TRUE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+static void
+on_update_tag_query_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ NautilusTagManager *self;
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ on_update_callback (object,
+ result,
+ self);
+}
+
+void
+nautilus_tag_manager_remove_tag (NautilusTagManager *self,
+ const gchar *tag_name,
+ GCancellable *cancellable)
+{
+ GString *query;
+ GError *error;
+ GList *l;
+ TagData *tag_data;
+
+ if (!nautilus_tag_queue_has_tag (self->all_tags, tag_name))
+ {
+ error = g_error_new (G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "this tag doesn't exist");
+
+ g_signal_emit_by_name (self, "tags-updated", error);
+
+ return;
+ }
+
+ query = g_string_new ("");
+ g_string_append_printf (query,
+ "DELETE { ?urn nao:hasTag ?label } "
+ "WHERE { ?urn nie:url ?f . "
+ "?label nao:prefLabel '%s' }\n",
+ tag_name);
+
+ for (l = g_queue_peek_head_link (self->all_tags); l != NULL; l = l->next)
+ {
+ tag_data = l->data;
+
+ if (g_strcmp0 (tag_name, tag_data->name) == 0)
+ {
+ g_string_append_printf (query,
+ "DELETE { <%s> a rdfs:Resource }",
+ tag_data->id);
+
+ break;
+ }
+ }
+
+ g_print ("\n\"%s\"\n", query->str);
+
+ start_query_or_update (self,
+ query,
+ on_update_tag_query_callback,
+ FALSE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+static GString*
+nautilus_tag_manager_delete_tag (NautilusTagManager *self,
+ GString *query,
+ TagData *tag_data)
+{
+ g_string_append (query,
+ "DELETE { ?urn nao:hasTag ?label } "
+ "WHERE { ?urn a nfo:FileDataObject ; nie:url ?url . ");
+
+ g_string_append_printf (query,
+ "?label nao:prefLabel '%s' . ",
+ tag_data->name);
+
+ query = add_selection_filter (self, query);
+
+ g_string_append (query, "}\n");
+
+ return query;
+}
+
+static GString*
+nautilus_tag_manager_insert_tag (NautilusTagManager *self,
+ GString *query,
+ TagData *tag_data)
+{
+ g_autofree gchar *tag_color = NULL;
+
+ tag_color = parse_color_from_tag_id (tag_data->id);
+
+ if (!nautilus_tag_queue_has_tag (self->all_tags, tag_data->name))
+ {
+ g_string_append_printf (query,
+ "INSERT DATA { <org:gnome:nautilus:tag:%s:%s> a nao:Tag ; nao:prefLabel '%s'
}\n",
+ tag_data->name,
+ tag_color,
+ tag_data->name);
+ }
+
+ query = g_string_append (query,
+ "INSERT { ?urn nao:hasTag ?label }"
+ "WHERE { ?urn a nfo:FileDataObject ; nie:url ?url . ");
+
+ g_string_append_printf (query, "?label nao:prefLabel '%s'", tag_data->name);
+
+ query = add_selection_filter (self, query);
+
+ g_string_append (query, "}\n");
+
+ //g_print ("\n\"%s\"\n", query->str);
+
+ return query;
+}
+
+void
+nautilus_tag_manager_update_tags (NautilusTagManager *self,
+ GList *selection,
+ GQueue *selection_tags,
+ GQueue *all_tags,
+ GQueue *new_selection_tags,
+ GCancellable *cancellable)
+{
+ GString *query;
+ GList *l;
+ TagData *tag_data;
+ gchar *current_tag_name;
+
+ query = g_string_new ("");
+
+ g_print ("\nTAGS TO INSERT:\n");
+ for (l = g_queue_peek_head_link (new_selection_tags); l != NULL; l = l->next)
+ {
+ tag_data = l->data;
+
+ if (!nautilus_tag_queue_has_tag (selection_tags, tag_data->name))
+ {
+ g_print ("%s\n", tag_data->name);
+ query = nautilus_tag_manager_insert_tag (self, query, tag_data);
+ }
+ }
+
+ g_print ("\nTAGS TO DELETE:\n");
+ for (l = g_queue_peek_head_link (selection_tags); l != NULL; l = l->next)
+ {
+ tag_data = l->data;
+
+ if (!nautilus_tag_queue_has_tag (new_selection_tags, tag_data->name) &&
+ tag_data->name != NULL)
+ {
+ g_print ("%s\n", tag_data->name);
+ query = nautilus_tag_manager_delete_tag (self, query, tag_data);
+ }
+
+ current_tag_name = tag_data->name;
+ while (TRUE)
+ {
+ if (l->next == NULL)
+ {
+ break;
+ }
+
+ tag_data = l->next->data;
+ if (g_strcmp0 (tag_data->name, current_tag_name) == 0)
+ {
+ l = l->next;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ g_print ("\n%s\n", query->str);
+
+ start_query_or_update (self,
+ query,
+ on_update_tag_query_callback,
+ FALSE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+static void
+nautilus_tag_manager_finalize (GObject *object)
+{
+ NautilusTagManager *self;
+
+ self = NAUTILUS_TAG_MANAGER (object);
+
+ if (self->all_tags)
+ {
+ g_queue_free_full (self->all_tags, nautilus_tag_data_free);
+ }
+ if (self->selection_tags)
+ {
+ g_queue_free_full (self->selection_tags, nautilus_tag_data_free);
+ }
+ if (self->files_with_tag)
+ {
+ g_queue_free_full (self->files_with_tag, g_free);
+ }
+
+ G_OBJECT_CLASS (nautilus_tag_manager_parent_class)->finalize (object);
+}
+
+static void
+nautilus_tag_manager_class_init (NautilusTagManagerClass *klass)
+{
+ GObjectClass *oclass;
+
+ oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = nautilus_tag_manager_finalize;
+
+ signals[ALL_TAGS] = g_signal_new ("all-tags",
+ NAUTILUS_TYPE_TAG_MANAGER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_POINTER);
+
+ signals[SELECTION_TAGS] = g_signal_new ("selection-tags",
+ NAUTILUS_TYPE_TAG_MANAGER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_POINTER);
+
+ signals[TAGS_UPDATED] = g_signal_new ("tags-updated",
+ NAUTILUS_TYPE_TAG_MANAGER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_POINTER);
+
+ signals[FILES_WITH_TAG] = g_signal_new ("files-with-tag",
+ NAUTILUS_TYPE_TAG_MANAGER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_POINTER);
+}
+
+NautilusTagManager* nautilus_tag_manager_new (GList *selection)
+{
+ NautilusTagManager *self;
+
+ self = g_object_new (NAUTILUS_TYPE_TAG_MANAGER, NULL);
+
+ self->selection = selection;
+
+ return self;
+}
+
+static void
+nautilus_tag_manager_init (NautilusTagManager *self)
+{
+}
diff --git a/src/nautilus-tag-manager.h b/src/nautilus-tag-manager.h
new file mode 100644
index 0000000..67a2124
--- /dev/null
+++ b/src/nautilus-tag-manager.h
@@ -0,0 +1,77 @@
+/* nautilus-tag-manager.h
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru pandelea gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NAUTILUS_TAG_MANAGER_H
+#define NAUTILUS_TAG_MANAGER_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_TAG_MANAGER (nautilus_tag_manager_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusTagManager, nautilus_tag_manager, NAUTILUS, TAG_MANAGER, GObject);
+
+typedef struct
+{
+ gchar *id;
+ gchar *name;
+ gchar *url;
+} TagData;
+
+NautilusTagManager* nautilus_tag_manager_new (GList *selection);
+
+void nautilus_tag_manager_get_all_tags (NautilusTagManager *self,
+ GCancellable *cancellable);
+
+void nautilus_tag_manager_get_selection_tags (NautilusTagManager *self,
+ GCancellable *cancellable);
+
+void nautilus_tag_manager_get_files_with_tag (NautilusTagManager *self,
+ const gchar *tag_name,
+ GCancellable *cancellable);
+
+void nautilus_tag_manager_remove_tag (NautilusTagManager *self,
+ const gchar *tag_name,
+ GCancellable *cancellable);
+
+void nautilus_tag_manager_update_tags (NautilusTagManager *self,
+ GList *selection,
+ GQueue *selection_tags,
+ GQueue *all_tags,
+ GQueue *new_selection_tags,
+ GCancellable *cancellable);
+
+GQueue* nautilus_tag_copy_tag_queue (GQueue *queue);
+
+gboolean nautilus_tag_queue_has_tag (GQueue *selection_tags,
+ const gchar *tag_name);
+
+void nautilus_tag_data_free (gpointer data);
+
+gchar* parse_color_from_tag_id (const gchar *tag_id);
+
+TagData* nautilus_tag_data_new (const gchar *id,
+ const gchar *name,
+ const gchar *url);
+
+G_END_DECLS
+
+#endif
\ No newline at end of file
diff --git a/src/nautilus-tag-widget.c b/src/nautilus-tag-widget.c
new file mode 100644
index 0000000..73893ce
--- /dev/null
+++ b/src/nautilus-tag-widget.c
@@ -0,0 +1,304 @@
+/* nautilus-tag-widget.c
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru pandelea gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nautilus-tag-widget.h"
+#include "nautilus-tag-manager.h"
+
+struct _NautilusTagWidget
+{
+ GtkEventBox parent;
+
+ GtkWidget *box;
+ GtkWidget *label;
+ GtkWidget *circle;
+
+ gboolean cursor_over;
+
+ gchar *color;
+ TagData *tag_data;
+};
+
+G_DEFINE_TYPE (NautilusTagWidget, nautilus_tag_widget, GTK_TYPE_EVENT_BOX);
+
+GtkWidget*
+nautilus_tag_widget_queue_get_tag_with_name (GQueue *queue,
+ const gchar *tag_name)
+{
+ GList *l;
+ NautilusTagWidget *tag_widget;
+
+ for (l = g_queue_peek_head_link (queue); l != NULL; l = l->next)
+ {
+ tag_widget = NAUTILUS_TAG_WIDGET (l->data);
+
+ if (g_strcmp0 (tag_name, tag_widget->tag_data->name) == 0)
+ {
+ return GTK_WIDGET (tag_widget);
+ }
+ }
+
+ return NULL;
+}
+
+gboolean
+nautilus_tag_widget_queue_contains_tag (GQueue *queue,
+ const gchar *tag_name)
+{
+ GList *l;
+ NautilusTagWidget *tag_widget;
+
+ for (l = g_queue_peek_head_link (queue); l != NULL; l = l->next)
+ {
+ tag_widget = NAUTILUS_TAG_WIDGET (l->data);
+
+ if (g_strcmp0 (tag_name, tag_widget->tag_data->name) == 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+const gchar*
+nautilus_tag_widget_get_tag_name (NautilusTagWidget *self)
+{
+ return self->tag_data->name;
+}
+
+const gchar*
+nautilus_tag_widget_get_tag_id (NautilusTagWidget *self)
+{
+ return self->tag_data->id;
+}
+
+static gboolean
+paint_circle (GtkWidget *widget,
+ cairo_t *cr,
+ gpointer data)
+{
+ guint width, height;
+ GdkRGBA color;
+ GtkStyleContext *context;
+ NautilusTagWidget *tag_widget;
+ /*GtkIconInfo *info;
+ GdkPixbuf *pixbuf;
+ gint icon_size;
+ gint scale_factor;*/
+
+ tag_widget = NAUTILUS_TAG_WIDGET (data);
+
+ context = gtk_widget_get_style_context (widget);
+
+ width = gtk_widget_get_allocated_width (widget);
+ height = gtk_widget_get_allocated_height (widget);
+
+ gtk_render_background (context, cr, 0, 0, width, height);
+
+ cairo_arc (cr,
+ width / 2.0, height / 2.0,
+ MIN (width, height) / 2.0,
+ 0, 2 * G_PI);
+
+ gtk_style_context_get_color (context,
+ gtk_style_context_get_state (context),
+ &color);
+
+
+ gdk_rgba_parse (&color, tag_widget->color);
+
+ gdk_cairo_set_source_rgba (cr, &color);
+
+ cairo_fill (cr);
+
+ if (tag_widget->cursor_over)
+ {
+
+ /*gtk_icon_size_lookup (GTK_ICON_SIZE_MENU,
+ &icon_size, NULL);
+ scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (tag_widget->label));
+
+ info = gtk_icon_theme_lookup_icon_for_scale (gtk_icon_theme_get_default (),
+ "window-close-symbolic",
+ icon_size, scale_factor,
+ GTK_ICON_LOOKUP_GENERIC_FALLBACK);
+
+ pixbuf = gtk_icon_info_load_symbolic_for_context (info, context, NULL, NULL);
+ g_print("%d %d\n", width, height);
+ width = 0;
+ height = 0;
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, width / 2, height / 2);
+
+ cairo_paint (cr);
+
+ g_object_unref (info);
+ g_object_unref (pixbuf);*/
+
+ gtk_style_context_get_color (context,
+ gtk_style_context_get_state (context),
+ &color);
+
+ gdk_cairo_set_source_rgba (cr, &color);
+
+ cairo_move_to (cr,
+ (MIN (width, height) - MIN (width, height) * 0.25) / 2.0 * G_SQRT2 / 2 * -1 + width /
2.0,
+ (MIN (width, height) - MIN (width, height) * 0.25) / 2.0 * G_SQRT2 / 2 + height /
2.0);
+ cairo_line_to (cr,
+ (MIN (width, height) - MIN (width, height) * 0.25) / 2.0 * G_SQRT2 / 2 + width / 2.0,
+ (MIN (width, height) - MIN (width, height) * 0.25) / 2.0 * G_SQRT2 / 2 * -1 + height
/ 2.0);
+
+ cairo_stroke (cr);
+
+ cairo_move_to (cr,
+ (MIN (width, height) - MIN (width, height) * 0.25) / 2.0 * G_SQRT2 / 2 + width / 2.0,
+ (MIN (width, height) - MIN (width, height) * 0.25) / 2.0 * G_SQRT2 / 2 + height /
2.0);
+ cairo_line_to (cr,
+ (MIN (width, height) - MIN (width, height) * 0.25) / 2.0 * G_SQRT2 / 2 * -1 + width /
2.0,
+ (MIN (width, height) - MIN (width, height) * 0.25) / 2.0 * G_SQRT2 / 2 * -1 + height
/ 2.0);
+
+ cairo_stroke (cr);
+ }
+
+ return FALSE;
+}
+
+gboolean
+on_leave_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ NautilusTagWidget *self;
+
+ self = NAUTILUS_TAG_WIDGET (widget);
+
+ if (self->cursor_over == TRUE)
+ {
+ gtk_widget_queue_draw (self->circle);
+
+ gtk_label_set_label (GTK_LABEL (self->label), self->tag_data->name);
+ }
+
+ self->cursor_over = FALSE;
+
+ return FALSE;
+}
+
+gboolean
+on_motion_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ NautilusTagWidget *self;
+ g_autofree gchar *markup = NULL;
+
+ self = NAUTILUS_TAG_WIDGET (widget);
+
+ if (self->cursor_over == FALSE)
+ {
+ gtk_widget_queue_draw (self->circle);
+
+ markup = g_markup_printf_escaped ("<u>%s</u>", self->tag_data->name);
+ gtk_label_set_label (GTK_LABEL (self->label), markup);
+ }
+
+ self->cursor_over = TRUE;
+
+ return FALSE;
+}
+
+
+static void
+nautilus_tag_widget_finalize (GObject *object)
+{
+ NautilusTagWidget *self;
+
+ self = NAUTILUS_TAG_WIDGET (object);
+
+ g_free (self->color);
+ nautilus_tag_data_free (self->tag_data);
+
+ G_OBJECT_CLASS (nautilus_tag_widget_parent_class)->finalize (object);
+}
+
+static void
+nautilus_tag_widget_class_init (NautilusTagWidgetClass *klass)
+{
+ //GtkWidgetClass *widget_class;
+ GObjectClass *oclass;
+
+ //widget_class = GTK_WIDGET_CLASS (klass);
+ oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = nautilus_tag_widget_finalize;
+}
+
+GtkWidget* nautilus_tag_widget_new (const gchar *tag_label,
+ const gchar *tag_id,
+ gboolean can_close)
+{
+ NautilusTagWidget *self;
+ self = g_object_new (NAUTILUS_TYPE_TAG_WIDGET,
+ NULL);
+
+ self->color = parse_color_from_tag_id (tag_id);
+
+ self->tag_data = nautilus_tag_data_new (tag_id, tag_label, NULL);
+
+ self->label = gtk_label_new (tag_label);
+ gtk_label_set_use_markup (GTK_LABEL (self->label), TRUE);
+
+ self->box = g_object_new (GTK_TYPE_BOX,
+ "orientation",
+ GTK_ORIENTATION_HORIZONTAL,
+ "spacing",
+ 5,
+ NULL);
+
+ gtk_widget_add_events (GTK_WIDGET (self), GDK_POINTER_MOTION_MASK);
+
+ if (can_close)
+ {
+ g_signal_connect (self,
+ "motion-notify-event",
+ G_CALLBACK (on_motion_event),
+ NULL);
+
+ g_signal_connect (self,
+ "leave-notify-event",
+ G_CALLBACK (on_leave_event),
+ NULL);
+ }
+
+ self->circle = gtk_drawing_area_new ();
+ gtk_widget_set_size_request (self->circle, 15, 15);
+ g_signal_connect (self->circle, "draw",
+ G_CALLBACK (paint_circle), self);
+
+ gtk_box_pack_start (GTK_BOX (self->box), self->circle, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (self->box), self->label, FALSE, FALSE, 0);
+
+ gtk_container_add (GTK_CONTAINER (self), self->box);
+
+ return GTK_WIDGET (self);
+}
+
+static void
+nautilus_tag_widget_init (NautilusTagWidget *self)
+{
+
+}
diff --git a/src/nautilus-tag-widget.h b/src/nautilus-tag-widget.h
new file mode 100644
index 0000000..01ea881
--- /dev/null
+++ b/src/nautilus-tag-widget.h
@@ -0,0 +1,51 @@
+/* nautilus-tag-widget.h
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru pandelea gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NAUTILUS_TAG_WIDGET_H
+#define NAUTILUS_TAG_WIDGET_H
+
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "nautilus-files-view.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_TAG_WIDGET (nautilus_tag_widget_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusTagWidget, nautilus_tag_widget, NAUTILUS, TAG_WIDGET, GtkEventBox);
+
+GtkWidget* nautilus_tag_widget_new (const gchar *tag_label,
+ const gchar *tag_id,
+ gboolean can_close);
+
+const gchar* nautilus_tag_widget_get_tag_name (NautilusTagWidget *self);
+
+const gchar* nautilus_tag_widget_get_tag_id (NautilusTagWidget *self);
+
+GtkWidget* nautilus_tag_widget_queue_get_tag_with_name (GQueue *queue,
+ const gchar *tag_name);
+
+gboolean nautilus_tag_widget_queue_contains_tag (GQueue *queue,
+ const gchar *tag_name);
+
+
+G_END_DECLS
+
+#endif
diff --git a/src/nautilus-tags-dialog.c b/src/nautilus-tags-dialog.c
new file mode 100644
index 0000000..861fb5c
--- /dev/null
+++ b/src/nautilus-tags-dialog.c
@@ -0,0 +1,698 @@
+/* nautilus-tags-dialog.c
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru pandelea gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nautilus-tags-dialog.h"
+#include "nautilus-tag-manager.h"
+#include "nautilus-tag-widget.h"
+
+typedef enum
+{
+ NO_ACTION,
+ UPDATE_TAGS
+} ChosenAction;
+
+struct _NautilusTagsDialog
+{
+ GtkDialog parent;
+
+ NautilusWindow *window;
+ GList *selection;
+
+ GtkWidget *cancel_button;
+ GtkWidget *update_tags_button;
+ GtkWidget *tags_entry;
+ GtkWidget *color_button;
+ GtkWidget *tags_listbox;
+ GtkWidget *selection_tags_box;
+ GtkWidget *add_tag_button;
+
+ NautilusTagManager *tag_manager;
+ GQueue *all_tags;
+ GQueue *selection_tags;
+ GQueue *new_selection_tags;
+
+ /* queues that represent all the tags */
+ GQueue *list_box_rows;
+ GQueue *all_tags_widgets;
+ /* initilay it has tags widgets that the selection
+ * has, but updates when a tag is removed/added */
+ GQueue *selection_tags_widgets;
+
+ ChosenAction action;
+
+ GCancellable *cancellable_all;
+ GCancellable *cancellable_selection;
+ GCancellable *cancellable_update;
+ GCancellable *cancellable_get_files;
+};
+
+G_DEFINE_TYPE (NautilusTagsDialog, nautilus_tags_dialog, GTK_TYPE_DIALOG);
+
+static void
+update_tags (NautilusTagsDialog *dialog)
+{
+ nautilus_tag_manager_get_all_tags (dialog->tag_manager, dialog->cancellable_all);
+}
+
+static gint
+get_tag_position_in_listbox (NautilusTagsDialog *dialog,
+ const gchar *tag_name)
+{
+ GList *l;
+ gint index = 0;
+ g_autofree gchar *casefold_tag_name = NULL;
+ gchar *casefold_widget_tag_name;
+
+ casefold_tag_name = g_utf8_casefold (tag_name, -1);
+
+ for (l = g_queue_peek_head_link (dialog->all_tags_widgets), index = 0;
+ l != NULL;
+ l = l->next, index++)
+ {
+ // g_print ("compare %s with %s\n", tag_name, nautilus_tag_widget_get_tag_name (NAUTILUS_TAG_WIDGET
(l->data)));
+
+ casefold_widget_tag_name = g_utf8_casefold (nautilus_tag_widget_get_tag_name (NAUTILUS_TAG_WIDGET
(l->data)), -1);
+
+ if (g_strcmp0 (casefold_tag_name, casefold_widget_tag_name) < 0)
+ {
+ g_free (casefold_widget_tag_name);
+
+ return index;
+ }
+
+ g_free (casefold_widget_tag_name);
+ }
+
+ return index;
+}
+
+static gboolean
+on_tag_widget_button_press_event (GtkWidget *old_tag_widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ NautilusTagsDialog *dialog;
+ gboolean got_button;
+ guint button;
+ GtkWidget *row;
+ GtkWidget *tag_widget;
+ gint index;
+
+ dialog = NAUTILUS_TAGS_DIALOG (user_data);
+
+ got_button = gdk_event_get_button (event, &button);
+
+ if (got_button && button == GDK_BUTTON_PRIMARY)
+ {
+ if (nautilus_tag_queue_has_tag (dialog->all_tags,
+ nautilus_tag_widget_get_tag_name (NAUTILUS_TAG_WIDGET (old_tag_widget))))
+ {
+ row = gtk_list_box_row_new ();
+
+ tag_widget = nautilus_tag_widget_new (nautilus_tag_widget_get_tag_name (NAUTILUS_TAG_WIDGET
(old_tag_widget)),
+ nautilus_tag_widget_get_tag_id (NAUTILUS_TAG_WIDGET
(old_tag_widget)),
+ FALSE);
+
+ g_queue_push_tail (dialog->list_box_rows, row);
+
+ gtk_container_add (GTK_CONTAINER (row), tag_widget);
+ gtk_widget_show_all (row);
+
+ index = get_tag_position_in_listbox (dialog,
+ nautilus_tag_widget_get_tag_name (NAUTILUS_TAG_WIDGET
(old_tag_widget)));
+
+ gtk_list_box_insert (GTK_LIST_BOX (dialog->tags_listbox),
+ row,
+ index);
+
+ g_queue_push_nth (dialog->all_tags_widgets, tag_widget, index);
+ }
+
+ g_queue_remove (dialog->selection_tags_widgets, old_tag_widget);
+
+ gtk_widget_destroy (old_tag_widget);
+ }
+
+ return FALSE;
+}
+
+static void
+on_row_activated (GtkListBox *box,
+ GtkListBoxRow *row,
+ gpointer user_data)
+{
+ NautilusTagsDialog *dialog;
+
+ dialog = NAUTILUS_TAGS_DIALOG (user_data);
+
+ GtkWidget *old_tag_widget;
+ GtkWidget *tag_widget;
+
+ old_tag_widget = gtk_bin_get_child (GTK_BIN (row));
+
+ tag_widget = nautilus_tag_widget_new (nautilus_tag_widget_get_tag_name (NAUTILUS_TAG_WIDGET
(old_tag_widget)),
+ nautilus_tag_widget_get_tag_id (NAUTILUS_TAG_WIDGET
(old_tag_widget)),
+ TRUE);
+
+ g_signal_connect (tag_widget,
+ "button-press-event",
+ G_CALLBACK (on_tag_widget_button_press_event),
+ dialog);
+
+ gtk_container_add (GTK_CONTAINER (dialog->selection_tags_box), tag_widget);
+ gtk_widget_show_all (dialog->selection_tags_box);
+
+ g_queue_push_tail (dialog->selection_tags_widgets, tag_widget);
+
+ g_queue_remove (dialog->all_tags_widgets, old_tag_widget);
+ g_queue_remove (dialog->list_box_rows, row);
+ gtk_widget_destroy (GTK_WIDGET (row));
+
+ g_print ("activated row\n");
+}
+
+static void
+fill_selection_box (NautilusTagsDialog *dialog)
+{
+ GList *l;
+ GtkWidget *tag_widget;
+ TagData *tag_data;
+ gint index;
+ GtkListBoxRow *row;
+
+ dialog->selection_tags_widgets = g_queue_new ();
+
+ for (l = g_queue_peek_head_link (dialog->selection_tags); l != NULL; l = l->next)
+ {
+ tag_data = l->data;
+
+ if (nautilus_tag_widget_queue_contains_tag (dialog->selection_tags_widgets,
+ tag_data->name) || tag_data->name == NULL)
+ {
+ continue;
+ }
+
+ if (nautilus_tag_widget_queue_contains_tag (dialog->all_tags_widgets,
+ tag_data->name))
+ {
+ tag_widget = nautilus_tag_widget_queue_get_tag_with_name (dialog->all_tags_widgets,
+ tag_data->name);
+
+ index = g_queue_index (dialog->all_tags_widgets, tag_widget);
+ g_queue_remove (dialog->all_tags_widgets, tag_widget);
+
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX(dialog->tags_listbox), index);
+ g_queue_remove (dialog->list_box_rows, row);
+ gtk_widget_destroy (GTK_WIDGET (row));
+ }
+
+ tag_widget = nautilus_tag_widget_new (tag_data->name, tag_data->id, TRUE);
+
+ g_signal_connect (tag_widget,
+ "button-press-event",
+ G_CALLBACK (on_tag_widget_button_press_event),
+ dialog);
+
+ gtk_container_add (GTK_CONTAINER (dialog->selection_tags_box), tag_widget);
+
+ g_queue_push_tail (dialog->selection_tags_widgets, tag_widget);
+ }
+
+ gtk_widget_show_all (dialog->selection_tags_box);
+
+
+ if (!nautilus_tag_widget_queue_contains_tag (dialog->selection_tags_widgets,
+ gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry))) &&
+ g_strcmp0 (gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)), "") != 0)
+ {
+ gtk_widget_set_sensitive (dialog->add_tag_button, TRUE);
+ }
+}
+
+static void
+fill_list_box (NautilusTagsDialog *dialog)
+{
+ GList *l;
+ TagData *tag_data;
+ GtkWidget *row;
+ GtkWidget *tag_widget;
+
+ dialog->all_tags_widgets = g_queue_new ();
+
+ for (l = g_queue_peek_head_link (dialog->all_tags); l != NULL; l = l->next)
+ {
+ tag_data = l->data;
+
+ row = gtk_list_box_row_new ();
+
+ tag_widget = nautilus_tag_widget_new (tag_data->name, tag_data->id, FALSE);
+
+ g_queue_push_tail (dialog->list_box_rows, row);
+
+ gtk_container_add (GTK_CONTAINER (row), tag_widget);
+ gtk_widget_show_all (row);
+
+ gtk_container_add (GTK_CONTAINER (dialog->tags_listbox), row);
+
+ g_queue_push_tail (dialog->all_tags_widgets, tag_widget);
+ }
+}
+
+static void
+nautilus_tags_dialog_on_response (NautilusTagsDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ dialog->action = UPDATE_TAGS;
+
+ update_tags (dialog);
+ }
+ else
+ {
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ }
+}
+
+static void
+on_selection_tags_obtained (NautilusTagManager *tag,
+ gpointer user_data,
+ NautilusTagsDialog *dialog)
+{
+ GList *l;
+ TagData *tag_data;
+ GQueue *selection_tags_check;
+ GList *l_check;
+ TagData *tag_data_check;
+ g_autofree gchar *color_rgb = NULL;
+ NautilusTagWidget *tag_widget;
+
+ if (dialog->action == NO_ACTION)
+ {
+ dialog->selection_tags = nautilus_tag_copy_tag_queue (user_data);
+ g_print("\nselection tags:\n");
+
+ for (l = g_queue_peek_head_link (dialog->selection_tags); l != NULL; l = l->next)
+ {
+ tag_data = l->data;
+
+ g_print("%s %s %s\n", tag_data->id, tag_data->name, tag_data->url);
+ }
+
+ fill_selection_box (dialog);
+ }
+ else
+ {
+ selection_tags_check = user_data;
+
+ for (l = g_queue_peek_head_link (dialog->selection_tags), l_check = g_queue_peek_head_link
(selection_tags_check);
+ l != NULL && l_check != NULL; l = l->next, l_check = l_check->next)
+ {
+ tag_data = l->data;
+ tag_data_check = l_check->data;
+
+ if (g_strcmp0 (tag_data->id, tag_data_check->id) != 0 ||
+ g_strcmp0 (tag_data->name, tag_data_check->name) != 0)
+ {
+ g_print ("something changed with selection tags\n");
+
+ return;
+ }
+ }
+
+ dialog->new_selection_tags = g_queue_new ();
+
+ for (l = g_queue_peek_head_link (dialog->selection_tags_widgets); l != NULL; l = l->next)
+ {
+ tag_widget = NAUTILUS_TAG_WIDGET (l->data);
+
+ tag_data = nautilus_tag_data_new (nautilus_tag_widget_get_tag_id (tag_widget),
+ nautilus_tag_widget_get_tag_name (tag_widget),
+ NULL);
+
+ g_queue_push_tail (dialog->new_selection_tags, tag_data);
+ }
+
+ nautilus_tag_manager_update_tags (dialog->tag_manager,
+ dialog->selection,
+ dialog->selection_tags,
+ dialog->all_tags,
+ dialog->new_selection_tags,
+ dialog->cancellable_update);
+
+ /*if (dialog->action == ADD_TAG)
+ {
+ gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (dialog->color_button), &color);
+ color_rgb = gdk_rgba_to_string (&color);
+
+ nautilus_tag_manager_insert_tag (dialog->tag_manager,
+ gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)),
+ color_rgb,
+ dialog->cancellable_update);
+ }
+ else if (dialog->action == DELETE_TAG)
+ {
+ nautilus_tag_manager_delete_tag (dialog->tag_manager,
+ gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)),
+ dialog->cancellable_update);
+ }
+ else if (dialog->action == REMOVE_TAG)
+ {
+ nautilus_tag_manager_remove_tag (dialog->tag_manager,
+ gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)),
+ dialog->cancellable_update);
+ }*/
+ }
+}
+
+static void
+on_tags_updated (NautilusTagManager *tag,
+ gpointer user_data,
+ NautilusTagsDialog *dialog)
+{
+ GError *error;
+
+ error = user_data;
+
+ if (error != NULL)
+ {
+ g_print ("something went wrong: %s\n", error->message);
+
+ g_error_free (error);
+ }
+ else
+ {
+ g_print ("tags updated succesfully!\n");
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+}
+
+static void
+on_all_tags_obtained (NautilusTagManager *tag,
+ gpointer user_data,
+ NautilusTagsDialog *dialog)
+{
+ GList *l;
+ TagData *tag_data;
+ GQueue *all_tags_check;
+ GList *l_check;
+ TagData *tag_data_check;
+
+ /* only for testing */
+ nautilus_tag_manager_get_files_with_tag (dialog->tag_manager, "demo", dialog->cancellable_get_files);
+
+ if (dialog->action == NO_ACTION)
+ {
+ dialog->all_tags = nautilus_tag_copy_tag_queue (user_data);
+
+ g_print("\n\nall tags\n");
+
+ for (l = g_queue_peek_head_link (dialog->all_tags); l != NULL; l = l->next)
+ {
+ tag_data = l->data;
+
+ g_print("%s %s\n", tag_data->id, tag_data->name);
+ }
+
+ fill_list_box (dialog);
+
+ nautilus_tag_manager_get_selection_tags (dialog->tag_manager, dialog->cancellable_selection);
+ }
+ else
+ {
+ all_tags_check = user_data;
+
+ for (l = g_queue_peek_head_link (dialog->all_tags), l_check = g_queue_peek_head_link
(all_tags_check);
+ l != NULL && l_check != NULL; l = l->next, l_check = l_check->next)
+ {
+ tag_data = l->data;
+ tag_data_check = l_check->data;
+
+ if (g_strcmp0 (tag_data->id, tag_data_check->id) != 0 ||
+ g_strcmp0 (tag_data->name, tag_data_check->name) != 0)
+ {
+ g_print ("something changed with all tags\n");
+
+ return;
+ }
+ }
+
+ nautilus_tag_manager_get_selection_tags (dialog->tag_manager, dialog->cancellable_selection);
+ }
+}
+
+static void
+on_files_with_tag_obtained (NautilusTagManager *tag,
+ gpointer user_data,
+ NautilusTagsDialog *dialog)
+{
+ GList *l;
+ GQueue *files_with_tag;
+
+ files_with_tag = user_data;
+
+ g_print ("\nthe following files have the tag 'demo':\n");
+ for (l = g_queue_peek_head_link (files_with_tag); l != NULL; l = l->next)
+ {
+ g_print ("%s\n", (char*)l->data);
+ }
+}
+
+static void
+on_tags_entry_activate (NautilusTagsDialog *dialog)
+{
+ g_print ("\n%s\n", gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)));
+
+ if (gtk_widget_get_sensitive (dialog->add_tag_button))
+ {
+ g_signal_emit_by_name (dialog->add_tag_button, "clicked", dialog);
+ }
+}
+
+static void
+on_tags_entry_changed (NautilusTagsDialog *dialog)
+{
+ //check if selection has this tag and update add button acordingly
+
+ if (nautilus_tag_widget_queue_contains_tag (dialog->selection_tags_widgets,
+ gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry))) ||
+ g_strcmp0 (gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)), "") == 0 ||
+ dialog->selection_tags_widgets == NULL)
+ {
+ gtk_widget_set_sensitive (dialog->add_tag_button, FALSE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (dialog->add_tag_button, TRUE);
+ }
+}
+
+static void
+on_add_button_clicked (GtkButton *button,
+ gpointer user_data)
+{
+ NautilusTagsDialog *dialog;
+ g_autofree gchar *tag_id = NULL;
+ g_autofree gchar *color_rgb = NULL;
+ const gchar *const_tag_id;
+ GdkRGBA color;
+ GtkWidget *tag_widget;
+ GtkWidget *old_tag_widget;
+ GtkListBoxRow *row;
+ gint index;
+
+ dialog = NAUTILUS_TAGS_DIALOG (user_data);
+ g_print ("add button clicked\n");
+
+ //if tag exists but it's not in selection: move from listbox to box
+ if (nautilus_tag_queue_has_tag (dialog->all_tags,
+ gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry))))
+ {
+ old_tag_widget = nautilus_tag_widget_queue_get_tag_with_name (dialog->all_tags_widgets,
+ gtk_entry_get_text (GTK_ENTRY
(dialog->tags_entry)));
+
+ const_tag_id = nautilus_tag_widget_get_tag_id (NAUTILUS_TAG_WIDGET (old_tag_widget));
+
+ tag_widget = nautilus_tag_widget_new (gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)),
+ const_tag_id,
+ TRUE);
+
+ index = g_queue_index (dialog->all_tags_widgets, old_tag_widget);
+ g_queue_remove (dialog->all_tags_widgets, old_tag_widget);
+
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX(dialog->tags_listbox), index);
+ g_queue_remove (dialog->list_box_rows, row);
+ gtk_widget_destroy (GTK_WIDGET (row));
+ }
+ else
+ {
+ //if tag doesn't exist and it's not in selection: create new widget for box
+ gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (dialog->color_button), &color);
+ color_rgb = gdk_rgba_to_string (&color);
+ tag_id = g_strdup_printf ("org:gnome:nautilus:tag:%s:%s",
+ gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)),
+ color_rgb);
+
+ tag_widget = nautilus_tag_widget_new (gtk_entry_get_text (GTK_ENTRY (dialog->tags_entry)), tag_id,
TRUE);
+ }
+
+ g_signal_connect (tag_widget,
+ "button-press-event",
+ G_CALLBACK (on_tag_widget_button_press_event),
+ dialog);
+
+ gtk_container_add (GTK_CONTAINER (dialog->selection_tags_box), tag_widget);
+
+ g_queue_push_tail (dialog->selection_tags_widgets, tag_widget);
+
+ gtk_widget_show_all (dialog->selection_tags_box);
+
+ gtk_entry_set_text (GTK_ENTRY (dialog->tags_entry), "");
+}
+
+static void
+nautilus_tags_dialog_finalize (GObject *object)
+{
+ NautilusTagsDialog *dialog;
+
+ dialog = NAUTILUS_TAGS_DIALOG (object);
+
+ g_cancellable_cancel (dialog->cancellable_all);
+ g_clear_object (&dialog->cancellable_all);
+
+ g_cancellable_cancel (dialog->cancellable_selection);
+ g_clear_object (&dialog->cancellable_selection);
+
+ g_cancellable_cancel (dialog->cancellable_update);
+ g_clear_object (&dialog->cancellable_update);
+
+ g_cancellable_cancel (dialog->cancellable_get_files);
+ g_clear_object (&dialog->cancellable_get_files);
+
+ if (dialog->all_tags)
+ {
+ g_queue_free_full (dialog->all_tags, nautilus_tag_data_free);
+ }
+ if (dialog->selection_tags)
+ {
+ g_queue_free_full (dialog->selection_tags, nautilus_tag_data_free);
+ }
+ if (dialog->new_selection_tags)
+ {
+ g_queue_free_full (dialog->new_selection_tags, nautilus_tag_data_free);
+ }
+ if (dialog->selection_tags_widgets)
+ {
+ g_queue_free (dialog->selection_tags_widgets);
+ }
+ if (dialog->all_tags_widgets)
+ {
+ g_queue_free (dialog->all_tags_widgets);
+ }
+ if (dialog->list_box_rows)
+ {
+ g_queue_free (dialog->list_box_rows);
+ }
+
+ g_signal_handlers_disconnect_by_func (dialog->tag_manager, on_selection_tags_obtained, dialog);
+ g_signal_handlers_disconnect_by_func (dialog->tag_manager, on_all_tags_obtained, dialog);
+ g_signal_handlers_disconnect_by_func (dialog->tag_manager, on_tags_updated, dialog);
+ g_signal_handlers_disconnect_by_func (dialog->tag_manager, on_files_with_tag_obtained, dialog);
+
+ g_object_unref (dialog->tag_manager);
+
+ G_OBJECT_CLASS (nautilus_tags_dialog_parent_class)->finalize (object);
+}
+
+static void
+nautilus_tags_dialog_class_init (NautilusTagsDialogClass *klass)
+{
+ GtkWidgetClass *widget_class;
+ GObjectClass *oclass;
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+ oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = nautilus_tags_dialog_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/nautilus/ui/nautilus-tags-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, cancel_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, update_tags_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, tags_entry);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, color_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, tags_listbox);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, selection_tags_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, add_tag_button);
+
+ gtk_widget_class_bind_template_callback (widget_class, nautilus_tags_dialog_on_response);
+ gtk_widget_class_bind_template_callback (widget_class, on_tags_entry_activate);
+ gtk_widget_class_bind_template_callback (widget_class, on_tags_entry_changed);
+ gtk_widget_class_bind_template_callback (widget_class, on_add_button_clicked);
+}
+
+GtkWidget* nautilus_tags_dialog_new (GList *selection,
+ NautilusWindow *window)
+{
+ NautilusTagsDialog *dialog;
+
+ dialog = g_object_new (NAUTILUS_TYPE_TAGS_DIALOG, "use-header-bar", TRUE, NULL);
+
+ dialog->window = window;
+ dialog->selection = selection;
+
+ gtk_window_set_transient_for (GTK_WINDOW (dialog),
+ GTK_WINDOW (window));
+
+ dialog->tag_manager = nautilus_tag_manager_new (dialog->selection);
+
+ g_signal_connect (dialog->tag_manager, "selection-tags", G_CALLBACK (on_selection_tags_obtained),
dialog);
+ g_signal_connect (dialog->tag_manager, "all-tags", G_CALLBACK (on_all_tags_obtained), dialog);
+ g_signal_connect (dialog->tag_manager, "tags-updated", G_CALLBACK (on_tags_updated), dialog);
+ /*the signal below is not for this dialog, it's here for testing */
+ g_signal_connect (dialog->tag_manager, "files-with-tag", G_CALLBACK (on_files_with_tag_obtained),
dialog);
+
+ g_signal_connect (dialog->tags_listbox, "row-activated", G_CALLBACK (on_row_activated), dialog);
+
+ dialog->cancellable_all = g_cancellable_new ();
+ dialog->cancellable_selection = g_cancellable_new ();
+ dialog->cancellable_update = g_cancellable_new ();
+
+ nautilus_tag_manager_get_all_tags (dialog->tag_manager, dialog->cancellable_all);
+
+ dialog->action = NO_ACTION;
+
+ return GTK_WIDGET (dialog);
+}
+
+static void
+nautilus_tags_dialog_init (NautilusTagsDialog *self)
+{
+ GdkRGBA color;
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->list_box_rows = g_queue_new ();
+
+ gdk_rgba_parse (&color, "rgb(220,220,220)");
+ gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (self->color_button),
+ &color);
+}
\ No newline at end of file
diff --git a/src/nautilus-tags-dialog.h b/src/nautilus-tags-dialog.h
new file mode 100644
index 0000000..1968bc6
--- /dev/null
+++ b/src/nautilus-tags-dialog.h
@@ -0,0 +1,39 @@
+/* nautilus-tags-dialog.h
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru pandelea gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NAUTILUS_TAGS_DIALOG_H
+#define NAUTILUS_TAGS_DIALOG_H
+
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "nautilus-files-view.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_TAGS_DIALOG (nautilus_tags_dialog_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusTagsDialog, nautilus_tags_dialog, NAUTILUS, TAGS_DIALOG, GtkDialog);
+
+GtkWidget* nautilus_tags_dialog_new (GList *selection,
+ NautilusWindow *window);
+
+G_END_DECLS
+
+#endif
\ No newline at end of file
diff --git a/src/resources/nautilus.gresource.xml b/src/resources/nautilus.gresource.xml
index 720bfa5..7f3a5e9 100644
--- a/src/resources/nautilus.gresource.xml
+++ b/src/resources/nautilus.gresource.xml
@@ -20,6 +20,7 @@
<file>ui/nautilus-trash-is-empty.ui</file>
<file>gtk/help-overlay.ui</file>
<file>ui/nautilus-batch-rename-dialog.ui</file>
+ <file>ui/nautilus-tags-dialog.ui</file>
<file alias="gtk/ui/nautilusgtkplacesview.ui">../gtk/nautilusgtkplacesview.ui</file>
<file alias="gtk/ui/nautilusgtkplacesviewrow.ui">../gtk/nautilusgtkplacesviewrow.ui</file>
<file alias="icons/thumbnail_frame.png">../../icons/thumbnail_frame.png</file>
diff --git a/src/resources/ui/nautilus-files-view-context-menus.ui
b/src/resources/ui/nautilus-files-view-context-menus.ui
index 4a53de4..409db20 100644
--- a/src/resources/ui/nautilus-files-view-context-menus.ui
+++ b/src/resources/ui/nautilus-files-view-context-menus.ui
@@ -253,6 +253,13 @@
</section>
<section>
<item>
+ <attribute name="label" translatable="yes">Tags</attribute>
+ <attribute name="action">view.edit-tags</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
<attribute name="label" translatable="yes">P_roperties</attribute>
<attribute name="action">view.properties</attribute>
</item>
diff --git a/src/resources/ui/nautilus-tags-dialog.ui b/src/resources/ui/nautilus-tags-dialog.ui
new file mode 100644
index 0000000..5139c7c
--- /dev/null
+++ b/src/resources/ui/nautilus-tags-dialog.ui
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="NautilusTagsDialog" parent="GtkDialog">
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <signal name="response" handler="nautilus_tags_dialog_on_response"/>
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="update_tags_button">
+ <property name="label" translatable="yes">_Update Tags</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="ok">update_tags_button</action-widget>
+ <action-widget response="cancel">cancel_button</action-widget>
+ </action-widgets>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="vbox">
+ <property name="border-width">0</property>
+ <child>
+ <object class="GtkGrid" id="grid">
+ <property name="visible">True</property>
+ <property name="margin">10</property>
+ <property name="row-spacing">6</property>
+ <property name="column-spacing">6</property>
+ <property name="hexpand">True</property>
+ <property name="row-homogeneous">False</property>
+ <property name="column-homogeneous">False</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled_tags">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">False</property>
+ <property name="vexpand">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="selection_tags_box">
+ <property name="orientation">horizontal</property>
+ <property name="visible">True</property>
+ <property name="spacing">5</property>
+ <property name="halign">start</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">0</property>
+ <property name="visible">True</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkEntry" id="tags_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="width_request">250</property>
+ <property name="hexpand">True</property>
+ <property name="activates-default">True</property>
+ <signal name="activate" handler="on_tags_entry_activate" swapped="yes" />
+ <signal name="changed" handler="on_tags_entry_changed" swapped="yes" />
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="add_tag_button">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="label" translatable="yes">Add</property>
+ <signal name="clicked" handler="on_add_button_clicked" swapped="no" />
+ </object>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">horizontal</property>
+ <child>
+ <object class="GtkLabel" id="color_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">New Tag Color: </property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="color_button">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled_window">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">False</property>
+ <property name="vexpand">True</property>
+ <property name="shadow_type">in</property>
+ <property name="max-content-height">200</property>
+ <property name="min-content-height">200</property>
+ <property name="max-content-width">150</property>
+ <property name="min-content-width">150</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkListBox" id="tags_listbox">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
\ No newline at end of file
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]