From 5d05180eba0adbabc84b2d9a33f4a3e97f01274b Mon Sep 17 00:00:00 2001 From: Alexandru Pandelea Date: Wed, 28 Jun 2017 17:47:41 +0100 Subject: implement tags --- src/meson.build | 10 +- src/nautilus-application.c | 25 + src/nautilus-bookmark-list.c | 5 + src/nautilus-column-utilities.c | 12 + src/nautilus-directory.c | 1 + src/nautilus-favorite-directory.c | 577 ++++++++ src/nautilus-favorite-directory.h | 35 + src/nautilus-file-utilities.c | 18 +- src/nautilus-file-utilities.h | 1 + src/nautilus-file.c | 86 ++ src/nautilus-file.h | 2 + src/nautilus-files-view.c | 128 ++ src/nautilus-files-view.h | 4 + src/nautilus-list-view-private.h | 4 + src/nautilus-list-view.c | 186 ++- src/nautilus-list-view.h | 1 + src/nautilus-pathbar.c | 12 +- src/nautilus-query.c | 19 + src/nautilus-query.h | 4 + src/nautilus-search-directory.c | 1 + src/nautilus-search-engine-model.c | 17 + src/nautilus-search-engine-simple.c | 19 +- src/nautilus-search-engine-tracker.c | 5 + src/nautilus-search-engine.c | 1 + src/nautilus-tag-manager.c | 1399 ++++++++++++++++++++ src/nautilus-tag-manager.h | 113 ++ src/nautilus-tag-widget.c | 304 +++++ src/nautilus-tag-widget.h | 51 + src/nautilus-tags-dialog.c | 620 +++++++++ src/nautilus-tags-dialog.h | 39 + src/nautilus-window.c | 14 + src/resources/nautilus.gresource.xml | 1 + .../ui/nautilus-files-view-context-menus.ui | 17 + src/resources/ui/nautilus-tags-dialog.ui | 157 +++ src/resources/ui/nautilus-window.ui | 2 + 35 files changed, 3870 insertions(+), 20 deletions(-) create mode 100644 src/nautilus-favorite-directory.c create mode 100644 src/nautilus-favorite-directory.h create mode 100644 src/nautilus-tag-manager.c create mode 100644 src/nautilus-tag-manager.h create mode 100644 src/nautilus-tag-widget.c create mode 100644 src/nautilus-tag-widget.h create mode 100644 src/nautilus-tags-dialog.c create mode 100644 src/nautilus-tags-dialog.h create mode 100644 src/resources/ui/nautilus-tags-dialog.ui diff --git a/src/meson.build b/src/meson.build index 83723b94f..2ca2e84ba 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-favorite-directory.c', + 'nautilus-favorite-directory.h'] endif nautilus_deps = [glib, diff --git a/src/nautilus-application.c b/src/nautilus-application.c index 77443f5f6..108319fc6 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-bookmark-list.c b/src/nautilus-bookmark-list.c index d7a0ea092..926d949fe 100644 --- a/src/nautilus-bookmark-list.c +++ b/src/nautilus-bookmark-list.c @@ -643,6 +643,11 @@ nautilus_bookmark_list_can_bookmark_location (NautilusBookmarkList *list, return FALSE; } + if (nautilus_is_favorite_directory (location)) + { + return FALSE; + } + bookmark = nautilus_bookmark_new (location, NULL); is_builtin = nautilus_bookmark_get_is_builtin (bookmark); g_object_unref (bookmark); diff --git a/src/nautilus-column-utilities.c b/src/nautilus-column-utilities.c index 1a2a0927c..4a2572224 100644 --- a/src/nautilus-column-utilities.c +++ b/src/nautilus-column-utilities.c @@ -41,6 +41,7 @@ static const char *default_column_order[] = "date_modified_with_time", "date_modified", "date_accessed", + "favorite", "recency", NULL }; @@ -71,6 +72,17 @@ get_builtin_columns (void) "label", _("Type"), "description", _("The type of the file."), NULL)); + + columns = g_list_append (columns, + g_object_new (NAUTILUS_TYPE_COLUMN, + "name", "favorite", + "attribute", "favorite", + "label", _("Star"), + "description", _("Shows if file is favorite."), + "default-sort-order", GTK_SORT_DESCENDING, + "xalign", 0.5, + NULL)); + columns = g_list_append (columns, g_object_new (NAUTILUS_TYPE_COLUMN, "name", "date_modified", diff --git a/src/nautilus-directory.c b/src/nautilus-directory.c index c2ff9111c..647cee8d7 100644 --- a/src/nautilus-directory.c +++ b/src/nautilus-directory.c @@ -27,6 +27,7 @@ #include "nautilus-file-private.h" #include "nautilus-file-utilities.h" #include "nautilus-search-directory.h" +#include "nautilus-favorite-directory.h" #include "nautilus-search-directory-file.h" #include "nautilus-vfs-file.h" #include "nautilus-global-preferences.h" diff --git a/src/nautilus-favorite-directory.c b/src/nautilus-favorite-directory.c new file mode 100644 index 000000000..dd76db335 --- /dev/null +++ b/src/nautilus-favorite-directory.c @@ -0,0 +1,577 @@ +/* nautilus-favorite-directory.c + * + * Copyright (C) 2017 Alexandru Pandelea + * + * 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 . + */ + +#include "nautilus-favorite-directory.h" +#include "nautilus-tag-manager.h" +#include "nautilus-file-utilities.h" +#include "nautilus-directory-private.h" +#include + +struct _NautilusFavoriteDirectory +{ + NautilusDirectory parent; + + NautilusTagManager *tag_manager; + GList *files; + + GList *monitor_list; + GList *callback_list; + GList *pending_callback_list; +}; + +typedef struct +{ + gboolean monitor_hidden_files; + NautilusFileAttributes monitor_attributes; + + gconstpointer client; +} FavoriteMonitor; + +typedef struct +{ + NautilusFavoriteDirectory *favorite_directory; + + NautilusDirectoryCallback callback; + gpointer callback_data; + + NautilusFileAttributes wait_for_attributes; + gboolean wait_for_file_list; + GList *file_list; +} FavoriteCallback; + +G_DEFINE_TYPE_WITH_CODE (NautilusFavoriteDirectory, nautilus_favorite_directory, NAUTILUS_TYPE_DIRECTORY, + nautilus_ensure_extension_points (); + g_io_extension_point_implement (NAUTILUS_DIRECTORY_PROVIDER_EXTENSION_POINT_NAME, + g_define_type_id, + NAUTILUS_FAVORITE_DIRECTORY_PROVIDER_NAME, + 0)); + +static void +file_changed (NautilusFile *file, + NautilusFavoriteDirectory *favorite) +{ + GList list; + + list.data = file; + list.next = NULL; + + nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (favorite), &list); +} + +static void +nautilus_favorite_directory_update_files (NautilusFavoriteDirectory *self) +{ + GList *l; + GList *tmp_l; + GList *new_favorite_files; + GList *monitor_list; + FavoriteMonitor *monitor; + NautilusFile *file; + GHashTable *uri_table; + GList *files_added; + GList *files_removed; + gchar *uri; + + files_added = NULL; + files_removed = NULL; + + uri_table = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + NULL); + + for (l = self->files; l != NULL; l = l->next) + { + g_hash_table_add (uri_table, nautilus_file_get_uri (NAUTILUS_FILE (l->data))); + } + + new_favorite_files = nautilus_tag_manager_get_favorite_files (self->tag_manager); + + for (l = new_favorite_files; l != NULL; l = l->next) + { + if (!g_hash_table_contains (uri_table, l->data)) + { + file = nautilus_file_get_by_uri ((gchar*) l->data); + + for (monitor_list = self->monitor_list; monitor_list; monitor_list = monitor_list->next) + { + monitor = monitor_list->data; + + /* Add monitors */ + nautilus_file_monitor_add (file, monitor, monitor->monitor_attributes); + } + + g_signal_connect (file, "changed", G_CALLBACK (file_changed), self); + + files_added = g_list_prepend (files_added, file); + } + } + + l = self->files; + while (l != NULL) + { + uri = nautilus_file_get_uri (NAUTILUS_FILE (l->data)); + + if (!nautilus_tag_manager_file_is_favorite (self->tag_manager, uri)) + { + files_removed = g_list_prepend (files_removed, + nautilus_file_ref (NAUTILUS_FILE (l->data))); + + g_signal_handlers_disconnect_by_func (NAUTILUS_FILE (l->data), + file_changed, + self); + + /* Remove monitors */ + for (monitor_list = self->monitor_list; monitor_list; + monitor_list = monitor_list->next) + { + monitor = monitor_list->data; + nautilus_file_monitor_remove (NAUTILUS_FILE (l->data), monitor); + } + + if (l == self->files) + { + self->files = g_list_delete_link (self->files, l); + l = self->files; + } + else + { + tmp_l = l->prev; + self->files = g_list_delete_link (self->files, l); + l = tmp_l->next; + } + } + else + { + l = l->next; + } + + g_free (uri); + } + + if (files_added) + { + nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (self), files_added); + + for (l = files_added; l != NULL; l = l->next) + { + self->files = g_list_prepend (self->files, nautilus_file_ref (NAUTILUS_FILE (l->data))); + } + } + + if (files_removed) + { + nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (self), files_removed); + } + + nautilus_file_list_free (files_added); + nautilus_file_list_free (files_removed); + g_hash_table_destroy (uri_table); +} + +static void +on_favorites_files_changed (NautilusTagManager *tag_manager, + NautilusFavoriteDirectory *self) +{ + nautilus_favorite_directory_update_files (self); +} + +static gboolean +real_contains_file (NautilusDirectory *directory, + NautilusFile *file) +{ + NautilusFavoriteDirectory *self; + g_autofree gchar *uri = NULL; + + self = NAUTILUS_FAVORITE_DIRECTORY (directory); + + uri = nautilus_file_get_uri (file); + + return nautilus_tag_manager_file_is_favorite (self->tag_manager, uri); +} + +static gboolean +real_is_editable (NautilusDirectory *directory) +{ + return FALSE; +} + +static void +real_force_reload (NautilusDirectory *directory) +{ + nautilus_favorite_directory_update_files (NAUTILUS_FAVORITE_DIRECTORY (directory)); +} + +static void +real_call_when_ready (NautilusDirectory *directory, + NautilusFileAttributes file_attributes, + gboolean wait_for_file_list, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + GList *file_list; + NautilusFavoriteDirectory *favorite; + + favorite = NAUTILUS_FAVORITE_DIRECTORY (directory); + + file_list = nautilus_file_list_copy (favorite->files); + + callback (NAUTILUS_DIRECTORY (directory), + file_list, + callback_data); +} + +static gboolean +real_are_all_files_seen (NautilusDirectory *directory) +{ + return TRUE; +} + +static void +real_file_monitor_add (NautilusDirectory *directory, + gconstpointer client, + gboolean monitor_hidden_files, + NautilusFileAttributes file_attributes, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + GList *list; + FavoriteMonitor *monitor; + NautilusFavoriteDirectory *favorite; + NautilusFile *file; + + favorite = NAUTILUS_FAVORITE_DIRECTORY (directory); + + monitor = g_new0 (FavoriteMonitor, 1); + monitor->monitor_hidden_files = monitor_hidden_files; + monitor->monitor_attributes = file_attributes; + monitor->client = client; + + favorite->monitor_list = g_list_prepend (favorite->monitor_list, monitor); + + if (callback != NULL) + { + (*callback)(directory, favorite->files, callback_data); + } + + for (list = favorite->files; list != NULL; list = list->next) + { + file = list->data; + + /* Add monitors */ + nautilus_file_monitor_add (file, monitor, file_attributes); + } +} + +static void +favorite_monitor_destroy (FavoriteMonitor *monitor, + NautilusFavoriteDirectory *favorite) +{ + GList *l; + NautilusFile *file; + + for (l = favorite->files; l != NULL; l = l->next) + { + file = l->data; + + nautilus_file_monitor_remove (file, monitor); + } + + g_free (monitor); +} + +static void +real_monitor_remove (NautilusDirectory *directory, + gconstpointer client) +{ + NautilusFavoriteDirectory *favorite; + FavoriteMonitor *monitor; + GList *list; + + favorite = NAUTILUS_FAVORITE_DIRECTORY (directory); + + for (list = favorite->monitor_list; list != NULL; list = list->next) + { + monitor = list->data; + + if (monitor->client == client) + { + favorite->monitor_list = g_list_delete_link (favorite->monitor_list, list); + + favorite_monitor_destroy (monitor, favorite); + + break; + } + } +} + +static gboolean +real_handles_location (GFile *location) +{ + g_autofree gchar *uri = NULL; + + uri = g_file_get_uri (location); + + if (g_strcmp0 (uri, "favorites:///") == 0) + { + return TRUE; + } + + return FALSE; +} + +static FavoriteCallback* +favorite_callback_find_pending (NautilusFavoriteDirectory *favorite, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + FavoriteCallback *favorite_callback; + GList *list; + + for (list = favorite->pending_callback_list; list != NULL; list = list->next) + { + favorite_callback = list->data; + + if (favorite_callback->callback == callback && + favorite_callback->callback_data == callback_data) + { + return favorite_callback; + } + } + + return NULL; +} + +static FavoriteCallback* +favorite_callback_find (NautilusFavoriteDirectory *favorite, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + FavoriteCallback *favorite_callback; + GList *list; + + for (list = favorite->callback_list; list != NULL; list = list->next) + { + favorite_callback = list->data; + + if (favorite_callback->callback == callback && + favorite_callback->callback_data == callback_data) + { + return favorite_callback; + } + } + + return NULL; +} + +static void +favorite_callback_destroy (FavoriteCallback *favorite_callback) +{ + nautilus_file_list_free (favorite_callback->file_list); + + g_free (favorite_callback); +} + +static void +real_cancel_callback (NautilusDirectory *directory, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + NautilusFavoriteDirectory *favorite; + FavoriteCallback *favorite_callback; + + favorite = NAUTILUS_FAVORITE_DIRECTORY (directory); + favorite_callback = favorite_callback_find (favorite, callback, callback_data); + + if (favorite_callback) + { + favorite->callback_list = g_list_remove (favorite->callback_list, favorite_callback); + + favorite_callback_destroy (favorite_callback); + + return; + } + + /* Check for a pending callback */ + favorite_callback = favorite_callback_find_pending (favorite, callback, callback_data); + + if (favorite_callback) + { + favorite->pending_callback_list = g_list_remove (favorite->pending_callback_list, favorite_callback); + + favorite_callback_destroy (favorite_callback); + } +} + +static GList* +real_get_file_list (NautilusDirectory *directory) +{ + NautilusFavoriteDirectory *favorite; + + favorite = NAUTILUS_FAVORITE_DIRECTORY (directory); + + return nautilus_file_list_copy (favorite->files); +} + +static void +nautilus_favorite_directory_set_files (NautilusFavoriteDirectory *self) +{ + GList *favorite_files; + NautilusFile *file; + GList *l; + GList *file_list; + FavoriteMonitor *monitor; + GList *monitor_list; + + file_list = NULL; + + favorite_files = nautilus_tag_manager_get_favorite_files (self->tag_manager); + + for (l = favorite_files; l != NULL; l = l->next) + { + file = nautilus_file_get_by_uri ((gchar*) l->data); + + g_signal_connect (file, "changed", G_CALLBACK (file_changed), self); + + for (monitor_list = self->monitor_list; monitor_list; monitor_list = monitor_list->next) + { + monitor = monitor_list->data; + + /* Add monitors */ + nautilus_file_monitor_add (file, monitor, monitor->monitor_attributes); + } + + file_list = g_list_prepend (file_list, file); + } + + nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (self), file_list); + + self->files = file_list; +} + +static void +nautilus_favorite_directory_finalize (GObject *object) +{ + NautilusFavoriteDirectory *self; + + self = NAUTILUS_FAVORITE_DIRECTORY (object); + + g_signal_handlers_disconnect_by_func (self->tag_manager, + on_favorites_files_changed, + self); + + g_object_unref (self->tag_manager); + nautilus_file_list_free (self->files); + + G_OBJECT_CLASS (nautilus_favorite_directory_parent_class)->finalize (object); +} + +static void +nautilus_favorite_directory_dispose (GObject *object) +{ + NautilusFavoriteDirectory *favorite; + GList *l; + GList *monitor_list; + FavoriteMonitor *monitor; + NautilusFile *file; + + favorite = NAUTILUS_FAVORITE_DIRECTORY (object); + + /* Remove file connections */ + for (l = favorite->files; l != NULL; l = l->next) + { + file = l->data; + + /* Disconnect change handler */ + g_signal_handlers_disconnect_by_func (file, file_changed, favorite); + + /* Remove monitors */ + for (monitor_list = favorite->monitor_list; monitor_list; + monitor_list = monitor_list->next) + { + monitor = monitor_list->data; + nautilus_file_monitor_remove (file, monitor); + } + } + + /* Remove search monitors */ + if (favorite->monitor_list) + { + for (l = favorite->monitor_list; l != NULL; l = l->next) + { + favorite_monitor_destroy ((FavoriteMonitor*) l->data, favorite); + } + + g_list_free (favorite->monitor_list); + favorite->monitor_list = NULL; + } + + G_OBJECT_CLASS (nautilus_favorite_directory_parent_class)->dispose (object); +} + +static void +nautilus_favorite_directory_class_init (NautilusFavoriteDirectoryClass *klass) +{ + GObjectClass *oclass; + NautilusDirectoryClass *directory_class; + + oclass = G_OBJECT_CLASS (klass); + directory_class = NAUTILUS_DIRECTORY_CLASS (klass); + + oclass->finalize = nautilus_favorite_directory_finalize; + oclass->dispose = nautilus_favorite_directory_dispose; + + directory_class->handles_location = real_handles_location; + directory_class->contains_file = real_contains_file; + directory_class->is_editable = real_is_editable; + directory_class->force_reload = real_force_reload; + directory_class->call_when_ready = real_call_when_ready; + directory_class->are_all_files_seen = real_are_all_files_seen; + directory_class->file_monitor_add = real_file_monitor_add; + directory_class->file_monitor_remove = real_monitor_remove; + directory_class->cancel_callback = real_cancel_callback; + directory_class->get_file_list = real_get_file_list; +} + +NautilusFavoriteDirectory* +nautilus_favorite_directory_new () +{ + NautilusFavoriteDirectory *self; + + self = g_object_new (NAUTILUS_TYPE_FAVORITE_DIRECTORY, NULL); + + return self; +} + +static void +nautilus_favorite_directory_init (NautilusFavoriteDirectory *self) +{ + NautilusTagManager *tag_manager; + + tag_manager = nautilus_tag_manager_new (NULL, NULL, NULL); + + g_signal_connect (tag_manager, + "favorites-changed", + (GCallback) on_favorites_files_changed, + self); + + self->tag_manager = tag_manager; + + nautilus_favorite_directory_set_files (self); + +} diff --git a/src/nautilus-favorite-directory.h b/src/nautilus-favorite-directory.h new file mode 100644 index 000000000..3da343d96 --- /dev/null +++ b/src/nautilus-favorite-directory.h @@ -0,0 +1,35 @@ +/* nautilus-favorite-directory.h + * + * Copyright (C) 2017 Alexandru Pandelea + * + * 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 . + */ + +#ifndef NAUTILUS_FAVORITE_DIRECTORY_H +#define NAUTILUS_FAVORITE_DIRECTORY_H + +#include "nautilus-directory.h" + +G_BEGIN_DECLS + +#define NAUTILUS_FAVORITE_DIRECTORY_PROVIDER_NAME "favorite-directory-provider" + +#define NAUTILUS_TYPE_FAVORITE_DIRECTORY (nautilus_favorite_directory_get_type ()) + +G_DECLARE_FINAL_TYPE (NautilusFavoriteDirectory, nautilus_favorite_directory, NAUTILUS, FAVORITE_DIRECTORY, NautilusDirectory); +NautilusFavoriteDirectory* nautilus_favorite_directory_new (); + +G_END_DECLS + +#endif diff --git a/src/nautilus-file-utilities.c b/src/nautilus-file-utilities.c index 59add50e4..ee60430d7 100644 --- a/src/nautilus-file-utilities.c +++ b/src/nautilus-file-utilities.c @@ -322,7 +322,10 @@ nautilus_compute_title_for_location (GFile *location) { title = g_strdup (_("Other Locations")); } - else + else if (nautilus_file_is_favorite_location (file)) + { + title = g_strdup (_("Favorite Files")); + } { title = nautilus_file_get_description (file); @@ -592,6 +595,19 @@ nautilus_is_search_directory (GFile *dir) return eel_uri_is_search (uri); } +gboolean +nautilus_is_favorite_directory (GFile *dir) +{ + g_autofree gchar *uri = NULL; + + uri = g_file_get_uri (dir); + + if (g_strcmp0 (uri, "favorites:///") == 0) + return TRUE; + + return FALSE; +} + gboolean nautilus_is_other_locations_directory (GFile *dir) { diff --git a/src/nautilus-file-utilities.h b/src/nautilus-file-utilities.h index 29ad7b3c8..c0d5529c2 100644 --- a/src/nautilus-file-utilities.h +++ b/src/nautilus-file-utilities.h @@ -46,6 +46,7 @@ gboolean nautilus_is_home_directory_file (GFile *dir, const char *filename); gboolean nautilus_is_in_system_dir (GFile *location); gboolean nautilus_is_search_directory (GFile *dir); +gboolean nautilus_is_favorite_directory (GFile *dir); gboolean nautilus_is_other_locations_directory (GFile *dir); GMount * nautilus_get_mounted_mount_for_root (GFile *location); diff --git a/src/nautilus-file.c b/src/nautilus-file.c index daa4009dd..df2b5433d 100644 --- a/src/nautilus-file.c +++ b/src/nautilus-file.c @@ -40,6 +40,7 @@ #include "nautilus-vfs-file.h" #include "nautilus-file-undo-operations.h" #include "nautilus-file-undo-manager.h" +#include "nautilus-tag-manager.h" #include #include #include @@ -129,6 +130,7 @@ static NautilusSpeedTradeoffValue show_directory_item_count; static GQuark attribute_name_q, attribute_size_q, attribute_type_q, + attribute_favorite_q, attribute_detailed_type_q, attribute_modification_date_q, attribute_date_modified_q, @@ -3659,6 +3661,42 @@ compare_by_type (NautilusFile *file_1, return result; } +static int +compare_by_favorite (NautilusFile *file_1, + NautilusFile *file_2) +{ + NautilusTagManager *tag_manager; + g_autofree gchar *uri_1 = NULL; + g_autofree gchar *uri_2 = NULL; + gboolean file_1_is_favorite; + gboolean file_2_is_favorite; + + tag_manager = nautilus_tag_manager_new (NULL, NULL, NULL); + + uri_1 = nautilus_file_get_uri (file_1); + uri_2 = nautilus_file_get_uri (file_2); + + file_1_is_favorite = nautilus_tag_manager_file_is_favorite (tag_manager, + uri_1); + file_2_is_favorite = nautilus_tag_manager_file_is_favorite (tag_manager, + uri_2); + + + if ((file_1_is_favorite && file_2_is_favorite) || + (!file_1_is_favorite && !file_2_is_favorite)) + { + return 0; + } + else if (file_1_is_favorite && !file_2_is_favorite) + { + return -1; + } + else + { + return 1; + } +} + static Knowledge get_search_relevance (NautilusFile *file, gdouble *relevance_out) @@ -3862,6 +3900,16 @@ nautilus_file_compare_for_sort (NautilusFile *file_1, } break; + case NAUTILUS_FILE_SORT_BY_FAVORITE: + { + result = compare_by_favorite (file_1, file_2); + if (result == 0) + { + result = compare_by_full_path (file_1, file_2); + } + } + break; + case NAUTILUS_FILE_SORT_BY_MTIME: { result = compare_by_time (file_1, file_2, NAUTILUS_DATE_TYPE_MODIFIED); @@ -3966,6 +4014,13 @@ nautilus_file_compare_for_sort_by_attribute_q (NautilusFile *file_1, directories_first, reversed); } + else if (attribute == attribute_favorite_q) + { + return nautilus_file_compare_for_sort (file_1, file_2, + NAUTILUS_FILE_SORT_BY_FAVORITE, + directories_first, + reversed); + } else if (attribute == attribute_modification_date_q || attribute == attribute_date_modified_q || attribute == attribute_date_modified_with_time_q || attribute == attribute_date_modified_full_q) { return nautilus_file_compare_for_sort (file_1, file_2, @@ -4557,6 +4612,11 @@ nautilus_file_peek_display_name (NautilusFile *file) char * nautilus_file_get_display_name (NautilusFile *file) { + if (nautilus_file_is_other_locations (file)) + return g_strdup (_("Other Locations")); + if (nautilus_file_is_favorite_location (file)) + return g_strdup (_("Favorite Files")); + return g_strdup (nautilus_file_peek_display_name (file)); } @@ -8336,6 +8396,31 @@ nautilus_file_is_other_locations (NautilusFile *file) return is_other_locations; } +/** + * nautilus_file_is_favorite_location + * + * Check if this file is the Favorite location. + * @file: NautilusFile representing the file in question. + * + * Returns: TRUE if @file is the Favorite location. + * + **/ +gboolean +nautilus_file_is_favorite_location (NautilusFile *file) +{ + gboolean is_favorite_location; + gchar *uri; + + g_assert (NAUTILUS_IS_FILE (file)); + + uri = nautilus_file_get_uri (file); + is_favorite_location = g_strcmp0 (uri, "favorites:///") == 0; + + g_free (uri); + + return is_favorite_location; +} + /** * nautilus_file_is_in_admin * @@ -9317,6 +9402,7 @@ nautilus_file_class_init (NautilusFileClass *class) attribute_name_q = g_quark_from_static_string ("name"); attribute_size_q = g_quark_from_static_string ("size"); attribute_type_q = g_quark_from_static_string ("type"); + attribute_favorite_q = g_quark_from_static_string ("favorite"); attribute_detailed_type_q = g_quark_from_static_string ("detailed_type"); attribute_modification_date_q = g_quark_from_static_string ("modification_date"); attribute_date_modified_q = g_quark_from_static_string ("date_modified"); diff --git a/src/nautilus-file.h b/src/nautilus-file.h index c4d17801e..210be647f 100644 --- a/src/nautilus-file.h +++ b/src/nautilus-file.h @@ -55,6 +55,7 @@ typedef enum { NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, NAUTILUS_FILE_SORT_BY_SIZE, NAUTILUS_FILE_SORT_BY_TYPE, + NAUTILUS_FILE_SORT_BY_FAVORITE, NAUTILUS_FILE_SORT_BY_MTIME, NAUTILUS_FILE_SORT_BY_ATIME, NAUTILUS_FILE_SORT_BY_TRASHED_TIME, @@ -218,6 +219,7 @@ gboolean nautilus_file_is_in_recent (Nautilu gboolean nautilus_file_is_in_admin (NautilusFile *file); gboolean nautilus_file_is_remote (NautilusFile *file); gboolean nautilus_file_is_other_locations (NautilusFile *file); +gboolean nautilus_file_is_favorite_location (NautilusFile *file); gboolean nautilus_file_is_home (NautilusFile *file); gboolean nautilus_file_is_desktop_directory (NautilusFile *file); gboolean nautilus_file_is_child_of_desktop_directory (NautilusFile *file); diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c index 3d569131a..60360abaf 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" @@ -44,6 +45,7 @@ #include "nautilus-window.h" #include "nautilus-toolbar.h" #include "nautilus-view.h" +#include "nautilus-tag-manager.h" #ifdef HAVE_X11_XF86KEYSYM_H #include @@ -72,6 +74,7 @@ #include #include "nautilus-clipboard.h" #include "nautilus-search-directory.h" +#include "nautilus-favorite-directory.h" #include "nautilus-directory.h" #include "nautilus-dnd.h" #include "nautilus-file-attributes.h" @@ -274,6 +277,9 @@ typedef struct gulong stop_signal_handler; gulong reload_signal_handler; + + GCancellable *favorite_cancellable; + NautilusTagManager *tag_manager; } NautilusFilesViewPrivate; typedef struct @@ -1579,6 +1585,85 @@ action_delete (GSimpleAction *action, delete_selected_files (NAUTILUS_FILES_VIEW (user_data)); } +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 (view); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + + dialog = nautilus_tags_dialog_new (selection, window); + gtk_widget_show (GTK_WIDGET (dialog)); +} + +void +on_favorite_tags_updated (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + GTask *task; + NautilusFilesView *view; + + view = NAUTILUS_FILES_VIEW (object); + + if (NAUTILUS_IS_LIST_VIEW (view)) + { + nautilus_list_view_redraw_tree_view (view); + } + + task = user_data; + g_clear_object (&task); +} + +static void +action_star (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + GList *selection; + NautilusFilesViewPrivate *priv; + + view = NAUTILUS_FILES_VIEW (user_data); + priv = nautilus_files_view_get_instance_private (view); + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + + nautilus_tag_manager_star_files (priv->tag_manager, + G_OBJECT (view), + selection, + on_favorite_tags_updated, + priv->favorite_cancellable); +} + +static void +action_unstar (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + GList *selection; + NautilusFilesViewPrivate *priv; + + view = NAUTILUS_FILES_VIEW (user_data); + priv = nautilus_files_view_get_instance_private (view); + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + + nautilus_tag_manager_unstar_files (priv->tag_manager, + G_OBJECT (view), + selection, + on_favorite_tags_updated, + priv->favorite_cancellable); +} + static void action_restore_from_trash (GSimpleAction *action, GVariant *state, @@ -3236,6 +3321,9 @@ nautilus_files_view_finalize (GObject *object) g_hash_table_destroy (priv->non_ready_files); g_hash_table_destroy (priv->pending_reveal); + g_cancellable_cancel (priv->favorite_cancellable); + g_clear_object (&priv->favorite_cancellable); + G_OBJECT_CLASS (nautilus_files_view_parent_class)->finalize (object); } @@ -3484,6 +3572,10 @@ nautilus_files_view_set_location (NautilusView *view, set_search_query_internal (files_view, previous_query, base_model); g_object_unref (previous_query); } + else if (NAUTILUS_IS_FAVORITE_DIRECTORY (directory)) + { + load_directory (NAUTILUS_FILES_VIEW (view), directory); + } else { load_directory (NAUTILUS_FILES_VIEW (view), directory); @@ -7009,6 +7101,9 @@ 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}, + { "star", action_star}, + { "unstar", action_unstar}, /* 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 @@ -7401,6 +7496,9 @@ real_update_actions_state (NautilusFilesView *view) gboolean settings_show_create_link; gboolean settings_automatic_decompression; GDriveStartStopType start_stop_type; + gboolean show_star; + gboolean show_unstar; + gchar *uri; priv = nautilus_files_view_get_instance_private (view); @@ -7741,6 +7839,33 @@ real_update_actions_state (NautilusFilesView *view) g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !nautilus_files_view_is_empty (view)); + show_star = (selection != NULL); + show_unstar = (selection != NULL); + for (l = selection; l != NULL; l = l->next) + { + file = NAUTILUS_FILE (l->data); + uri = nautilus_file_get_uri (file); + + if (nautilus_tag_manager_file_is_favorite (priv->tag_manager, uri)) + { + show_star = FALSE; + } + else + { + show_unstar = FALSE; + } + + g_free (uri); + } + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "star"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_star); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "unstar"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_unstar); + nautilus_file_list_free (selection); } @@ -9714,6 +9839,9 @@ nautilus_files_view_init (NautilusFilesView *view) * changed */ nautilus_application_set_accelerator (app, "view.show-move-to-trash-shortcut-changed-dialog", "Delete"); + priv->favorite_cancellable = g_cancellable_new (); + priv->tag_manager = nautilus_tag_manager_new (NULL, NULL, NULL); + nautilus_profile_end (NULL); } diff --git a/src/nautilus-files-view.h b/src/nautilus-files-view.h index cc3340ae5..4c3ebfe4e 100644 --- a/src/nautilus-files-view.h +++ b/src/nautilus-files-view.h @@ -338,6 +338,10 @@ void nautilus_files_view_action_show_hidden_files (NautilusFilesV GActionGroup * nautilus_files_view_get_action_group (NautilusFilesView *view); GtkWidget* nautilus_files_view_get_content_widget (NautilusFilesView *view); +void on_favorite_tags_updated (GObject *object, + GAsyncResult *res, + gpointer user_data); + G_END_DECLS #endif /* NAUTILUS_FILES_VIEW_H */ diff --git a/src/nautilus-list-view-private.h b/src/nautilus-list-view-private.h index e2a09a1e6..f0d17164a 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 14a074a05..13beaf550 100644 --- a/src/nautilus-list-view.c +++ b/src/nautilus-list-view.c @@ -32,6 +32,7 @@ #include "nautilus-toolbar.h" #include "nautilus-list-view-dnd.h" #include "nautilus-view.h" +#include "nautilus-tag-manager.h" #include #include @@ -106,22 +107,22 @@ G_DEFINE_TYPE (NautilusListView, nautilus_list_view, NAUTILUS_TYPE_FILES_VIEW); static const char *default_search_visible_columns[] = { - "name", "size", "where", NULL + "name", "size", "where", "favorite", NULL }; static const char *default_search_columns_order[] = { - "name", "size", "where", NULL + "name", "size", "where", "favorite", NULL }; static const char *default_recent_visible_columns[] = { - "name", "size", "where", NULL + "name", "size", "where", "favorite", NULL }; static const char *default_recent_columns_order[] = { - "name", "size", "where", NULL + "name", "size", "where", "favorite", NULL }; static const char *default_trash_visible_columns[] = @@ -149,6 +150,7 @@ get_default_sort_order (NautilusFile *file, "type", "date_modified", "date_accessed", + "favorite", "trashed_on", NULL }; @@ -456,6 +458,51 @@ row_activated_callback (GtkTreeView *treeview, activate_selected_items (view); } +void +nautilus_list_view_redraw_tree_view (NautilusFilesView *view) +{ + NautilusListView *list_view; + + list_view = NAUTILUS_LIST_VIEW (view); + + gtk_widget_queue_draw (GTK_WIDGET (list_view->details->tree_view)); +} + +static void +on_star_cell_renderer_clicked (GtkTreePath *path, + NautilusListView *list_view) +{ + NautilusListModel *list_model; + NautilusFile *file; + g_autofree gchar *uri = NULL; + GList *selection; + + list_model = list_view->details->model; + + file = nautilus_list_model_file_for_path (list_model, path); + uri = nautilus_file_get_uri (file); + selection = g_list_prepend (NULL, file); + + if (nautilus_tag_manager_file_is_favorite (list_view->details->tag_manager, uri)) + { + nautilus_tag_manager_unstar_files (list_view->details->tag_manager, + G_OBJECT (list_view), + selection, + on_favorite_tags_updated, + list_view->details->favorite_cancellable); + } + else + { + nautilus_tag_manager_star_files (list_view->details->tag_manager, + G_OBJECT (list_view), + selection, + on_favorite_tags_updated, + list_view->details->favorite_cancellable); + } + + nautilus_file_list_free (selection); +} + static gboolean button_press_callback (GtkWidget *widget, GdkEventButton *event, @@ -702,6 +749,33 @@ button_press_callback (GtkWidget *widget, } } + if (is_simple_click) + { + GtkTreeViewColumn *column; + gchar **visible_columns; + gint i; + gdouble m; + + visible_columns = get_visible_columns (view); + + for (i = 0; visible_columns[i] != NULL; i++) + { + column = g_hash_table_lookup (view->details->columns, visible_columns[i]); + + if (g_strcmp0 (visible_columns[i], "favorite") == 0) + { + m = gtk_tree_view_column_get_width (column) / 2 + + gtk_tree_view_column_get_x_offset (column); + + if (event->x > m - 10 && event->x < m + 10) + { + on_star_cell_renderer_clicked (path, view); + } + } + + } + } + gtk_tree_path_free (path); /* We chained to the default handler in this method, so never @@ -1545,6 +1619,45 @@ apply_columns_settings (NautilusListView *list_view, g_list_free (view_columns); } +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; + + 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); + + uri = nautilus_file_get_uri (file); + + if (nautilus_tag_manager_file_is_favorite (view->details->tag_manager, uri)) + { + g_object_set (renderer, + "icon-name", "starred-symbolic", + NULL); + } + else + { + g_object_set (renderer, + "icon-name", "non-starred-symbolic", + NULL); + + } + + nautilus_file_unref (file); +} + static void filename_cell_data_func (GtkTreeViewColumn *column, GtkCellRenderer *renderer, @@ -2029,13 +2142,34 @@ 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 = gtk_cell_renderer_pixbuf_new (); + g_object_set (cell, + "icon-name", "non-starred-symbolic", + "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, + NULL); + + 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 +2183,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 +2210,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); @@ -3170,6 +3307,18 @@ nautilus_list_view_get_zoom_level_percentage (NautilusFilesView *view) return (gfloat) icon_size / NAUTILUS_LIST_ICON_SIZE_STANDARD; } +static gboolean +nautilus_list_view_is_zoom_level_default (NautilusFilesView *view) +{ + NautilusListView *list_view; + guint icon_size; + + list_view = NAUTILUS_LIST_VIEW (view); + icon_size = nautilus_list_model_get_icon_size_for_zoom_level (list_view->details->zoom_level); + + return icon_size == NAUTILUS_LIST_ICON_SIZE_STANDARD; +} + static void nautilus_list_view_click_policy_changed (NautilusFilesView *directory_view) { @@ -3355,6 +3504,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); } @@ -3560,6 +3712,7 @@ nautilus_list_view_class_init (NautilusListViewClass *class) nautilus_files_view_class->can_zoom_in = nautilus_list_view_can_zoom_in; nautilus_files_view_class->can_zoom_out = nautilus_list_view_can_zoom_out; nautilus_files_view_class->get_zoom_level_percentage = nautilus_list_view_get_zoom_level_percentage; + nautilus_files_view_class->is_zoom_level_default = nautilus_list_view_is_zoom_level_default; nautilus_files_view_class->click_policy_changed = nautilus_list_view_click_policy_changed; nautilus_files_view_class->clear = nautilus_list_view_clear; nautilus_files_view_class->file_changed = nautilus_list_view_file_changed; @@ -3637,6 +3790,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-list-view.h b/src/nautilus-list-view.h index f77543100..cedeb024f 100644 --- a/src/nautilus-list-view.h +++ b/src/nautilus-list-view.h @@ -53,4 +53,5 @@ typedef struct { GType nautilus_list_view_get_type (void); NautilusFilesView * nautilus_list_view_new (NautilusWindowSlot *slot); +void nautilus_list_view_redraw_tree_view (NautilusFilesView *view); #endif /* NAUTILUS_LIST_VIEW_H */ diff --git a/src/nautilus-pathbar.c b/src/nautilus-pathbar.c index 60817b442..24b1685e9 100644 --- a/src/nautilus-pathbar.c +++ b/src/nautilus-pathbar.c @@ -47,7 +47,8 @@ typedef enum OTHER_LOCATIONS_BUTTON, ROOT_BUTTON, HOME_BUTTON, - MOUNT_BUTTON + MOUNT_BUTTON, + FAVORITE_LOCATION_BUTTON } ButtonType; #define BUTTON_DATA(x) ((ButtonData *) (x)) @@ -331,6 +332,11 @@ get_dir_name (ButtonData *button_data) return _("Other Locations"); } + case FAVORITE_LOCATION_BUTTON: + { + return _("Favorite Files"); + } + default: return button_data->dir_name; } @@ -1723,6 +1729,10 @@ setup_button_type (ButtonData *button_data, g_object_unref (mount); } + else if (g_strcmp0 (uri, "favorites:///") == 0) + { + button_data->type = FAVORITE_LOCATION_BUTTON; + } else { button_data->type = NORMAL_BUTTON; diff --git a/src/nautilus-query.c b/src/nautilus-query.c index 53771ce66..15a01bfd1 100644 --- a/src/nautilus-query.c +++ b/src/nautilus-query.c @@ -42,6 +42,7 @@ struct _NautilusQuery GPtrArray *date_range; NautilusQuerySearchType search_type; NautilusQuerySearchContent search_content; + gboolean search_favorite; gboolean searching; gboolean recursive; @@ -339,6 +340,7 @@ nautilus_query_init (NautilusQuery *query) query->location = g_file_new_for_path (g_get_home_dir ()); query->search_type = g_settings_get_enum (nautilus_preferences, "search-filter-time-type"); query->search_content = NAUTILUS_QUERY_SEARCH_CONTENT_SIMPLE; + query->search_favorite = FALSE; g_mutex_init (&query->prepared_words_mutex); } @@ -541,6 +543,23 @@ nautilus_query_set_search_content (NautilusQuery *query, } } +gboolean +nautilus_query_get_search_favorite (NautilusQuery *query) +{ + g_return_val_if_fail (NAUTILUS_IS_QUERY (query), FALSE); + + return query->search_favorite; +} + +void +nautilus_query_set_search_favorite (NautilusQuery *query, + gboolean search_favorite) +{ + g_return_if_fail (NAUTILUS_IS_QUERY (query)); + + query->search_favorite = search_favorite; +} + NautilusQuerySearchType nautilus_query_get_search_type (NautilusQuery *query) { diff --git a/src/nautilus-query.h b/src/nautilus-query.h index 2264f505f..5367844b2 100644 --- a/src/nautilus-query.h +++ b/src/nautilus-query.h @@ -59,6 +59,10 @@ NautilusQuerySearchContent nautilus_query_get_search_content (NautilusQuery *que void nautilus_query_set_search_content (NautilusQuery *query, NautilusQuerySearchContent content); +gboolean nautilus_query_get_search_favorite (NautilusQuery *query); +void nautilus_query_set_search_favorite (NautilusQuery *query, + gboolean search_favorite); + NautilusQuerySearchType nautilus_query_get_search_type (NautilusQuery *query); void nautilus_query_set_search_type (NautilusQuery *query, NautilusQuerySearchType type); diff --git a/src/nautilus-search-directory.c b/src/nautilus-search-directory.c index d51f4f861..e04d5cbf4 100644 --- a/src/nautilus-search-directory.c +++ b/src/nautilus-search-directory.c @@ -641,6 +641,7 @@ search_engine_hits_added (NautilusSearchEngine *engine, g_signal_connect (file, "changed", G_CALLBACK (file_changed), search), file_list = g_list_prepend (file_list, file); + g_hash_table_add (search->details->files_hash, file); } diff --git a/src/nautilus-search-engine-model.c b/src/nautilus-search-engine-model.c index b8f2a940f..aa6e47e65 100644 --- a/src/nautilus-search-engine-model.c +++ b/src/nautilus-search-engine-model.c @@ -27,6 +27,7 @@ #include "nautilus-directory-private.h" #include "nautilus-file.h" #include "nautilus-ui-utilities.h" +#include "nautilus-tag-manager.h" #define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH #include "nautilus-debug.h" @@ -137,6 +138,7 @@ model_directory_ready_cb (NautilusDirectory *directory, GDateTime *initial_date; GDateTime *end_date; GPtrArray *date_range; + NautilusTagManager *tag_manager; files = nautilus_directory_get_file_list (directory); mime_types = nautilus_query_get_mime_types (model->details->query); @@ -189,12 +191,27 @@ model_directory_ready_cb (NautilusDirectory *directory, g_ptr_array_unref (date_range); } + if (nautilus_query_get_search_favorite (model->details->query)) + { + tag_manager = nautilus_tag_manager_new (NULL, NULL, NULL); + + uri = nautilus_file_get_uri (file); + + if (!nautilus_tag_manager_file_is_favorite (tag_manager, uri)) + { + found = FALSE; + } + + g_free (uri); + } + if (found) { uri = nautilus_file_get_uri (file); hit = nautilus_search_hit_new (uri); nautilus_search_hit_set_fts_rank (hit, match); hits = g_list_prepend (hits, hit); + g_free (uri); } diff --git a/src/nautilus-search-engine-simple.c b/src/nautilus-search-engine-simple.c index ea2dbe364..f0d74eaf6 100644 --- a/src/nautilus-search-engine-simple.c +++ b/src/nautilus-search-engine-simple.c @@ -24,6 +24,7 @@ #include "nautilus-search-provider.h" #include "nautilus-search-engine-simple.h" #include "nautilus-ui-utilities.h" +#include "nautilus-tag-manager.h" #define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH #include "nautilus-debug.h" @@ -222,7 +223,8 @@ visit_directory (GFile *dir, GPtrArray *date_range; GDateTime *initial_date; GDateTime *end_date; - + NautilusTagManager *tag_manager; + gchar *uri; enumerator = g_file_enumerate_children (dir, data->mime_types != NULL ? @@ -299,11 +301,24 @@ visit_directory (GFile *dir, g_ptr_array_unref (date_range); } + if (nautilus_query_get_search_favorite (data->query)) + { + tag_manager = nautilus_tag_manager_new (NULL, NULL, NULL); + + uri = g_file_get_uri (child); + + if (!nautilus_tag_manager_file_is_favorite (tag_manager, uri)) + { + found = FALSE; + } + + g_free (uri); + } + if (found) { NautilusSearchHit *hit; GDateTime *date; - char *uri; uri = g_file_get_uri (child); hit = nautilus_search_hit_new (uri); diff --git a/src/nautilus-search-engine-tracker.c b/src/nautilus-search-engine-tracker.c index b868dbf89..54bdeeb34 100644 --- a/src/nautilus-search-engine-tracker.c +++ b/src/nautilus-search-engine-tracker.c @@ -351,6 +351,11 @@ nautilus_search_engine_tracker_start (NautilusSearchProvider *provider) g_string_append_printf (sparql, "; fts:match '\"%s\"*'", search_text); } + if (nautilus_query_get_search_favorite (tracker->details->query)) + { + g_string_append_printf (sparql, "; nao:hasTag nao:predefined-tag-favorite"); + } + if (mime_count > 0) { g_string_append (sparql, "; nie:mimeType ?mime"); diff --git a/src/nautilus-search-engine.c b/src/nautilus-search-engine.c index f2bc0126b..cc0bc3220 100644 --- a/src/nautilus-search-engine.c +++ b/src/nautilus-search-engine.c @@ -106,6 +106,7 @@ search_engine_start_real (NautilusSearchEngine *engine) nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (priv->tracker)); priv->providers_running++; #endif + if (nautilus_search_engine_model_get_model (priv->model)) { nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (priv->model)); diff --git a/src/nautilus-tag-manager.c b/src/nautilus-tag-manager.c new file mode 100644 index 000000000..6825aae8f --- /dev/null +++ b/src/nautilus-tag-manager.c @@ -0,0 +1,1399 @@ +/* nautilus-tag-manager.c + * + * Copyright (C) 2017 Alexandru Pandelea + * + * 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 . + */ + +#include "nautilus-tag-manager.h" +#include "nautilus-file.h" +#include + +#define GINT64_TO_POINTER(i) ((gpointer) (gint64) (i)) +#define GPOINTER_TO_GINT64(p) ((gint64) (p)) + +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, + GET_IDS_FOR_URLS +} OperationType; + +typedef struct +{ + GTask *task; + GList *selection; + GHashTable *ids; + GObject *object; + GAsyncReadyCallback callback; + GCancellable *cancellable; +} InsertTaskData; + +enum +{ + FAVORITES_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +static const gchar* +nautilus_tag_manager_file_with_id_changed_url (GHashTable *hash_table, + gint64 id, + const gchar *url) +{ + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + if ((gint64) value == id && g_strcmp0 (url, key) != 0) + { + return key; + } + } + + return NULL; +} + +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 +destroy_insert_task_data (gpointer data) +{ + InsertTaskData *task_data; + + task_data = data; + + nautilus_file_list_free (task_data->selection); + g_hash_table_destroy (task_data->ids); + g_free (data); +} + +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) + { + if (error->code != G_IO_ERROR_CANCELLED) + { + if (op_type == GET_ALL_TAGS) + { + g_warning ("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); + } + else if (op_type == GET_FAVORITE_FILES) + { + g_warning ("Error on getting favorite files: %s", error->message); + } + else if (op_type == GET_IDS_FOR_URLS) + { + g_warning ("Error on getting id for url: %s", error->message); + g_task_return_pointer (task, g_task_get_task_data (task), destroy_insert_task_data); + } + else + { + g_warning ("Error on getting query callback: %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); + } +} + +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_SELECTION_TAGS) + { + g_task_return_pointer (task, g_task_get_task_data (task), nautilus_tag_data_queue_free); + + } + else if (op_type == GET_FILES_WITH_TAG) + { + g_task_return_pointer (task, g_task_get_task_data (task), destroy_url_queue); + } + else if (op_type == GET_IDS_FOR_URLS) + { + g_task_return_pointer (task, g_task_get_task_data (task), destroy_insert_task_data); + } + } + } + + 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; +} + +GList* +nautilus_tag_manager_get_favorite_files (NautilusTagManager *self) +{ + return g_hash_table_get_keys (self->favorite_files); +} + +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"); + + 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; + gint64 id; + 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); + id = tracker_sparql_cursor_get_integer (cursor, 1); + + g_hash_table_insert (self->favorite_files, + g_strdup (url), + GINT64_TO_POINTER (id)); + g_signal_emit_by_name (self, "favorites-changed", NULL); + + 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}"); + + 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; + + on_query_callback (object, + result, + task, + on_get_selection_tags_cursor_callback, + GET_SELECTION_TAGS, + g_task_get_cancellable (task)); +} + +gpointer +nautilus_tag_manager_gpointer_task_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 = 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)"); + + 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); + + 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); + + 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); + + 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; + } + } + + 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 { 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 (""); + + 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)) + { + query = nautilus_tag_manager_insert_tag (self, + selection, + query, + tag_data, + FALSE); + } + } + + 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) + { + 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; + } + } + } + + 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); +} + +static void +on_get_file_ids_for_urls_cursor_callback (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + TrackerSparqlCursor *cursor; + GTask *task; + gint64 id; + const gchar *url; + gboolean success; + GList *l; + gchar *file_url; + InsertTaskData *data; + + task = user_data; + data = g_task_get_task_data (task); + + cursor = TRACKER_SPARQL_CURSOR (object); + + success = get_query_status (cursor, result, GET_IDS_FOR_URLS, task); + if (!success) + { + return; + } + + url = tracker_sparql_cursor_get_string (cursor, 0, NULL); + id = tracker_sparql_cursor_get_integer (cursor, 1); + + for (l = data->selection; l != NULL; l = l->next) + { + file_url = nautilus_file_get_uri (NAUTILUS_FILE (l->data)); + + if (g_strcmp0 (file_url, url) == 0) + { + g_hash_table_insert (data->ids, + g_strdup (url), + GINT64_TO_POINTER (id)); + + g_free (file_url); + + break; + } + + g_free (file_url); + } + + tracker_sparql_cursor_next_async (cursor, + g_task_get_cancellable (task), + on_get_file_ids_for_urls_cursor_callback, + task); + +} + + +static void +on_get_file_ids_for_urls_query_callback (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GTask *task; + + task = user_data; + + on_query_callback (object, + result, + user_data, + on_get_file_ids_for_urls_cursor_callback, + GET_IDS_FOR_URLS, + g_task_get_cancellable (task)); +} + +static void +nautilus_tag_manager_get_file_ids_for_urls (GObject *object, + GList *selection, + GTask *task) +{ + GString *query; + + query = g_string_new ("SELECT ?url tracker:id(?urn) WHERE { ?urn nie:url ?url; ."); + + query = add_selection_filter (selection, query); + + g_string_append (query, "}\n"); + + start_query_or_update (query, + on_get_file_ids_for_urls_query_callback, + task, + TRUE, + g_task_get_cancellable (task)); + + g_string_free (query, TRUE); +} + +static void +on_star_file_callback (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusTagManager *self; + GString *query; + InsertTaskData *data; + g_autoptr (GError) error = NULL; + GTask *task; + gchar* uri; + gint64 id; + GList *l; + + self = NAUTILUS_TAG_MANAGER (object); + + data = nautilus_tag_manager_gpointer_task_finish (object, res, &error); + task = user_data; + g_clear_object (&task); + + task = g_task_new (data->object, data->cancellable, data->callback, NULL); + + query = g_string_new (""); + + query = nautilus_tag_manager_insert_tag (self, + data->selection, + query, + NULL, + TRUE); + + for (l = data->selection; l != NULL; l = l->next) + { + uri = nautilus_file_get_uri (NAUTILUS_FILE (l->data)); + + if (g_hash_table_contains (data->ids, uri)) + { + id = GPOINTER_TO_GINT64 (g_hash_table_lookup (data->ids, uri)); + g_hash_table_insert (self->favorite_files, + nautilus_file_get_uri (NAUTILUS_FILE (l->data)), + GINT64_TO_POINTER (id)); + } + + g_free (uri); + } + g_signal_emit_by_name (self, "favorites-changed", NULL); + + start_query_or_update (query, + on_update_callback, + task, + FALSE, + g_task_get_cancellable (task)); + + g_string_free (query, TRUE); +} + +void +nautilus_tag_manager_star_files (NautilusTagManager *self, + GObject *object, + GList *selection, + GAsyncReadyCallback callback, + GCancellable *cancellable) +{ + GTask *task; + InsertTaskData *data; + + data = g_new0 (InsertTaskData, 1); + data->selection = nautilus_file_list_copy (selection); + data->ids = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + NULL); + data->callback = callback; + data->object = object; + data->cancellable = cancellable; + + task = g_task_new (self, cancellable, on_star_file_callback, NULL); + g_task_set_task_data (task, + data, + destroy_insert_task_data); + + nautilus_tag_manager_get_file_ids_for_urls (G_OBJECT (self), selection, task); +} + +void +nautilus_tag_manager_unstar_files (NautilusTagManager *self, + GObject *object, + GList *selection, + GAsyncReadyCallback callback, + GCancellable *cancellable) +{ + GString *query; + GTask *task; + NautilusFile *file; + gchar *uri = NULL; + GList *l; + + file = NAUTILUS_FILE (selection->data); + uri = nautilus_file_get_uri (file); + + task = g_task_new (object, cancellable, callback, NULL); + + query = g_string_new (""); + + query = nautilus_tag_manager_delete_tag (self, + selection, + query, + NULL, + TRUE); + + for (l = selection; l != NULL; l = l->next) + { + uri = nautilus_file_get_uri (NAUTILUS_FILE (l->data)); + + g_hash_table_remove (self->favorite_files, uri); + + g_free (uri); + } + g_signal_emit_by_name (self, "favorites-changed", NULL); + + start_query_or_update (query, + on_update_callback, + task, + FALSE, + cancellable); + + g_string_free (query, TRUE); +} + +void +on_tracker_notifier_events(TrackerNotifier *notifier, + GPtrArray *events, + gpointer user_data) +{ + TrackerNotifierEvent *event; + NautilusTagManager *self; + int i; + const gchar *location_uri; + const gchar *new_location_uri; + GError *error = NULL; + TrackerSparqlConnection *connection; + TrackerSparqlCursor *cursor; + GString *query; + gboolean query_has_results; + + self = NAUTILUS_TAG_MANAGER (user_data); + + for (i = 0; i < events->len; i++) + { + event = g_ptr_array_index (events, i); + + location_uri = tracker_notifier_event_get_location (event); + + query = g_string_new (""); + g_string_append_printf (query, + "SELECT ?url WHERE { ?urn nie:url ?url; nao:hasTag nao:predefined-tag-favorite . FILTER (tracker:id(?urn) = %ld)}", + tracker_notifier_event_get_id (event)); + + /* check if the file changed it's location and update hash table if so */ + new_location_uri = nautilus_tag_manager_file_with_id_changed_url (self->favorite_files, + tracker_notifier_event_get_id (event), + location_uri); + if (new_location_uri) + { + g_hash_table_remove (self->favorite_files, new_location_uri); + g_hash_table_insert (self->favorite_files, + g_strdup (location_uri), + GINT64_TO_POINTER (tracker_notifier_event_get_id (event))); + g_signal_emit_by_name (self, "favorites-changed", NULL); + } + + connection = tracker_sparql_connection_get (NULL, &error); + + if (!connection) + { + g_printerr ("Couldn't obtain a direct connection to the Tracker store: %s", + error ? error->message : "unknown error"); + g_clear_error (&error); + + return; + } + + cursor = tracker_sparql_connection_query (connection, + query->str, + NULL, + &error); + + if (error) + { + g_printerr ("Couldn't query the Tracker Store: '%s'", error->message); + + g_clear_error (&error); + + return; + } + + if (cursor) + { + query_has_results = tracker_sparql_cursor_next (cursor, NULL, &error); + + /* if no results are found, then the file isn't marked as favorite. + * If needed, update the hashtable. + */ + if (!query_has_results && location_uri && g_hash_table_contains (self->favorite_files, location_uri)) + { + g_hash_table_remove (self->favorite_files, location_uri); + g_signal_emit_by_name (self, "favorites-changed", NULL); + } + else if (query_has_results && location_uri && !g_hash_table_contains (self->favorite_files, location_uri)) + { + g_hash_table_insert (self->favorite_files, + g_strdup (location_uri), + GINT64_TO_POINTER (tracker_notifier_event_get_id (event))); + g_signal_emit_by_name (self, "favorites-changed", NULL); + } + + g_object_unref (cursor); + } + + g_object_unref (connection); + + 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); + } + + g_signal_handlers_disconnect_by_func (self->notifier, + G_CALLBACK (on_tracker_notifier_events), + self); + g_clear_object (&self->notifier); + + 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; + + signals[FAVORITES_CHANGED] = g_signal_new ("favorites-changed", + NAUTILUS_TYPE_TAG_MANAGER, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} + +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, + 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 000000000..3aa06abf5 --- /dev/null +++ b/src/nautilus-tag-manager.h @@ -0,0 +1,113 @@ +/* nautilus-tag-manager.h + * + * Copyright (C) 2017 Alexandru Pandelea + * + * 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 . + */ + +#ifndef NAUTILUS_TAG_MANAGER_H +#define NAUTILUS_TAG_MANAGER_H + +#include +#include +#include + +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); + +GList* nautilus_tag_manager_get_favorite_files (NautilusTagManager *self); + +GQueue* nautilus_tag_manager_get_all_tags (NautilusTagManager *self); + +gpointer nautilus_tag_manager_gpointer_task_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_star_files (NautilusTagManager *self, + GObject *object, + GList *selection, + GAsyncReadyCallback callback, + GCancellable *cancellable); + +void nautilus_tag_manager_unstar_files (NautilusTagManager *self, + GObject *object, + GList *selection, + 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 000000000..73893ce96 --- /dev/null +++ b/src/nautilus-tag-widget.c @@ -0,0 +1,304 @@ +/* nautilus-tag-widget.c + * + * Copyright (C) 2017 Alexandru Pandelea + * + * 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 . + */ + +#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 ("%s", 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 000000000..01ea88185 --- /dev/null +++ b/src/nautilus-tag-widget.h @@ -0,0 +1,51 @@ +/* nautilus-tag-widget.h + * + * Copyright (C) 2017 Alexandru Pandelea + * + * 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 . + */ + +#ifndef NAUTILUS_TAG_WIDGET_H +#define NAUTILUS_TAG_WIDGET_H + +#include +#include +#include +#include +#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 000000000..db194aa71 --- /dev/null +++ b/src/nautilus-tags-dialog.c @@ -0,0 +1,620 @@ +/* nautilus-tags-dialog.c + * + * Copyright (C) 2017 Alexandru Pandelea + * + * 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 . + */ + +#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) + { + 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++) + { + 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)); +} + +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; + TagData *tag_data; + GQueue *selection_tags_check; + GList *l_check; + GList *l; + TagData *tag_data_check; + NautilusTagWidget *tag_widget; + GTask *task; + + dialog = NAUTILUS_TAGS_DIALOG (object); + + if (dialog->action == NO_ACTION) + { + dialog->selection_tags = nautilus_tag_manager_gpointer_task_finish (object, res, &error); + + fill_selection_box (dialog); + } + else + { + selection_tags_check = nautilus_tag_manager_gpointer_task_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) + { + 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, + G_OBJECT (dialog), + dialog->selection, + dialog->selection_tags, + dialog->new_selection_tags, + on_tags_updated, + dialog->cancellable_update); + } + + task = user_data; + g_clear_object (&task); +} + +static void +on_tags_updated (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusTagsDialog *dialog; + g_autoptr (GError) error = NULL; + gboolean result; + GTask *task; + + dialog = NAUTILUS_TAGS_DIALOG (object); + + result = nautilus_tag_manager_update_tags_finish (object, res, &error); + + task = user_data; + g_clear_object (&task); + + if (!result) + { + g_warning ("something went wrong while updating tags"); + } + + gtk_widget_destroy (GTK_WIDGET (dialog)); + +} + +static void +on_tags_entry_activate (NautilusTagsDialog *dialog) +{ + 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); + + /* 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; + + 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); + + 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 000000000..1968bc682 --- /dev/null +++ b/src/nautilus-tags-dialog.h @@ -0,0 +1,39 @@ +/* nautilus-tags-dialog.h + * + * Copyright (C) 2017 Alexandru Pandelea + * + * 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 . + */ + +#ifndef NAUTILUS_TAGS_DIALOG_H +#define NAUTILUS_TAGS_DIALOG_H + +#include +#include +#include +#include +#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/nautilus-window.c b/src/nautilus-window.c index b2c6a8216..0e6367abe 100644 --- a/src/nautilus-window.c +++ b/src/nautilus-window.c @@ -1156,6 +1156,19 @@ places_sidebar_show_other_locations_with_flags (NautilusWindow *window, g_object_unref (location); } +static void +places_sidebar_show_starred_location (NautilusWindow *window, + GtkPlacesOpenFlags open_flags) +{ + GFile *location; + + location = g_file_new_for_uri ("favorites:///"); + + open_location_cb (window, location, open_flags); + + g_object_unref (location); +} + static GList * build_selection_list_from_gfile_list (GList *gfile_list) { @@ -2967,6 +2980,7 @@ nautilus_window_class_init (NautilusWindowClass *class) gtk_widget_class_bind_template_child_private (wclass, NautilusWindow, notification_operation_close); gtk_widget_class_bind_template_callback (wclass, places_sidebar_show_other_locations_with_flags); + gtk_widget_class_bind_template_callback (wclass, places_sidebar_show_starred_location); properties[PROP_DISABLE_CHROME] = g_param_spec_boolean ("disable-chrome", diff --git a/src/resources/nautilus.gresource.xml b/src/resources/nautilus.gresource.xml index 720bfa5d5..7f3a5e9f1 100644 --- a/src/resources/nautilus.gresource.xml +++ b/src/resources/nautilus.gresource.xml @@ -20,6 +20,7 @@ ui/nautilus-trash-is-empty.ui gtk/help-overlay.ui ui/nautilus-batch-rename-dialog.ui + ui/nautilus-tags-dialog.ui ../gtk/nautilusgtkplacesview.ui ../gtk/nautilusgtkplacesviewrow.ui ../../icons/thumbnail_frame.png diff --git a/src/resources/ui/nautilus-files-view-context-menus.ui b/src/resources/ui/nautilus-files-view-context-menus.ui index 4a53de439..377b71f57 100644 --- a/src/resources/ui/nautilus-files-view-context-menus.ui +++ b/src/resources/ui/nautilus-files-view-context-menus.ui @@ -251,6 +251,23 @@ action-disabled +
+ + Tags + view.edit-tags + action-disabled + + + Star + view.star + action-disabled + + + Unstar + view.unstar + action-disabled + +
P_roperties diff --git a/src/resources/ui/nautilus-tags-dialog.ui b/src/resources/ui/nautilus-tags-dialog.ui new file mode 100644 index 000000000..5139c7ced --- /dev/null +++ b/src/resources/ui/nautilus-tags-dialog.ui @@ -0,0 +1,157 @@ + + + + \ No newline at end of file diff --git a/src/resources/ui/nautilus-window.ui b/src/resources/ui/nautilus-window.ui index 2fc5ffcc1..74e8c6293 100644 --- a/src/resources/ui/nautilus-window.ui +++ b/src/resources/ui/nautilus-window.ui @@ -34,7 +34,9 @@ True True True + True + start -- cgit v1.2.1