[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, 11 Jul 2017 15:03:55 +0000 (UTC)
commit 64514dbd87b1a287371740e7204f746632719b45
Author: Alexandru Pandelea <alexandru pandelea gmail com>
Date: Wed Jun 28 17:47:41 2017 +0100
implement tags
src/meson.build | 10 +-
src/nautilus-application.c | 25 +
src/nautilus-column-utilities.c | 9 +
src/nautilus-files-view.c | 22 +
src/nautilus-list-view-private.h | 4 +
src/nautilus-list-view.c | 156 +++-
src/nautilus-star-cell-renderer.c | 175 +++
src/nautilus-star-cell-renderer.h | 35 +
src/nautilus-tag-manager.c | 1130 ++++++++++++++++++++
src/nautilus-tag-manager.h | 105 ++
src/nautilus-tag-widget.c | 304 ++++++
src/nautilus-tag-widget.h | 51 +
src/nautilus-tags-dialog.c | 646 +++++++++++
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 +++
17 files changed, 2864 insertions(+), 12 deletions(-)
---
diff --git a/src/meson.build b/src/meson.build
index 83723b9..166f14e 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -264,7 +264,15 @@ if get_option ('enable-tracker')
'nautilus-batch-rename-utilities.c',
'nautilus-batch-rename-utilities.h',
'nautilus-search-engine-tracker.c',
- 'nautilus-search-engine-tracker.h']
+ 'nautilus-search-engine-tracker.h',
+ 'nautilus-tags-dialog.c',
+ 'nautilus-tags-dialog.h',
+ 'nautilus-tag-manager.c',
+ 'nautilus-tag-manager.h',
+ 'nautilus-tag-widget.c',
+ 'nautilus-tag-widget.h',
+ 'nautilus-star-cell-renderer.c',
+ 'nautilus-star-cell-renderer.h']
endif
nautilus_deps = [glib,
diff --git a/src/nautilus-application.c b/src/nautilus-application.c
index 77443f5..108319f 100644
--- a/src/nautilus-application.c
+++ b/src/nautilus-application.c
@@ -39,6 +39,7 @@
#include "nautilus-window.h"
#include "nautilus-window-slot.h"
#include "nautilus-preferences-window.h"
+#include "nautilus-tag-manager.h"
#include "nautilus-directory-private.h"
#include "nautilus-file-utilities.h"
@@ -80,6 +81,11 @@ typedef struct
GHashTable *notifications;
NautilusFileUndoManager *undo_manager;
+
+ NautilusTagManager *tag_manager;
+ GCancellable *tag_manager_tags_cancellable;
+ GCancellable *tag_manager_notifier_cancellable;
+ GCancellable *tag_manager_favorite_cancellable;
} NautilusApplicationPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (NautilusApplication, nautilus_application, GTK_TYPE_APPLICATION);
@@ -612,6 +618,17 @@ nautilus_application_finalize (GObject *object)
g_clear_object (&priv->undo_manager);
+ g_clear_object (&priv->tag_manager);
+
+ g_cancellable_cancel (priv->tag_manager_tags_cancellable);
+ g_clear_object (&priv->tag_manager_tags_cancellable);
+
+ g_cancellable_cancel (priv->tag_manager_notifier_cancellable);
+ g_clear_object (&priv->tag_manager_notifier_cancellable);
+
+ g_cancellable_cancel (priv->tag_manager_favorite_cancellable);
+ g_clear_object (&priv->tag_manager_favorite_cancellable);
+
G_OBJECT_CLASS (nautilus_application_parent_class)->finalize (object);
}
@@ -1077,6 +1094,14 @@ nautilus_application_init (NautilusApplication *self)
priv->undo_manager = nautilus_file_undo_manager_new ();
+ priv->tag_manager_tags_cancellable = g_cancellable_new ();
+ priv->tag_manager_notifier_cancellable = g_cancellable_new ();
+ priv->tag_manager_favorite_cancellable = g_cancellable_new ();
+
+ priv->tag_manager = nautilus_tag_manager_new (priv->tag_manager_tags_cancellable,
+ priv->tag_manager_notifier_cancellable,
+ priv->tag_manager_favorite_cancellable);
+
g_application_add_main_option_entries (G_APPLICATION (self), options);
nautilus_ensure_extension_points ();
diff --git a/src/nautilus-column-utilities.c b/src/nautilus-column-utilities.c
index 1a2a092..9def6b3 100644
--- a/src/nautilus-column-utilities.c
+++ b/src/nautilus-column-utilities.c
@@ -33,6 +33,7 @@ static const char *default_column_order[] =
"name",
"size",
"type",
+ "favorite",
"owner",
"group",
"permissions",
@@ -73,6 +74,14 @@ get_builtin_columns (void)
NULL));
columns = g_list_append (columns,
g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "favorite",
+ "attribute", "favorite",
+ "label", _("Favorite"),
+ "description", _("Shows if file is favorite."),
+ NULL));
+
+ columns = g_list_append (columns,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
"name", "date_modified",
"attribute", "date_modified",
"label", _("Modified"),
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-list-view-private.h b/src/nautilus-list-view-private.h
index e2a09a1..f0d1716 100644
--- a/src/nautilus-list-view-private.h
+++ b/src/nautilus-list-view-private.h
@@ -21,6 +21,7 @@
#include "nautilus-list-model.h"
#include "nautilus-tree-view-drag-dest.h"
#include "nautilus-dnd.h"
+#include "nautilus-tag-manager.h"
struct NautilusListViewDetails {
GtkTreeView *tree_view;
@@ -64,5 +65,8 @@ struct NautilusListViewDetails {
gulong clipboard_handler_id;
GQuark last_sort_attr;
+
+ NautilusTagManager *tag_manager;
+ GCancellable *favorite_cancellable;
};
diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c
index 14a074a..67ea48e 100644
--- a/src/nautilus-list-view.c
+++ b/src/nautilus-list-view.c
@@ -32,6 +32,8 @@
#include "nautilus-toolbar.h"
#include "nautilus-list-view-dnd.h"
#include "nautilus-view.h"
+#include "nautilus-star-cell-renderer.h"
+#include "nautilus-tag-manager.h"
#include <string.h>
#include <eel/eel-vfs-extensions.h>
@@ -1546,6 +1548,50 @@ apply_columns_settings (NautilusListView *list_view,
}
static void
+favorite_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ NautilusListView *view)
+{
+ g_autofree gchar *text = NULL;
+ g_autofree gchar *uri = NULL;
+ NautilusFile *file;
+
+ //if (nautilus_tag_manager_file_is_favorite (view->details->tag_manager, ))
+
+ gtk_tree_model_get (model, iter,
+ view->details->file_name_column_num, &text,
+ -1);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (model),
+ iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ //file = nautilus_list_model_file_for_path (list_model, path);
+
+ uri = nautilus_file_get_uri (file);
+ // g_print ("%s %d\n", nautilus_file_get_uri (file), nautilus_tag_manager_file_is_favorite
(view->details->tag_manager, uri));
+
+ if (nautilus_tag_manager_file_is_favorite (view->details->tag_manager, uri))
+ {
+ g_object_set (renderer,
+ "starred", TRUE,
+ NULL);
+ }
+ else
+ {
+ g_object_set (renderer,
+ "starred", FALSE,
+ NULL);
+
+ }
+
+ nautilus_file_unref (file);
+}
+
+static void
filename_cell_data_func (GtkTreeViewColumn *column,
GtkCellRenderer *renderer,
GtkTreeModel *model,
@@ -1833,6 +1879,62 @@ get_icon_scale_callback (NautilusListModel *model,
}
static void
+on_favorite_tags_updated (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ //g_print ("updated\n");
+}
+
+static void
+on_activate (NautilusStarCellRenderer *cell,
+ gpointer user_data,
+ NautilusListView *list_view)
+{
+ NautilusListModel *list_model;
+ const gchar *path_string;
+ NautilusFile *file;
+ GtkTreePath *path;
+ g_autofree gchar *uri = NULL;
+
+ path_string = user_data;
+
+ list_model = list_view->details->model;
+
+ path = gtk_tree_path_new_from_string (path_string);
+
+ file = nautilus_list_model_file_for_path (list_model, path);
+ uri = nautilus_file_get_uri (file);
+
+ g_print ("on activate %s\n", uri);
+
+ if (nautilus_tag_manager_file_is_favorite (list_view->details->tag_manager, uri))
+ {
+ g_print ("delete\n");
+
+ nautilus_tag_manager_update_favorite_tags (list_view->details->tag_manager,
+ G_OBJECT (list_view),
+ uri,
+ TRUE,
+ on_favorite_tags_updated,
+ list_view->details->favorite_cancellable);
+ }
+ else
+ {
+ g_print ("insert\n");
+
+ nautilus_tag_manager_update_favorite_tags (list_view->details->tag_manager,
+ G_OBJECT (list_view),
+ uri,
+ FALSE,
+ on_favorite_tags_updated,
+ list_view->details->favorite_cancellable);
+ }
+
+ nautilus_file_unref (file);
+}
+
+static void
create_and_set_up_tree_view (NautilusListView *view)
{
GtkCellRenderer *cell;
@@ -2029,13 +2131,36 @@ create_and_set_up_tree_view (NautilusListView *view)
}
else
{
- /* We need to use libgd */
- cell = gd_styled_text_renderer_new ();
- /* FIXME: should be just dim-label.
- * See https://bugzilla.gnome.org/show_bug.cgi?id=744397
- */
- gd_styled_text_renderer_add_class (GD_STYLED_TEXT_RENDERER (cell),
- "nautilus-list-dim-label");
+ if (!g_strcmp0 (name, "favorite"))
+ {
+ cell = nautilus_star_cell_renderer_new ();
+ g_object_set (cell,
+ "icon-name", "non-starred-symbolic",
+ "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE,
+ NULL);
+
+ g_signal_connect (cell, "clicked", G_CALLBACK (on_activate), view);
+
+ column = gtk_tree_view_column_new_with_attributes (label,
+ cell,
+ NULL);
+ }
+ else
+ {
+ /* We need to use libgd */
+ cell = gd_styled_text_renderer_new ();
+ /* FIXME: should be just dim-label.
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=744397
+ */
+ gd_styled_text_renderer_add_class (GD_STYLED_TEXT_RENDERER (cell),
+ "nautilus-list-dim-label");
+
+ column = gtk_tree_view_column_new_with_attributes (label,
+ cell,
+ "text", column_num,
+ NULL);
+ }
+
g_object_set (cell,
"xalign", xalign,
@@ -2049,10 +2174,7 @@ create_and_set_up_tree_view (NautilusListView *view)
}
view->details->cells = g_list_append (view->details->cells,
cell);
- column = gtk_tree_view_column_new_with_attributes (label,
- cell,
- "text", column_num,
- NULL);
+
gtk_tree_view_append_column (view->details->tree_view, column);
gtk_tree_view_column_set_sort_column_id (column, column_num);
g_hash_table_insert (view->details->columns,
@@ -2079,6 +2201,12 @@ create_and_set_up_tree_view (NautilusListView *view)
(GtkTreeCellDataFunc)
trash_orig_path_cell_data_func,
view, NULL);
}
+ else if (!strcmp (name, "favorite"))
+ {
+ gtk_tree_view_column_set_cell_data_func (column, cell,
+ (GtkTreeCellDataFunc) favorite_cell_data_func,
+ view, NULL);
+ }
}
g_free (name);
g_free (label);
@@ -3355,6 +3483,9 @@ nautilus_list_view_finalize (GObject *object)
g_free (list_view->details);
+ g_cancellable_cancel (list_view->details->favorite_cancellable);
+ g_clear_object (&list_view->details->favorite_cancellable);
+
G_OBJECT_CLASS (nautilus_list_view_parent_class)->finalize (object);
}
@@ -3637,6 +3768,9 @@ nautilus_list_view_init (NautilusListView *list_view)
/* Keep the action synced with the actual value, so the toolbar can poll it */
g_action_group_change_action_state (nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW
(list_view)),
"zoom-to-level", g_variant_new_int32 (get_default_zoom_level ()));
+
+ list_view->details->tag_manager = nautilus_tag_manager_new (NULL, NULL, NULL);
+ list_view->details->favorite_cancellable = g_cancellable_new ();
}
NautilusFilesView *
diff --git a/src/nautilus-star-cell-renderer.c b/src/nautilus-star-cell-renderer.c
new file mode 100644
index 0000000..9e1d09e
--- /dev/null
+++ b/src/nautilus-star-cell-renderer.c
@@ -0,0 +1,175 @@
+/* nautilus-star-cell-renderer.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-star-cell-renderer.h"
+
+struct _NautilusStarCellRenderer
+{
+ GtkCellRendererPixbuf parent;
+
+ gboolean starred;
+};
+
+G_DEFINE_TYPE (NautilusStarCellRenderer, nautilus_star_cell_renderer, GTK_TYPE_CELL_RENDERER_PIXBUF);
+
+enum
+{
+ CLICKED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_STARRED,
+ LAST_PROP
+};
+
+static guint signals[LAST_SIGNAL];
+
+gboolean
+nautilus_star_cell_renderer_activate (GtkCellRenderer *cell,
+ GdkEvent *event,
+ GtkWidget *widget,
+ const gchar *path,
+ const GdkRectangle *background_area,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ g_signal_emit_by_name (cell, "clicked", path);
+
+ return TRUE;
+}
+
+static void
+nautilus_star_cell_renderer_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusStarCellRenderer *self;
+
+ self = NAUTILUS_STAR_CELL_RENDERER (object);
+
+ switch (prop_id)
+ {
+ case PROP_STARRED:
+ {
+ g_value_set_boolean (value, self->starred);
+
+ break;
+ }
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+nautilus_star_cell_renderer_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusStarCellRenderer *self;
+
+ self = NAUTILUS_STAR_CELL_RENDERER (object);
+
+ switch (prop_id)
+ {
+ case PROP_STARRED:
+ {
+ self->starred = g_value_get_boolean (value);
+
+ if (self->starred)
+ {
+ g_object_set (self,
+ "icon-name", "starred-symbolic",
+ NULL);
+ }
+ else
+ {
+ g_object_set (self,
+ "icon-name", "non-starred-symbolic",
+ NULL);
+ }
+
+ break;
+ }
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+nautilus_star_cell_renderer_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (nautilus_star_cell_renderer_parent_class)->finalize (object);
+}
+
+static void
+nautilus_star_cell_renderer_class_init (NautilusStarCellRendererClass *klass)
+{
+ GObjectClass *oclass;
+ GtkCellRendererClass *rclass;
+
+ oclass = G_OBJECT_CLASS (klass);
+ rclass = GTK_CELL_RENDERER_CLASS (klass);
+
+ oclass->get_property = nautilus_star_cell_renderer_get_property;
+ oclass->set_property = nautilus_star_cell_renderer_set_property;
+ oclass->finalize = nautilus_star_cell_renderer_finalize;
+
+ rclass->activate = nautilus_star_cell_renderer_activate;
+
+ signals[CLICKED] = g_signal_new ("clicked",
+ NAUTILUS_TYPE_STAR_CELL_RENDERER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_POINTER);
+
+ g_object_class_install_property (oclass,
+ PROP_STARRED,
+ g_param_spec_boolean ("starred",
+ "starred",
+ "starred",
+ FALSE,
+ G_PARAM_READWRITE));
+}
+
+GtkCellRenderer*
+nautilus_star_cell_renderer_new ()
+{
+ NautilusStarCellRenderer *self;
+
+ self = g_object_new (NAUTILUS_TYPE_STAR_CELL_RENDERER, NULL);
+
+ return GTK_CELL_RENDERER (self);
+}
+
+static void
+nautilus_star_cell_renderer_init (NautilusStarCellRenderer *self)
+{
+ self->starred = FALSE;
+}
\ No newline at end of file
diff --git a/src/nautilus-star-cell-renderer.h b/src/nautilus-star-cell-renderer.h
new file mode 100644
index 0000000..7672ea4
--- /dev/null
+++ b/src/nautilus-star-cell-renderer.h
@@ -0,0 +1,35 @@
+/* nautilus-star-cell-renderer.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/>.
+ */
+
+#ifndef NAUTILUS_STAR_CELL_RENDERER_H
+#define NAUTILUS_STAR_CELL_RENDERER_H
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_STAR_CELL_RENDERER (nautilus_star_cell_renderer_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusStarCellRenderer, nautilus_star_cell_renderer, NAUTILUS, STAR_CELL_RENDERER,
GtkCellRendererPixbuf)
+
+GtkCellRenderer* nautilus_star_cell_renderer_new ();
+
+G_END_DECLS
+
+#endif
diff --git a/src/nautilus-tag-manager.c b/src/nautilus-tag-manager.c
new file mode 100644
index 0000000..e453802
--- /dev/null
+++ b/src/nautilus-tag-manager.c
@@ -0,0 +1,1130 @@
+/* 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;
+
+ TrackerNotifier *notifier;
+ GError *notifier_error;
+
+ GQueue *all_tags;
+ GCancellable *all_tags_cancellable;
+
+ GHashTable *favorite_files;
+ GCancellable *favorite_files_cancellable;
+};
+
+G_DEFINE_TYPE (NautilusTagManager, nautilus_tag_manager, G_TYPE_OBJECT);
+
+static NautilusTagManager *tag_manager = NULL;
+
+typedef enum
+{
+ GET_ALL_TAGS,
+ GET_FAVORITE_FILES,
+ GET_SELECTION_TAGS,
+ GET_FILES_WITH_TAG
+} OperationType;
+
+typedef struct
+{
+ GList *selection;
+ GTask *task;
+ GCancellable *cancellable;
+} TaskInfo;
+
+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);
+}
+
+static void
+nautilus_tag_data_queue_free (gpointer data)
+{
+ GQueue *queue;
+
+ queue = data;
+
+ g_queue_free_full (queue, nautilus_tag_data_free);
+}
+
+static void
+destroy_url_queue (gpointer data)
+{
+ GQueue *queue;
+
+ queue = data;
+
+ g_queue_free_full (queue, g_free);
+}
+
+TagData*
+nautilus_tag_data_new (const gchar *id,
+ const gchar *name,
+ const gchar *url)
+{
+ TagData *data;
+
+ data = g_new0 (TagData, 1);
+
+ data->id = g_strdup (id);
+ data->name = g_strdup (name);
+ data->url = g_strdup (url);
+
+ return data;
+}
+
+static GString*
+add_selection_filter (GList *selection,
+ GString *query)
+{
+ NautilusFile *file;
+ GList *l;
+ gchar *uri;
+
+ g_string_append (query, " FILTER(?url IN (");
+
+ for (l = selection; l != NULL; l = l->next)
+ {
+ file = l->data;
+
+ uri = nautilus_file_get_uri (file);
+
+ g_string_append_printf (query, "'%s'", uri);
+
+ if (l->next != NULL)
+ {
+ g_string_append (query, ", ");
+ }
+
+ g_free (uri);
+ }
+
+ g_string_append (query, "))");
+
+ return query;
+}
+
+static void
+start_query_or_update (GString *query,
+ GAsyncReadyCallback callback,
+ gpointer user_data,
+ gboolean is_query,
+ GCancellable *cancellable)
+{
+ g_autoptr (GError) error = NULL;
+ TrackerSparqlConnection *connection;
+
+ connection = tracker_sparql_connection_get (cancellable, &error);
+ if (!connection)
+ {
+ if (error)
+ {
+ g_warning ("Error on getting connection: %s", error->message);
+ }
+
+ return;
+ }
+
+ if (is_query)
+ {
+ tracker_sparql_connection_query_async (connection,
+ query->str,
+ cancellable,
+ callback,
+ user_data);
+ }
+ else
+ {
+ tracker_sparql_connection_update_async (connection,
+ query->str,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data);
+ }
+
+ g_object_unref (connection);
+}
+
+static void
+on_query_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data,
+ GAsyncReadyCallback callback,
+ OperationType op_type,
+ GCancellable *cancellable)
+{
+ TrackerSparqlCursor *cursor;
+ g_autoptr (GError) error = NULL;
+ TrackerSparqlConnection *connection;
+ GTask *task;
+
+ task = user_data;
+
+ connection = TRACKER_SPARQL_CONNECTION (object);
+
+ cursor = tracker_sparql_connection_query_finish (connection,
+ result,
+ &error);
+
+ if (error != NULL)
+ {
+ g_warning ("Error on getting query callback: %s", error->message);
+
+ if (error->code != G_IO_ERROR_CANCELLED)
+ {
+ if (op_type == GET_ALL_TAGS)
+ {
+ g_print ("error while getting tags: %s\n", error->message);
+ }
+ else if (op_type == GET_SELECTION_TAGS)
+ {
+ g_task_return_pointer (task, g_task_get_task_data (task), nautilus_tag_data_queue_free);
+
+ //g_clear_object (&task);
+ }
+ else if (op_type == GET_FAVORITE_FILES)
+ {
+ g_print ("Error on getting favorite files: %s", error->message);
+ }
+ }
+ }
+ else
+ {
+ tracker_sparql_cursor_next_async (cursor,
+ cancellable,
+ callback,
+ user_data);
+ }
+}
+
+static void
+on_update_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlConnection *connection;
+ GError *error;
+ GTask *task;
+
+ task = user_data;
+
+ error = NULL;
+
+ connection = TRACKER_SPARQL_CONNECTION (object);
+
+ tracker_sparql_connection_update_finish (connection, result, &error);
+
+ if (error == NULL ||
+ (error != NULL && error->code != G_IO_ERROR_CANCELLED))
+ {
+ g_task_return_boolean (task, TRUE);
+ }
+ else if (error && error->code == G_IO_ERROR_CANCELLED)
+ {
+ g_error_free (error);
+ }
+ else
+ {
+ g_task_return_error (task, error);
+ g_warning ("error updating tags: %s", error->message);
+ }
+
+ //g_clear_object (&task);
+
+}
+
+static gboolean
+get_query_status (TrackerSparqlCursor *cursor,
+ GAsyncResult *result,
+ OperationType op_type,
+ gpointer user_data)
+{
+ gboolean success;
+ GTask *task;
+ g_autoptr (GError) error = NULL;
+
+ task = user_data;
+
+ success = tracker_sparql_cursor_next_finish (cursor, result, &error);
+
+ if (!success)
+ {
+ if (error)
+ {
+ g_warning ("Error on getting all tags cursor callback: %s", error->message);
+ }
+
+ g_clear_object (&cursor);
+
+ if (error == NULL ||
+ (error != NULL && error->code != G_IO_ERROR_CANCELLED))
+ {
+ if (op_type == GET_ALL_TAGS)
+ {
+ g_print ("got all tags\n");
+ }
+ else if (op_type == GET_SELECTION_TAGS)
+ {
+ g_task_return_pointer (task, g_task_get_task_data (task), nautilus_tag_data_queue_free);
+
+ //g_clear_object (&task);
+
+ }
+ else if (op_type == GET_FILES_WITH_TAG)
+ {
+ g_task_return_pointer (task, g_task_get_task_data (task), destroy_url_queue);
+
+ //g_clear_object (&task);
+
+ }
+ else if (op_type == GET_FAVORITE_FILES)
+ {
+ g_print ("got favorite filesname\n");
+ }
+ }
+ }
+
+ return success;
+}
+
+static void
+on_get_all_tags_cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlCursor *cursor;
+ const gchar *id;
+ const gchar *name;
+ gboolean success;
+ NautilusTagManager *self;
+ TagData *tag_data;
+
+ cursor = TRACKER_SPARQL_CURSOR (object);
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ success = get_query_status (cursor, result, GET_ALL_TAGS, NULL);
+ if (!success)
+ {
+ return;
+ }
+
+ id = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+ name = tracker_sparql_cursor_get_string (cursor, 1, NULL);
+
+ tag_data = nautilus_tag_data_new (id, name, NULL);
+
+ g_queue_push_tail (self->all_tags, tag_data);
+
+ tracker_sparql_cursor_next_async (cursor,
+ self->all_tags_cancellable,
+ on_get_all_tags_cursor_callback,
+ self);
+}
+
+static void
+on_get_all_tags_query_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ NautilusTagManager *self;
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ on_query_callback (object,
+ result,
+ user_data,
+ on_get_all_tags_cursor_callback,
+ GET_ALL_TAGS,
+ self->all_tags_cancellable);
+}
+
+GQueue*
+nautilus_tag_manager_get_all_tags (NautilusTagManager *self)
+{
+ return self->all_tags;
+}
+
+static void
+nautilus_tag_manager_query_all_tags (NautilusTagManager *self,
+ GCancellable *cancellable)
+{
+ GString *query;
+
+ if (self->all_tags)
+ {
+ g_queue_free_full (self->all_tags, nautilus_tag_data_free);
+ }
+ self->all_tags = g_queue_new ();
+
+ self->all_tags_cancellable = cancellable;
+
+ query = g_string_new ("SELECT ?urn ?label WHERE { ?urn a nao:Tag ; nao:prefLabel ?label . } ORDER BY
?label");
+
+ g_print("\n\"%s\" \n", query->str);
+
+ start_query_or_update (query,
+ on_get_all_tags_query_callback,
+ self,
+ TRUE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+static void
+on_get_favorite_files_cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlCursor *cursor;
+ const gchar *url;
+ gboolean success;
+ NautilusTagManager *self;
+
+ cursor = TRACKER_SPARQL_CURSOR (object);
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ success = get_query_status (cursor, result, GET_FAVORITE_FILES, NULL);
+ if (!success)
+ {
+ return;
+ }
+
+ url = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+
+ g_hash_table_insert (self->favorite_files,
+ g_strdup (url),
+ NULL);
+
+ g_print ("fav file: %s\n", url);
+
+ tracker_sparql_cursor_next_async (cursor,
+ self->favorite_files_cancellable,
+ on_get_favorite_files_cursor_callback,
+ self);
+}
+
+static void
+on_get_favorite_files_query_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ NautilusTagManager *self;
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ on_query_callback (object,
+ result,
+ user_data,
+ on_get_favorite_files_cursor_callback,
+ GET_FAVORITE_FILES,
+ self->favorite_files_cancellable);
+}
+
+
+static void
+nautilus_tag_manager_query_favorite_files (NautilusTagManager *self,
+ GCancellable *cancellable)
+{
+ GString *query;
+
+ self->favorite_files_cancellable = cancellable;
+
+ query = g_string_new ("SELECT ?url tracker:id(?urn) WHERE { ?urn nie:url ?url ; nao:hasTag
nao:predefined-tag-favorite}");
+
+ g_print("\n\"%s\" \n", query->str);
+
+ start_query_or_update (query,
+ on_get_favorite_files_query_callback,
+ self,
+ TRUE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+static void
+on_get_selection_tags_cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlCursor *cursor;
+ const gchar *id;
+ const gchar *name;
+ const gchar *url;
+ gboolean success;
+ TagData *tag_data;
+ GTask *task;
+ GQueue *selection_tags;
+
+ cursor = TRACKER_SPARQL_CURSOR (object);
+
+ task = user_data;
+
+ selection_tags = g_task_get_task_data (task);
+
+ success = get_query_status (cursor, result, GET_SELECTION_TAGS, user_data);
+ if (!success)
+ {
+ return;
+ }
+
+ id = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+ name = tracker_sparql_cursor_get_string (cursor, 1, NULL);
+ url = tracker_sparql_cursor_get_string (cursor, 2, NULL);
+
+ tag_data = nautilus_tag_data_new (id, name, url);
+
+ g_queue_push_tail (selection_tags, tag_data);
+
+ tracker_sparql_cursor_next_async (cursor,
+ g_task_get_cancellable (task),
+ on_get_selection_tags_cursor_callback,
+ task);
+}
+
+static void
+on_get_selection_tags_query_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = user_data;
+ g_print ("aci\n");
+ on_query_callback (object,
+ result,
+ task,
+ on_get_selection_tags_cursor_callback,
+ GET_SELECTION_TAGS,
+ g_task_get_cancellable (task));
+}
+
+gpointer
+nautilus_tag_manager_get_queue_finish (GObject *source_object,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (res, source_object), FALSE);
+
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+void
+nautilus_tag_manager_get_selection_tags (GObject *object,
+ GList *selection,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable)
+{
+ GString *query;
+ GQueue *selection_tags;
+ GTask *task;
+
+ selection_tags = g_queue_new ();
+
+ //task_info = g_new0 (TaskInfo, 1);
+ task = g_task_new (object, cancellable, callback, NULL);
+ g_task_set_task_data (task,
+ selection_tags,
+ nautilus_tag_data_queue_free);
+
+ query = g_string_new ("SELECT ?tag nao:prefLabel(?tag) ?url WHERE"
+ "{ ?urn a nfo:FileDataObject ; nao:hasTag ?tag ; nie:url ?url .");
+
+ query = add_selection_filter (selection, query);
+
+ g_string_append (query, "} ORDER BY (?tag)");
+ g_print("\n\"%s\" \n", query->str);
+
+ start_query_or_update (query,
+ on_get_selection_tags_query_callback,
+ task,
+ TRUE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+static void
+on_get_files_with_tag_cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlCursor *cursor;
+ cursor = TRACKER_SPARQL_CURSOR (object);
+ gchar *url;
+ gboolean success;
+ GTask *task;
+ GQueue *files_with_tag;
+
+ task = user_data;
+ files_with_tag = g_task_get_task_data (task);
+
+ success = get_query_status (cursor, result, GET_FILES_WITH_TAG, task);
+ if (!success)
+ {
+ return;
+ }
+
+ url = g_strdup (tracker_sparql_cursor_get_string (cursor, 0, NULL));
+ g_queue_push_tail (files_with_tag, url);
+
+ tracker_sparql_cursor_next_async (cursor,
+ g_task_get_cancellable (task),
+ on_get_files_with_tag_cursor_callback,
+ task);
+}
+
+static void
+on_get_files_with_tag_query_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = user_data;
+
+ on_query_callback (object,
+ result,
+ user_data,
+ on_get_files_with_tag_cursor_callback,
+ GET_FILES_WITH_TAG,
+ g_task_get_cancellable (task));
+}
+
+void
+nautilus_tag_manager_get_files_with_tag (NautilusTagManager *self,
+ GObject *object,
+ const gchar *tag_name,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable)
+{
+ GString *query;
+ GQueue *files_with_tag;
+ GError *error;
+ GTask *task;
+
+ files_with_tag = g_queue_new ();
+
+ task = g_task_new (object, cancellable, callback, NULL);
+ g_task_set_task_data (task,
+ files_with_tag,
+ nautilus_tag_data_queue_free);
+
+ if (!nautilus_tag_queue_has_tag (self->all_tags, tag_name))
+ {
+ error = g_error_new (G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "this tag doesn't exist");
+
+ g_task_return_error (task, error);
+
+ //g_clear_object (&task);
+
+ 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 (query,
+ on_get_files_with_tag_query_callback,
+ task,
+ TRUE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+void
+nautilus_tag_manager_remove_tag (NautilusTagManager *self,
+ GObject *object,
+ const gchar *tag_name,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable)
+{
+ GString *query;
+ GError *error;
+ GList *l;
+ TagData *tag_data;
+ GTask *task;
+
+ task = g_task_new (object, cancellable, callback, NULL);
+
+ if (!nautilus_tag_queue_has_tag (self->all_tags, tag_name))
+ {
+ error = g_error_new (G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "this tag doesn't exist");
+
+ g_task_return_error (task, error);
+
+ //g_clear_object (&task);
+
+ 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 (query,
+ on_update_callback,
+ task,
+ FALSE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+static GString*
+nautilus_tag_manager_delete_tag (NautilusTagManager *self,
+ GList *selection,
+ GString *query,
+ TagData *tag_data,
+ gboolean favorite_tag)
+{
+
+ if (!favorite_tag)
+ {
+ if (!nautilus_tag_queue_has_tag (self->all_tags, tag_data->name))
+ {
+ return query;
+ }
+
+ g_string_append (query,
+ "DELETE { ?urn nao:hasTag ?label } "
+ "WHERE { ?urn a nfo:FileDataObject ; nie:url ?url . ");
+
+ g_string_append_printf (query,
+ "?label nao:prefLabel '%s' . ",
+ tag_data->name);
+ }
+ else
+ {
+ g_string_append (query,
+ "DELETE { ?urn nao:hasTag nao:predefined-tag-favorite }"
+ "WHERE { ?urn a nfo:FileDataObject ; nie:url ?url .");
+ }
+
+ query = add_selection_filter (selection, query);
+
+ g_string_append (query, "}\n");
+
+ return query;
+}
+
+static GString*
+nautilus_tag_manager_insert_tag (NautilusTagManager *self,
+ GList *selection,
+ GString *query,
+ TagData *tag_data,
+ gboolean favorite_tag)
+{
+ g_autofree gchar *tag_color = NULL;
+
+ if (!favorite_tag)
+ {
+ tag_color = parse_color_from_tag_id (tag_data->id);
+
+ if (!nautilus_tag_queue_has_tag (self->all_tags, tag_data->name))
+ {
+ g_string_append_printf (query,
+ "INSERT DATA { <org:gnome:nautilus:tag:%s:%s> a nao:Tag ; nao:prefLabel
'%s' }\n",
+ tag_data->name,
+ tag_color,
+ tag_data->name);
+ }
+
+ query = g_string_append (query,
+ "INSERT { ?urn nao:hasTag ?label }"
+ "WHERE { ?urn a nfo:FileDataObject ; nie:url ?url . ");
+
+ g_string_append_printf (query, "?label nao:prefLabel '%s'", tag_data->name);
+ }
+ else
+ {
+ g_string_append (query,
+ "INSERT { ?urn nao:hasTag nao:predefined-tag-favorite }"
+ "WHERE { ?urn a nfo:FileDataObject ; nie:url ?url .");
+ }
+
+ query = add_selection_filter (selection, query);
+
+ g_string_append (query, "}\n");
+
+ return query;
+}
+
+gboolean
+nautilus_tag_manager_update_tags_finish (GObject *source_object,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (res, source_object), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+void
+nautilus_tag_manager_update_tags (NautilusTagManager *self,
+ GObject *object,
+ GList *selection,
+ GQueue *selection_tags,
+ GQueue *new_selection_tags,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable)
+{
+ GString *query;
+ GList *l;
+ TagData *tag_data;
+ gchar *current_tag_name;
+ GTask *task;
+
+ task = g_task_new (object, cancellable, callback, NULL);
+
+ query = g_string_new ("");
+
+ 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,
+ selection,
+ query,
+ tag_data,
+ FALSE);
+ }
+ }
+
+ 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,
+ selection,
+ query,
+ tag_data,
+ FALSE);
+ }
+
+ current_tag_name = tag_data->name;
+ while (TRUE)
+ {
+ if (l->next == NULL)
+ {
+ break;
+ }
+
+ tag_data = l->next->data;
+ if (g_strcmp0 (tag_data->name, current_tag_name) == 0)
+ {
+ l = l->next;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ g_print ("\n%s\n", query->str);
+
+ start_query_or_update (query,
+ on_update_callback,
+ task,
+ FALSE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+gboolean
+nautilus_tag_manager_file_is_favorite (NautilusTagManager *self,
+ const gchar *file_name)
+{
+ return g_hash_table_contains (self->favorite_files, file_name);
+}
+
+
+void
+nautilus_tag_manager_update_favorite_tags (NautilusTagManager *self,
+ GObject *object,
+ const gchar *uri,
+ gboolean delete_tag,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable)
+{
+ GString *query;
+ GTask *task;
+ NautilusFile *file;
+ GList *selection;
+
+ file = nautilus_file_get_by_uri (uri);
+ selection = g_list_prepend (NULL, file);
+
+ task = g_task_new (object, cancellable, callback, NULL);
+
+ query = g_string_new ("");
+
+ if (delete_tag)
+ {
+ query = nautilus_tag_manager_delete_tag (self,
+ selection,
+ query,
+ NULL,
+ TRUE);
+ }
+ else
+ {
+ query = nautilus_tag_manager_insert_tag (self,
+ selection,
+ query,
+ NULL,
+ TRUE);
+ }
+
+ g_print ("%s\n", query->str);
+
+ start_query_or_update (query,
+ on_update_callback,
+ task,
+ FALSE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+ g_list_free (selection);
+ nautilus_file_unref (file);
+
+}
+
+void
+on_tracker_notifier_events(TrackerNotifier *self,
+ GPtrArray *events,
+ gpointer user_data)
+{
+ TrackerNotifierEvent *event;
+ int i;
+
+ for (i = 0; i < events->len; i++)
+ {
+ event = g_ptr_array_index (events, i);
+
+ switch (tracker_notifier_event_get_event_type (event))
+ {
+ case TRACKER_NOTIFIER_EVENT_CREATE:
+ case TRACKER_NOTIFIER_EVENT_UPDATE:
+ {
+ g_print ("notifier: added tag\n");
+ break;
+ }
+
+ case TRACKER_NOTIFIER_EVENT_DELETE:
+ {
+ g_print ("notifier: delete tag\n");
+ break;
+ }
+ }
+
+ g_print ("got signal %d %d %s\n",
+ tracker_notifier_event_get_id (event),
+ tracker_notifier_event_get_event_type (event),
+ tracker_notifier_event_get_location(event));
+ }
+}
+
+static void
+nautilus_tag_manager_finalize (GObject *object)
+{
+ NautilusTagManager *self;
+
+ self = NAUTILUS_TAG_MANAGER (object);
+
+ if (self->all_tags)
+ {
+ g_queue_free_full (self->all_tags, nautilus_tag_data_free);
+ }
+
+ g_signal_handlers_disconnect_by_func (self->notifier,
+ G_CALLBACK (on_tracker_notifier_events),
+ self);
+
+ g_hash_table_destroy (self->favorite_files);
+
+ G_OBJECT_CLASS (nautilus_tag_manager_parent_class)->finalize (object);
+}
+
+static void
+nautilus_tag_manager_class_init (NautilusTagManagerClass *klass)
+{
+ GObjectClass *oclass;
+
+ oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = nautilus_tag_manager_finalize;
+}
+
+NautilusTagManager* nautilus_tag_manager_new (GCancellable *cancellable_tags,
+ GCancellable *cancellable_notifier,
+ GCancellable *cancellable_favorite)
+{
+ gchar *classes[] = { "nao:hasTag", NULL };
+
+ if (tag_manager != NULL)
+ {
+ return g_object_ref (tag_manager);
+ }
+
+ tag_manager = g_object_new (NAUTILUS_TYPE_TAG_MANAGER, NULL);
+ g_object_add_weak_pointer (G_OBJECT (tag_manager), (gpointer)&tag_manager);
+
+ nautilus_tag_manager_query_all_tags (tag_manager, cancellable_tags);
+
+ nautilus_tag_manager_query_favorite_files (tag_manager, cancellable_favorite);
+
+ tag_manager->notifier = tracker_notifier_new (NULL,//(const gchar * const *) classes,
+ TRACKER_NOTIFIER_FLAG_QUERY_LOCATION,
+ cancellable_notifier,
+ &tag_manager->notifier_error);
+
+ g_signal_connect (tag_manager->notifier,
+ "events",
+ G_CALLBACK (on_tracker_notifier_events),
+ tag_manager);
+
+ return tag_manager;
+}
+
+static void
+nautilus_tag_manager_init (NautilusTagManager *self)
+{
+ self->favorite_files = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ NULL);
+}
diff --git a/src/nautilus-tag-manager.h b/src/nautilus-tag-manager.h
new file mode 100644
index 0000000..f47c77a
--- /dev/null
+++ b/src/nautilus-tag-manager.h
@@ -0,0 +1,105 @@
+/* nautilus-tag-manager.h
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru pandelea gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NAUTILUS_TAG_MANAGER_H
+#define NAUTILUS_TAG_MANAGER_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_TAG_MANAGER (nautilus_tag_manager_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusTagManager, nautilus_tag_manager, NAUTILUS, TAG_MANAGER, GObject);
+
+typedef struct
+{
+ gchar *id;
+ gchar *name;
+ gchar *url;
+} TagData;
+
+NautilusTagManager* nautilus_tag_manager_new (GCancellable *cancellable_tags,
+ GCancellable *cancellable_notifier,
+ GCancellable *cancellable_favorite);
+
+GQueue* nautilus_tag_manager_get_all_tags (NautilusTagManager *self);
+
+gpointer nautilus_tag_manager_get_queue_finish (GObject *source_object,
+ GAsyncResult *res,
+ GError **error);
+
+void nautilus_tag_manager_get_selection_tags (GObject *object,
+ GList *selection,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable);
+
+void nautilus_tag_manager_get_files_with_tag (NautilusTagManager *self,
+ GObject *object,
+ const gchar *tag_name,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable);
+
+void nautilus_tag_manager_remove_tag (NautilusTagManager *self,
+ GObject *object,
+ const gchar *tag_name,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable);
+
+gboolean nautilus_tag_manager_update_tags_finish (GObject *source_object,
+ GAsyncResult *res,
+ GError **error);
+
+void nautilus_tag_manager_update_tags (NautilusTagManager *self,
+ GObject *object,
+ GList *selection,
+ GQueue *selection_tags,
+ GQueue *new_selection_tags,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable);
+
+void nautilus_tag_manager_update_favorite_tags (NautilusTagManager *self,
+ GObject *object,
+ const gchar *uri,
+ gboolean delete_tag,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable);
+
+gboolean nautilus_tag_manager_file_is_favorite (NautilusTagManager *self,
+ const gchar *file_name);
+
+GQueue* nautilus_tag_copy_tag_queue (GQueue *queue);
+
+gboolean nautilus_tag_queue_has_tag (GQueue *selection_tags,
+ const gchar *tag_name);
+
+void nautilus_tag_data_free (gpointer data);
+
+gchar* parse_color_from_tag_id (const gchar *tag_id);
+
+TagData* nautilus_tag_data_new (const gchar *id,
+ const gchar *name,
+ const gchar *url);
+
+
+
+G_END_DECLS
+
+#endif
\ No newline at end of file
diff --git a/src/nautilus-tag-widget.c b/src/nautilus-tag-widget.c
new file mode 100644
index 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..1349c0e
--- /dev/null
+++ b/src/nautilus-tags-dialog.c
@@ -0,0 +1,646 @@
+/* nautilus-tags-dialog.c
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru pandelea gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nautilus-tags-dialog.h"
+#include "nautilus-tag-manager.h"
+#include "nautilus-tag-widget.h"
+
+typedef enum
+{
+ NO_ACTION,
+ UPDATE_TAGS
+} ChosenAction;
+
+struct _NautilusTagsDialog
+{
+ GtkDialog parent;
+
+ NautilusWindow *window;
+ GList *selection;
+
+ GtkWidget *cancel_button;
+ GtkWidget *update_tags_button;
+ GtkWidget *tags_entry;
+ GtkWidget *color_button;
+ GtkWidget *tags_listbox;
+ GtkWidget *selection_tags_box;
+ GtkWidget *add_tag_button;
+
+ NautilusTagManager *tag_manager;
+ GQueue *all_tags;
+ GQueue *selection_tags;
+ GQueue *new_selection_tags;
+
+ /* queues that represent all the tags */
+ GQueue *list_box_rows;
+ GQueue *all_tags_widgets;
+ /* initilay it has tags widgets that the selection
+ * has, but updates when a tag is removed/added */
+ GQueue *selection_tags_widgets;
+
+ ChosenAction action;
+
+ GCancellable *cancellable_selection;
+ GCancellable *cancellable_update;
+ GCancellable *cancellable_get_files;
+};
+
+G_DEFINE_TYPE (NautilusTagsDialog, nautilus_tags_dialog, GTK_TYPE_DIALOG);
+
+static void on_selection_tags_obtained (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data);
+
+static void on_tags_updated (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data);
+
+static void
+update_tags (NautilusTagsDialog *dialog)
+{
+ GQueue *all_tags_check;
+ GList *l;
+ GList *l_check;
+ TagData *tag_data;
+ TagData *tag_data_check;
+
+ all_tags_check = nautilus_tag_manager_get_all_tags (dialog->tag_manager);
+
+ for (l = g_queue_peek_head_link (dialog->all_tags), l_check = g_queue_peek_head_link (all_tags_check);
+ l != NULL && l_check != NULL; l = l->next, l_check = l_check->next)
+ {
+ tag_data = l->data;
+ tag_data_check = l_check->data;
+
+ if (g_strcmp0 (tag_data->id, tag_data_check->id) != 0 ||
+ g_strcmp0 (tag_data->name, tag_data_check->name) != 0)
+ {
+ g_print ("something changed with all tags\n");
+
+ return;
+ }
+ }
+
+ nautilus_tag_manager_get_selection_tags (G_OBJECT (dialog),
+ dialog->selection,
+ on_selection_tags_obtained,
+ dialog->cancellable_selection);
+}
+
+static gint
+get_tag_position_in_listbox (NautilusTagsDialog *dialog,
+ const gchar *tag_name)
+{
+ GList *l;
+ gint index = 0;
+ g_autofree gchar *casefold_tag_name = NULL;
+ gchar *casefold_widget_tag_name;
+
+ casefold_tag_name = g_utf8_casefold (tag_name, -1);
+
+ for (l = g_queue_peek_head_link (dialog->all_tags_widgets), index = 0;
+ l != NULL;
+ l = l->next, index++)
+ {
+ // 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 (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusTagsDialog *dialog;
+ g_autoptr (GError) error = NULL;
+ 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;
+
+ dialog = NAUTILUS_TAGS_DIALOG (object);
+
+ if (dialog->action == NO_ACTION)
+ {
+ dialog->selection_tags = nautilus_tag_manager_get_queue_finish (object, res, &error);
+
+ 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 = nautilus_tag_manager_get_queue_finish (object, res, &error);
+
+ for (l = g_queue_peek_head_link (dialog->selection_tags), l_check = g_queue_peek_head_link
(selection_tags_check);
+ l != NULL && l_check != NULL; l = l->next, l_check = l_check->next)
+ {
+ tag_data = l->data;
+ tag_data_check = l_check->data;
+
+ if (g_strcmp0 (tag_data->id, tag_data_check->id) != 0 ||
+ g_strcmp0 (tag_data->name, tag_data_check->name) != 0)
+ {
+ 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);
+ }
+
+ g_print ("good to go");
+
+ nautilus_tag_manager_update_tags (dialog->tag_manager,
+ G_OBJECT (dialog),
+ dialog->selection,
+ dialog->selection_tags,
+ dialog->new_selection_tags,
+ on_tags_updated,
+ dialog->cancellable_update);
+ }
+}
+
+static void
+on_tags_updated (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusTagsDialog *dialog;
+ g_autoptr (GError) error = NULL;
+ gboolean result;
+
+ dialog = NAUTILUS_TAGS_DIALOG (object);
+
+ result = nautilus_tag_manager_update_tags_finish (object, res, &error);
+
+ if (result)
+ {
+ g_print ("tags updated succesfully!\n");
+ }
+ else
+ {
+ g_warning ("something went wrong while updating tags");
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+}
+
+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_selection);
+ g_clear_object (&dialog->cancellable_selection);
+
+ g_cancellable_cancel (dialog->cancellable_update);
+ g_clear_object (&dialog->cancellable_update);
+
+ g_cancellable_cancel (dialog->cancellable_get_files);
+ g_clear_object (&dialog->cancellable_get_files);
+
+ if (dialog->all_tags)
+ {
+ g_queue_free_full (dialog->all_tags, nautilus_tag_data_free);
+ }
+ if (dialog->selection_tags)
+ {
+ g_queue_free_full (dialog->selection_tags, nautilus_tag_data_free);
+ }
+ if (dialog->new_selection_tags)
+ {
+ g_queue_free_full (dialog->new_selection_tags, nautilus_tag_data_free);
+ }
+ if (dialog->selection_tags_widgets)
+ {
+ g_queue_free (dialog->selection_tags_widgets);
+ }
+ if (dialog->all_tags_widgets)
+ {
+ g_queue_free (dialog->all_tags_widgets);
+ }
+ if (dialog->list_box_rows)
+ {
+ g_queue_free (dialog->list_box_rows);
+ }
+
+ g_clear_object (&dialog->tag_manager);
+
+ G_OBJECT_CLASS (nautilus_tags_dialog_parent_class)->finalize (object);
+}
+
+static void
+nautilus_tags_dialog_class_init (NautilusTagsDialogClass *klass)
+{
+ GtkWidgetClass *widget_class;
+ GObjectClass *oclass;
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+ oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = nautilus_tags_dialog_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/nautilus/ui/nautilus-tags-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, cancel_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, update_tags_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, tags_entry);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, color_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, tags_listbox);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, selection_tags_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTagsDialog, add_tag_button);
+
+ gtk_widget_class_bind_template_callback (widget_class, nautilus_tags_dialog_on_response);
+ gtk_widget_class_bind_template_callback (widget_class, on_tags_entry_activate);
+ gtk_widget_class_bind_template_callback (widget_class, on_tags_entry_changed);
+ gtk_widget_class_bind_template_callback (widget_class, on_add_button_clicked);
+}
+
+GtkWidget* nautilus_tags_dialog_new (GList *selection,
+ NautilusWindow *window)
+{
+ NautilusTagsDialog *dialog;
+ GList *l;
+ TagData *tag_data;
+
+ dialog = g_object_new (NAUTILUS_TYPE_TAGS_DIALOG, "use-header-bar", TRUE, NULL);
+
+ dialog->window = window;
+ dialog->selection = selection;
+
+ gtk_window_set_transient_for (GTK_WINDOW (dialog),
+ GTK_WINDOW (window));
+
+ g_signal_connect (dialog->tags_listbox, "row-activated", G_CALLBACK (on_row_activated), dialog);
+
+ dialog->cancellable_selection = g_cancellable_new ();
+ dialog->cancellable_update = g_cancellable_new ();
+
+ dialog->tag_manager = nautilus_tag_manager_new (NULL, NULL, NULL);
+
+ dialog->all_tags = nautilus_tag_manager_get_all_tags (dialog->tag_manager);
+ 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);
+
+ dialog->action = NO_ACTION;
+
+ nautilus_tag_manager_get_selection_tags (G_OBJECT (dialog),
+ selection,
+ on_selection_tags_obtained,
+ dialog->cancellable_selection);
+
+ return GTK_WIDGET (dialog);
+}
+
+static void
+nautilus_tags_dialog_init (NautilusTagsDialog *self)
+{
+ GdkRGBA color;
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->list_box_rows = g_queue_new ();
+
+ gdk_rgba_parse (&color, "rgb(220,220,220)");
+ gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (self->color_button),
+ &color);
+}
\ No newline at end of file
diff --git a/src/nautilus-tags-dialog.h b/src/nautilus-tags-dialog.h
new file mode 100644
index 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]