From 8cbdfdfa41d896a0be6d2057b6e77fc5f384d1c0 Mon Sep 17 00:00:00 2001 From: Alexandru Pandelea Date: Wed, 28 Jun 2017 17:47:41 +0100 Subject: Add favorite files Add option to make files Favorite, by either toggling a star in the list view, or from the context menu. https://bugzilla.gnome.org/show_bug.cgi?id=786039 --- data/org.gnome.nautilus.gschema.xml | 4 +- eel/eel-vfs-extensions.c | 6 + eel/eel-vfs-extensions.h | 1 + src/meson.build | 6 +- src/nautilus-application.c | 14 + src/nautilus-bookmark-list.c | 5 + src/nautilus-column-utilities.c | 11 + src/nautilus-directory.c | 14 + src/nautilus-directory.h | 1 + src/nautilus-favorite-directory.c | 585 ++++++++++++++ src/nautilus-favorite-directory.h | 57 ++ src/nautilus-file-undo-operations.c | 221 +++++ src/nautilus-file-undo-operations.h | 26 + src/nautilus-file-utilities.c | 18 +- src/nautilus-file-utilities.h | 1 + src/nautilus-file.c | 103 ++- src/nautilus-file.h | 3 + src/nautilus-files-view.c | 161 +++- src/nautilus-list-view-private.h | 4 + src/nautilus-list-view.c | 211 ++++- src/nautilus-pathbar.c | 12 +- src/nautilus-query.c | 19 + src/nautilus-query.h | 4 + src/nautilus-search-engine-model.c | 17 + src/nautilus-search-engine-simple.c | 19 +- src/nautilus-search-engine-tracker.c | 5 + src/nautilus-tag-manager.c | 887 +++++++++++++++++++++ src/nautilus-tag-manager.h | 57 ++ src/nautilus-window.c | 14 + src/resources/nautilus.gresource.xml | 2 + .../ui/nautilus-files-view-context-menus.ui | 17 + src/resources/ui/nautilus-starred-is-empty.ui | 42 + src/resources/ui/nautilus-tags-dialog.ui | 157 ++++ src/resources/ui/nautilus-window.ui | 2 + 34 files changed, 2674 insertions(+), 32 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/resources/ui/nautilus-starred-is-empty.ui create mode 100644 src/resources/ui/nautilus-tags-dialog.ui diff --git a/data/org.gnome.nautilus.gschema.xml b/data/org.gnome.nautilus.gschema.xml index 023035872..235aeab83 100644 --- a/data/org.gnome.nautilus.gschema.xml +++ b/data/org.gnome.nautilus.gschema.xml @@ -257,12 +257,12 @@ Default zoom level used by the list view. - [ 'name', 'size', 'date_modified' ] + [ 'name', 'size', 'date_modified', 'favorite'] Default list of columns visible in the list view Default list of columns visible in the list view. - [ 'name', 'size', 'type', 'owner', 'group', 'permissions', 'mime_type', 'where', 'date_modified', 'date_modified_with_time', 'date_accessed' ] + [ 'name', 'size', 'type', 'owner', 'group', 'permissions', 'mime_type', 'where', 'date_modified', 'date_modified_with_time', 'date_accessed', 'recency', 'favorite' ] Default column order in the list view Default column order in the list view. diff --git a/eel/eel-vfs-extensions.c b/eel/eel-vfs-extensions.c index 8bb33f7d2..6c626f7e3 100644 --- a/eel/eel-vfs-extensions.c +++ b/eel/eel-vfs-extensions.c @@ -37,6 +37,12 @@ #include #include +gboolean +eel_uri_is_favorites (const gchar *uri) +{ + return g_str_has_prefix (uri, "favorites:"); +} + gboolean eel_uri_is_trash (const char *uri) { diff --git a/eel/eel-vfs-extensions.h b/eel/eel-vfs-extensions.h index 8336efe51..0ad6b2be5 100644 --- a/eel/eel-vfs-extensions.h +++ b/eel/eel-vfs-extensions.h @@ -35,6 +35,7 @@ G_BEGIN_DECLS #define EEL_DESKTOP_URI "x-nautilus-desktop:" #define EEL_SEARCH_URI "x-nautilus-search:" +gboolean eel_uri_is_favorites (const char *uri); gboolean eel_uri_is_trash (const char *uri); gboolean eel_uri_is_trash_folder (const char *uri); gboolean eel_uri_is_in_trash (const char *uri); diff --git a/src/meson.build b/src/meson.build index 9f01f5ba2..46f010a0e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -259,7 +259,11 @@ libnautilus_sources = [ '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-tag-manager.c', + 'nautilus-tag-manager.h', + 'nautilus-favorite-directory.c', + 'nautilus-favorite-directory.h' ] nautilus_deps = [glib, diff --git a/src/nautilus-application.c b/src/nautilus-application.c index 98e6f35d4..adcb7d3e7 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,9 @@ typedef struct GHashTable *notifications; NautilusFileUndoManager *undo_manager; + + NautilusTagManager *tag_manager; + GCancellable *tag_manager_cancellable; } NautilusApplicationPrivate; G_DEFINE_TYPE_WITH_PRIVATE (NautilusApplication, nautilus_application, GTK_TYPE_APPLICATION); @@ -612,6 +616,11 @@ nautilus_application_finalize (GObject *object) g_clear_object (&priv->undo_manager); + g_clear_object (&priv->tag_manager); + + g_cancellable_cancel (priv->tag_manager_cancellable); + g_clear_object (&priv->tag_manager_cancellable); + G_OBJECT_CLASS (nautilus_application_parent_class)->finalize (object); } @@ -1103,6 +1112,11 @@ nautilus_application_init (NautilusApplication *self) priv->undo_manager = nautilus_file_undo_manager_new (); + priv->tag_manager_cancellable = g_cancellable_new (); + priv->tag_manager = nautilus_tag_manager_get (); + nautilus_tag_manager_set_cancellable (priv->tag_manager, + priv->tag_manager_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..22789938b 100644 --- a/src/nautilus-column-utilities.c +++ b/src/nautilus-column-utilities.c @@ -42,6 +42,7 @@ static const char *default_column_order[] = "date_modified", "date_accessed", "recency", + "favorite", NULL }; @@ -149,6 +150,16 @@ get_builtin_columns (void) "xalign", 1.0, 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)); + return columns; } diff --git a/src/nautilus-directory.c b/src/nautilus-directory.c index a1b44a1ee..6a71747d6 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" @@ -756,6 +757,19 @@ nautilus_directory_is_in_recent (NautilusDirectory *directory) return g_file_has_uri_scheme (directory->details->location, "recent"); } +gboolean +nautilus_directory_is_in_starred (NautilusDirectory *directory) +{ + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + + if (directory->details->location == NULL) + { + return FALSE; + } + + return g_file_has_uri_scheme (directory->details->location, "favorites"); +} + gboolean nautilus_directory_is_in_admin (NautilusDirectory *directory) { diff --git a/src/nautilus-directory.h b/src/nautilus-directory.h index 231384f3b..71b172f7c 100644 --- a/src/nautilus-directory.h +++ b/src/nautilus-directory.h @@ -225,6 +225,7 @@ gboolean nautilus_directory_is_local_or_fuse (NautilusDirector gboolean nautilus_directory_is_in_trash (NautilusDirectory *directory); gboolean nautilus_directory_is_in_recent (NautilusDirectory *directory); +gboolean nautilus_directory_is_in_starred (NautilusDirectory *directory); gboolean nautilus_directory_is_in_admin (NautilusDirectory *directory); /* Return false if directory contains anything besides a Nautilus metafile. diff --git a/src/nautilus-favorite-directory.c b/src/nautilus-favorite-directory.c new file mode 100644 index 000000000..7f367e45b --- /dev/null +++ b/src/nautilus-favorite-directory.c @@ -0,0 +1,585 @@ +/* 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 NautilusFavoriteDirectoryDetails +{ + 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->details->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->details->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->details->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->details->files; + while (l != NULL) + { + uri = nautilus_file_get_uri (NAUTILUS_FILE (l->data)); + + if (!nautilus_tag_manager_file_is_favorite (self->details->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->details->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->details->files) + { + self->details->files = g_list_delete_link (self->details->files, l); + l = self->details->files; + } + else + { + tmp_l = l->prev; + self->details->files = g_list_delete_link (self->details->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->details->files = g_list_prepend (self->details->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, + GList *changed_files, + gpointer user_data) +{ + NautilusFavoriteDirectory *self; + + self = NAUTILUS_FAVORITE_DIRECTORY (user_data); + + 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->details->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->details->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->details->monitor_list = g_list_prepend (favorite->details->monitor_list, monitor); + + if (callback != NULL) + { + (*callback) (directory, favorite->details->files, callback_data); + } + + for (list = favorite->details->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->details->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->details->monitor_list; list != NULL; list = list->next) + { + monitor = list->data; + + if (monitor->client != client) + continue; + + favorite->details->monitor_list = g_list_delete_link (favorite->details->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 (eel_uri_is_favorites (uri)) + { + 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->details->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->details->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->details->callback_list = g_list_remove (favorite->details->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->details->pending_callback_list = g_list_remove (favorite->details->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->details->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->details->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->details->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->details->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->details->tag_manager, + on_favorites_files_changed, + self); + + g_object_unref (self->details->tag_manager); + nautilus_file_list_free (self->details->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->details->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->details->monitor_list; monitor_list; + monitor_list = monitor_list->next) + { + monitor = monitor_list->data; + nautilus_file_monitor_remove (file, monitor); + } + } + + /* Remove search monitors */ + if (favorite->details->monitor_list) + { + for (l = favorite->details->monitor_list; l != NULL; l = l->next) + { + favorite_monitor_destroy ((FavoriteMonitor*) l->data, favorite); + } + + g_list_free (favorite->details->monitor_list); + favorite->details->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; + + g_type_class_add_private (klass, sizeof (NautilusFavoriteDirectoryDetails)); +} + +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; + + self->details = G_TYPE_INSTANCE_GET_PRIVATE (self, NAUTILUS_TYPE_FAVORITE_DIRECTORY, + NautilusFavoriteDirectoryDetails); + + tag_manager = nautilus_tag_manager_get (); + + g_signal_connect (tag_manager, + "favorites-changed", + (GCallback) on_favorites_files_changed, + self); + + self->details->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..2acf13c42 --- /dev/null +++ b/src/nautilus-favorite-directory.h @@ -0,0 +1,57 @@ +/* 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() +#define NAUTILUS_FAVORITE_DIRECTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_FAVORITE_DIRECTORY, NautilusFavoriteDirectory)) +#define NAUTILUS_FAVORITE_DIRECTORY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_FAVORITE_DIRECTORY, NautilusFavoriteDirectoryClass)) +#define NAUTILUS_IS_FAVORITE_DIRECTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_FAVORITE_DIRECTORY)) +#define NAUTILUS_IS_FAVORITE_DIRECTORY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_FAVORITE_DIRECTORY)) +#define NAUTILUS_FAVORITE_DIRECTORY_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_FAVORITE_DIRECTORY, NautilusFavoriteDirectoryClass)) + +typedef struct NautilusFavoriteDirectoryDetails NautilusFavoriteDirectoryDetails; + +typedef struct { + NautilusDirectory parent_slot; + NautilusFavoriteDirectoryDetails *details; +} NautilusFavoriteDirectory; + +typedef struct { + NautilusDirectoryClass parent_slot; +} NautilusFavoriteDirectoryClass; + +GType nautilus_favorite_directory_get_type (void); + +NautilusFavoriteDirectory* nautilus_favorite_directory_new (); + +G_END_DECLS + +#endif diff --git a/src/nautilus-file-undo-operations.c b/src/nautilus-file-undo-operations.c index e833d0578..e971274ef 100644 --- a/src/nautilus-file-undo-operations.c +++ b/src/nautilus-file-undo-operations.c @@ -33,6 +33,7 @@ #include "nautilus-file-undo-manager.h" #include "nautilus-batch-rename-dialog.h" #include "nautilus-batch-rename-utilities.h" +#include "nautilus-tag-manager.h" /* Since we use g_get_current_time for setting "orig_trash_time" in the undo @@ -1304,6 +1305,226 @@ nautilus_file_undo_info_batch_rename_set_data_post (NautilusFileUndoInfoBatchRen self->priv->new_display_names = g_list_reverse (self->priv->new_display_names); } +/* favorite files */ +G_DEFINE_TYPE (NautilusFileUndoInfoFavorites, nautilus_file_undo_info_favorites, NAUTILUS_TYPE_FILE_UNDO_INFO); + +struct _NautilusFileUndoInfoFavoritesDetails +{ + GList *files; + /* Whether the action was starring or unstarring */ + gboolean starred; +}; + +enum +{ + PROP_FILES = 1, + PROP_STARRED, + NUM_PROPERTIES +}; + +static void +favorites_strings_func (NautilusFileUndoInfo *info, + gchar **undo_label, + gchar **undo_description, + gchar **redo_label, + gchar **redo_description) +{ + NautilusFileUndoInfoFavorites *self = NAUTILUS_FILE_UNDO_INFO_FAVORITES (info); + + if (self->priv->starred) + { + *undo_description = g_strdup_printf (ngettext ("Unstar %d file", + "Unstar %d files", + g_list_length (self->priv->files)), + g_list_length (self->priv->files)); + *redo_description = g_strdup_printf (ngettext ("Star %d file", + "Star %d files", + g_list_length (self->priv->files)), + g_list_length (self->priv->files)); + *undo_label = g_strdup (_("_Undo Starring")); + *redo_label = g_strdup (_("_Redo Starring")); + } + else + { + *undo_description = g_strdup_printf (ngettext ("Star %d file", + "Star %d files", + g_list_length (self->priv->files)), + g_list_length (self->priv->files)); + *redo_description = g_strdup_printf (ngettext ("Unstar %d file", + "Unstar %d files", + g_list_length (self->priv->files)), + g_list_length (self->priv->files)); + *undo_label = g_strdup (_("_Undo Unstarring")); + *redo_label = g_strdup (_("_Redo Unstarring")); + } +} + +static void +on_undo_favorite_tags_updated (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + GTask *task; + NautilusFileUndoInfo *undo_info; + + undo_info = NAUTILUS_FILE_UNDO_INFO (object); + + task = user_data; + g_clear_object (&task); + + file_undo_info_operation_callback (NULL, NULL, NULL, undo_info); +} + +static void +favorites_redo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window) +{ + NautilusFileUndoInfoFavorites *self = NAUTILUS_FILE_UNDO_INFO_FAVORITES (info); + NautilusTagManager *tag_manager; + + tag_manager = nautilus_tag_manager_get (); + + if (self->priv->starred) + { + nautilus_tag_manager_star_files (tag_manager, + G_OBJECT (info), + self->priv->files, + on_undo_favorite_tags_updated, + NULL); + } + else + { + + nautilus_tag_manager_unstar_files (tag_manager, + G_OBJECT (info), + self->priv->files, + on_undo_favorite_tags_updated, + NULL); + } +} + +static void +favorites_undo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window) +{ + NautilusFileUndoInfoFavorites *self = NAUTILUS_FILE_UNDO_INFO_FAVORITES (info); + NautilusTagManager *tag_manager; + + tag_manager = nautilus_tag_manager_get (); + + if (self->priv->starred) + { + nautilus_tag_manager_unstar_files (tag_manager, + G_OBJECT (info), + self->priv->files, + on_undo_favorite_tags_updated, + NULL); + } + else + { + nautilus_tag_manager_star_files (tag_manager, + G_OBJECT (info), + self->priv->files, + on_undo_favorite_tags_updated, + NULL); + } +} + +static void +nautilus_file_undo_info_favorites_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusFileUndoInfoFavorites *self = NAUTILUS_FILE_UNDO_INFO_FAVORITES (object); + + switch (prop_id) + { + case PROP_FILES: + { + self->priv->files = nautilus_file_list_copy (g_value_get_pointer (value)); + } + break; + + case PROP_STARRED: + { + self->priv->starred = g_value_get_boolean (value); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_file_undo_info_favorites_init (NautilusFileUndoInfoFavorites *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, nautilus_file_undo_info_favorites_get_type (), + NautilusFileUndoInfoFavoritesDetails); +} + +static void +nautilus_file_undo_info_favorites_finalize (GObject *obj) +{ + NautilusFileUndoInfoFavorites *self = NAUTILUS_FILE_UNDO_INFO_FAVORITES (obj); + + nautilus_file_list_free (self->priv->files); + + G_OBJECT_CLASS (nautilus_file_undo_info_favorites_parent_class)->finalize (obj); +} + +static void +nautilus_file_undo_info_favorites_class_init (NautilusFileUndoInfoFavoritesClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass); + + oclass->finalize = nautilus_file_undo_info_favorites_finalize; + oclass->set_property = nautilus_file_undo_info_favorites_set_property; + + iclass->undo_func = favorites_undo_func; + iclass->redo_func = favorites_redo_func; + iclass->strings_func = favorites_strings_func; + + g_type_class_add_private (klass, sizeof (NautilusFileUndoInfoFavoritesDetails)); + + g_object_class_install_property (oclass, + PROP_FILES, + g_param_spec_pointer ("files", + "files", + "The files for which to undo star/unstar", + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (oclass, + PROP_STARRED, + g_param_spec_boolean ("starred", + "starred", + "Whether the files were starred or unstarred", + FALSE, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + +} + +NautilusFileUndoInfo * +nautilus_file_undo_info_favorites_new (GList *files, + gboolean starred) +{ + NautilusFileUndoInfoFavorites *self; + + self = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES, + "op-type", NAUTILUS_FILE_UNDO_OP_FAVORITES, + "item-count", g_list_length (files), + "files", files, + "starred", starred, + NULL); + + return NAUTILUS_FILE_UNDO_INFO (self); +} + /* trash */ G_DEFINE_TYPE (NautilusFileUndoInfoTrash, nautilus_file_undo_info_trash, NAUTILUS_TYPE_FILE_UNDO_INFO) diff --git a/src/nautilus-file-undo-operations.h b/src/nautilus-file-undo-operations.h index 630443f10..795a01f01 100644 --- a/src/nautilus-file-undo-operations.h +++ b/src/nautilus-file-undo-operations.h @@ -35,6 +35,7 @@ typedef enum { NAUTILUS_FILE_UNDO_OP_MOVE, NAUTILUS_FILE_UNDO_OP_RENAME, NAUTILUS_FILE_UNDO_OP_BATCH_RENAME, + NAUTILUS_FILE_UNDO_OP_FAVORITES, NAUTILUS_FILE_UNDO_OP_CREATE_EMPTY_FILE, NAUTILUS_FILE_UNDO_OP_CREATE_FILE_FROM_TEMPLATE, NAUTILUS_FILE_UNDO_OP_CREATE_FOLDER, @@ -217,6 +218,31 @@ void nautilus_file_undo_info_batch_rename_set_data_pre (NautilusFileUndoInfoBatc void nautilus_file_undo_info_batch_rename_set_data_post (NautilusFileUndoInfoBatchRename *self, GList *new_files); +/* favorite files */ +#define NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES (nautilus_file_undo_info_favorites_get_type ()) +#define NAUTILUS_FILE_UNDO_INFO_FAVORITES(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES, NautilusFileUndoInfoFavorites)) +#define NAUTILUS_FILE_UNDO_INFO_FAVORITES_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES, NautilusFileUndoInfoFavoritesClass)) +#define NAUTILUS_IS_FILE_UNDO_INFO_FAVORITES(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES)) +#define NAUTILUS_IS_FILE_UNDO_INFO_FAVORITES_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES)) +#define NAUTILUS_FILE_UNDO_INFO_FAVORITES_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES, NautilusFileUndoInfoFavoritesClass)) + +typedef struct _NautilusFileUndoInfoFavorites NautilusFileUndoInfoFavorites; +typedef struct _NautilusFileUndoInfoFavoritesClass NautilusFileUndoInfoFavoritesClass; +typedef struct _NautilusFileUndoInfoFavoritesDetails NautilusFileUndoInfoFavoritesDetails; + +struct _NautilusFileUndoInfoFavorites { + NautilusFileUndoInfo parent; + NautilusFileUndoInfoFavoritesDetails *priv; +}; + +struct _NautilusFileUndoInfoFavoritesClass { + NautilusFileUndoInfoClass parent_class; +}; + +GType nautilus_file_undo_info_favorites_get_type (void) G_GNUC_CONST; +NautilusFileUndoInfo *nautilus_file_undo_info_favorites_new (GList *files, + gboolean starred); + /* trash */ #define NAUTILUS_TYPE_FILE_UNDO_INFO_TRASH (nautilus_file_undo_info_trash_get_type ()) #define NAUTILUS_FILE_UNDO_INFO_TRASH(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_TRASH, NautilusFileUndoInfoTrash)) diff --git a/src/nautilus-file-utilities.c b/src/nautilus-file-utilities.c index 59add50e4..a8946914b 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 (_("Starred")); + } { 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 (eel_uri_is_favorites (uri)) + 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 da165cd73..44defe174 100644 --- a/src/nautilus-file-utilities.h +++ b/src/nautilus-file-utilities.h @@ -45,6 +45,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 2d668c1ce..e8a631423 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 @@ -159,7 +160,8 @@ static GQuark attribute_name_q, attribute_where_q, attribute_link_target_q, attribute_volume_q, - attribute_free_space_q; + attribute_free_space_q, + attribute_favorite_q; static void nautilus_file_info_iface_init (NautilusFileInfoIface *iface); static char *nautilus_file_get_owner_as_string (NautilusFile *file, @@ -3658,6 +3660,39 @@ 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_get (); + + 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) + { + 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) @@ -3861,6 +3896,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); @@ -3965,6 +4010,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, @@ -4572,6 +4624,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 (_("Starred")); + return g_strdup (nautilus_file_peek_display_name (file)); } @@ -7604,6 +7661,11 @@ nautilus_file_get_string_attribute_with_default_q (NautilusFile *file, /* If n/a */ return g_strdup (""); } + if (attribute_q == attribute_favorite_q) + { + /* If n/a */ + return g_strdup (""); + } /* Fallback, use for both unknown attributes and attributes * for which we have no more appropriate default. @@ -8293,6 +8355,23 @@ nautilus_file_is_in_recent (NautilusFile *file) return nautilus_directory_is_in_recent (file->details->directory); } +/** + * nautilus_file_is_in_starred + * + * Check if this file is a file in Starred. + * @file: NautilusFile representing the file in question. + * + * Returns: TRUE if @file is in Starred. + * + **/ +gboolean +nautilus_file_is_in_starred (NautilusFile *file) +{ + g_assert (NAUTILUS_IS_FILE (file)); + + return nautilus_directory_is_in_starred (file->details->directory); +} + static const gchar * const remote_types[] = { "afp", @@ -8351,6 +8430,27 @@ 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) +{ + g_autofree gchar *uri = NULL; + + g_assert (NAUTILUS_IS_FILE (file)); + + uri = nautilus_file_get_uri (file); + + return eel_uri_is_favorites (uri); +} + /** * nautilus_file_is_in_admin * @@ -9366,6 +9466,7 @@ nautilus_file_class_init (NautilusFileClass *class) attribute_link_target_q = g_quark_from_static_string ("link_target"); attribute_volume_q = g_quark_from_static_string ("volume"); attribute_free_space_q = g_quark_from_static_string ("free_space"); + attribute_favorite_q = g_quark_from_static_string ("favorite"); G_OBJECT_CLASS (class)->finalize = finalize; G_OBJECT_CLASS (class)->constructor = nautilus_file_constructor; diff --git a/src/nautilus-file.h b/src/nautilus-file.h index 3a83bd4de..464c66e0e 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, @@ -215,9 +216,11 @@ gboolean nautilus_file_is_archive (NautilusFile *file); gboolean nautilus_file_is_in_search (NautilusFile *file); gboolean nautilus_file_is_in_trash (NautilusFile *file); gboolean nautilus_file_is_in_recent (NautilusFile *file); +gboolean nautilus_file_is_in_starred (NautilusFile *file); 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 b5402a87b..c195840a5 100644 --- a/src/nautilus-files-view.c +++ b/src/nautilus-files-view.c @@ -42,6 +42,7 @@ #include "nautilus-window.h" #include "nautilus-toolbar.h" #include "nautilus-view.h" +#include "nautilus-tag-manager.h" #ifdef HAVE_X11_XF86KEYSYM_H #include @@ -70,6 +71,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" @@ -254,6 +256,7 @@ typedef struct GtkWidget *folder_is_empty_widget; GtkWidget *trash_is_empty_widget; GtkWidget *no_search_results_widget; + GtkWidget *starred_is_empty_widget; /* Floating bar */ guint floating_bar_set_status_timeout_id; @@ -272,6 +275,9 @@ typedef struct gulong stop_signal_handler; gulong reload_signal_handler; + + GCancellable *favorite_cancellable; + NautilusTagManager *tag_manager; } NautilusFilesViewPrivate; typedef struct @@ -720,6 +726,19 @@ showing_recent_directory (NautilusFilesView *view) return FALSE; } +static gboolean +showing_starred_directory (NautilusFilesView *view) +{ + NautilusFile *file; + + file = nautilus_files_view_get_directory_as_file (view); + if (file != NULL) + { + return nautilus_file_is_in_starred (file); + } + return FALSE; +} + static gboolean nautilus_files_view_supports_creating_files (NautilusFilesView *view) { @@ -727,7 +746,8 @@ nautilus_files_view_supports_creating_files (NautilusFilesView *view) return !nautilus_files_view_is_read_only (view) && !showing_trash_directory (view) - && !showing_recent_directory (view); + && !showing_recent_directory (view) + && !showing_starred_directory (view); } static gboolean @@ -1534,6 +1554,46 @@ action_delete (GSimpleAction *action, delete_selected_files (NAUTILUS_FILES_VIEW (user_data)); } +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, + NULL, + 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, + NULL, + priv->favorite_cancellable); +} + static void action_restore_from_trash (GSimpleAction *action, GVariant *state, @@ -3191,6 +3251,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); } @@ -3439,6 +3502,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); @@ -3479,6 +3546,7 @@ real_check_empty_states (NautilusFilesView *view) gtk_widget_hide (priv->no_search_results_widget); gtk_widget_hide (priv->folder_is_empty_widget); gtk_widget_hide (priv->trash_is_empty_widget); + gtk_widget_hide (priv->starred_is_empty_widget); if (!priv->loading && nautilus_files_view_is_empty (view)) @@ -3493,6 +3561,10 @@ real_check_empty_states (NautilusFilesView *view) { gtk_widget_show (priv->trash_is_empty_widget); } + else if (eel_uri_is_favorites (uri)) + { + gtk_widget_show (priv->starred_is_empty_widget); + } else { gtk_widget_show (priv->folder_is_empty_widget); @@ -6979,6 +7051,8 @@ const GActionEntry view_entries[] = { "copy-to", action_copy_to}, { "move-to-trash", action_move_to_trash}, { "delete-from-trash", action_delete }, + { "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 @@ -7058,6 +7132,7 @@ on_clipboard_contents_received (GtkClipboard *clipboard, gboolean settings_show_create_link; gboolean is_read_only; gboolean selection_contains_recent; + gboolean selection_contains_starred; GAction *action; view = NAUTILUS_FILES_VIEW (user_data); @@ -7075,9 +7150,10 @@ on_clipboard_contents_received (GtkClipboard *clipboard, NAUTILUS_PREFERENCES_SHOW_CREATE_LINK); is_read_only = nautilus_files_view_is_read_only (view); selection_contains_recent = showing_recent_directory (view); + selection_contains_starred = showing_starred_directory (view); can_link_from_copied_files = !nautilus_clipboard_is_cut_from_selection_data (selection_data) && - !selection_contains_recent && !is_read_only && - gtk_selection_data_get_length (selection_data) > 0; + !selection_contains_recent && !selection_contains_starred && + !is_read_only && gtk_selection_data_get_length (selection_data) > 0; action = g_action_map_lookup_action (G_ACTION_MAP (priv->view_action_group), "create-link"); @@ -7364,6 +7440,7 @@ real_update_actions_state (NautilusFilesView *view) gboolean selection_contains_desktop_or_home_dir; gboolean selection_contains_recent; gboolean selection_contains_search; + gboolean selection_contains_starred; gboolean selection_all_in_trash; gboolean selection_is_read_only; gboolean can_create_files; @@ -7388,6 +7465,9 @@ real_update_actions_state (NautilusFilesView *view) gboolean settings_show_delete_permanently; gboolean settings_show_create_link; GDriveStartStopType start_stop_type; + gboolean show_star; + gboolean show_unstar; + gchar *uri; priv = nautilus_files_view_get_instance_private (view); @@ -7398,6 +7478,7 @@ real_update_actions_state (NautilusFilesView *view) selection_contains_special_link = nautilus_files_view_special_link_in_selection (view, selection); selection_contains_desktop_or_home_dir = desktop_or_home_dir_in_selection (selection); selection_contains_recent = showing_recent_directory (view); + selection_contains_starred = showing_starred_directory (view); selection_contains_search = nautilus_view_is_searching (NAUTILUS_VIEW (view)); selection_is_read_only = selection_count == 1 && (!nautilus_file_can_write (NAUTILUS_FILE (selection->data)) && @@ -7419,8 +7500,10 @@ real_update_actions_state (NautilusFilesView *view) !selection_contains_desktop_or_home_dir; can_copy_files = selection_count != 0 && !selection_contains_special_link; - can_move_files = can_delete_files && !selection_contains_recent; + can_move_files = can_delete_files && !selection_contains_recent && + !selection_contains_starred; can_paste_files_into = (!selection_contains_recent && + !selection_contains_starred && selection_count == 1 && can_paste_into_file (NAUTILUS_FILE (selection->data))); can_extract_files = selection_count != 0 && @@ -7436,7 +7519,8 @@ real_update_actions_state (NautilusFilesView *view) action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "new-folder-with-selection"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), - can_create_files && can_delete_files && (selection_count > 1) && !selection_contains_recent); + can_create_files && can_delete_files && (selection_count > 1) && !selection_contains_recent + && !selection_contains_starred); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "rename"); @@ -7484,7 +7568,8 @@ real_update_actions_state (NautilusFilesView *view) g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selection_count == 1 && - (selection_contains_recent || selection_contains_search)); + (selection_contains_recent || selection_contains_search || + selection_contains_starred)); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "new-folder"); @@ -7550,14 +7635,16 @@ real_update_actions_state (NautilusFilesView *view) "delete-permanently-menu-item"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_delete_files && !can_trash_files && - !selection_all_in_trash && !selection_contains_recent); + !selection_all_in_trash && !selection_contains_recent && + !selection_contains_starred); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "permanent-delete-permanently-menu-item"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_delete_files && can_trash_files && settings_show_delete_permanently && - !selection_all_in_trash && !selection_contains_recent); + !selection_all_in_trash && !selection_contains_recent && + !selection_contains_starred); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "remove-from-recent"); @@ -7567,7 +7654,8 @@ real_update_actions_state (NautilusFilesView *view) action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "cut"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), - can_move_files && !selection_contains_recent); + can_move_files && !selection_contains_recent && + !selection_contains_starred); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "copy"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), @@ -7585,7 +7673,8 @@ real_update_actions_state (NautilusFilesView *view) action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "move-to"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), - can_move_files && !selection_contains_recent); + can_move_files && !selection_contains_recent && + !selection_contains_starred); /* Drive menu */ show_mount = (selection != NULL); @@ -7662,13 +7751,14 @@ real_update_actions_state (NautilusFilesView *view) action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "paste"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), - !is_read_only && !selection_contains_recent); + !is_read_only && !selection_contains_recent && + !selection_contains_starred); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "paste-into"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !selection_is_read_only && !selection_contains_recent && - can_paste_files_into); + can_paste_files_into && !selection_contains_starred); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "properties"); @@ -7679,6 +7769,7 @@ real_update_actions_state (NautilusFilesView *view) g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_create_files && !selection_contains_recent && + !selection_contains_starred && priv->templates_present); /* Actions that are related to the clipboard need request, request the data @@ -7722,6 +7813,38 @@ 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 (!show_star && !show_unstar) + { + break; + } + + 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); } @@ -7996,7 +8119,8 @@ static void update_background_menu (NautilusFilesView *view) { if (nautilus_files_view_supports_creating_files (view) && - !showing_recent_directory (view)) + !showing_recent_directory (view) && + !showing_starred_directory (view)) { update_templates_menu (view); } @@ -9554,6 +9678,14 @@ nautilus_files_view_init (NautilusFilesView *view) TRUE); g_object_unref (builder); + builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-starred-is-empty.ui"); + priv->starred_is_empty_widget = GTK_WIDGET (gtk_builder_get_object (builder, "starred_is_empty")); + gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay), priv->starred_is_empty_widget); + gtk_overlay_set_overlay_pass_through (GTK_OVERLAY (priv->overlay), + priv->starred_is_empty_widget, + TRUE); + g_object_unref (builder); + builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-trash-is-empty.ui"); priv->trash_is_empty_widget = GTK_WIDGET (gtk_builder_get_object (builder, "trash_is_empty")); gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay), priv->trash_is_empty_widget); @@ -9695,6 +9827,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_get (); + nautilus_profile_end (NULL); } diff --git a/src/nautilus-list-view-private.h b/src/nautilus-list-view-private.h index 2638c36ec..ac0919f62 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; @@ -66,5 +67,8 @@ struct NautilusListViewDetails { GQuark last_sort_attr; GRegex *regex; + + NautilusTagManager *tag_manager; + GCancellable *favorite_cancellable; }; diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c index 759e3b158..aa2c0e56b 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 @@ -456,6 +457,90 @@ row_activated_callback (GtkTreeView *treeview, activate_selected_items (view); } +gboolean +check_starred_status (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + NautilusFile *file; + GList *l; + GList *changed_files; + + changed_files = data; + + gtk_tree_model_get (GTK_TREE_MODEL (model), + iter, + NAUTILUS_LIST_MODEL_FILE_COLUMN, &file, + -1); + + if (!file) + { + return FALSE; + } + + for (l = changed_files; l != NULL; l = l->next) + { + if (nautilus_file_compare_location (NAUTILUS_FILE (l->data), file) == 0) + { + gtk_tree_model_row_changed (model, path, iter); + } + } + + nautilus_file_unref (file); + + return FALSE; +} + +static void +on_favorites_files_changed (NautilusTagManager *tag_manager, + GList *changed_files, + gpointer user_data) +{ + NautilusListView *list_view; + + list_view = NAUTILUS_LIST_VIEW (user_data); + + gtk_tree_model_foreach (GTK_TREE_MODEL (list_view->details->model), + check_starred_status, + changed_files); +} + +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, + NULL, + list_view->details->favorite_cancellable); + } + else + { + nautilus_tag_manager_star_files (list_view->details->tag_manager, + G_OBJECT (list_view), + selection, + NULL, + list_view->details->favorite_cancellable); + } + + nautilus_file_list_free (selection); +} + static gboolean button_press_callback (GtkWidget *widget, GdkEventButton *event, @@ -702,6 +787,32 @@ button_press_callback (GtkWidget *widget, } } + if (is_simple_click) + { + GtkTreeViewColumn *column = NULL; + gdouble cell_middle_x; + + gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view->details->tree_view), + event->x, + event->y, + NULL, + &column, + NULL, + NULL); + + if (g_strcmp0 (gtk_tree_view_column_get_title (column), "Star") == 0) + { + cell_middle_x = gtk_tree_view_column_get_width (column) / 2 + + gtk_tree_view_column_get_x_offset (column); + + if (event->x > cell_middle_x - 10 && + event->x < cell_middle_x + 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 +1656,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, @@ -2039,13 +2189,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") == 0) + { + 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, @@ -2059,10 +2230,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, @@ -2089,6 +2257,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); @@ -3376,6 +3550,13 @@ nautilus_list_view_finalize (GObject *object) g_regex_unref (list_view->details->regex); + g_cancellable_cancel (list_view->details->favorite_cancellable); + g_clear_object (&list_view->details->favorite_cancellable); + + g_signal_handlers_disconnect_by_func (list_view->details->tag_manager, + on_favorites_files_changed, + list_view); + g_free (list_view->details); G_OBJECT_CLASS (nautilus_list_view_parent_class)->finalize (object); @@ -3663,6 +3844,14 @@ nautilus_list_view_init (NautilusListView *list_view) "zoom-to-level", g_variant_new_int32 (get_default_zoom_level ())); list_view->details->regex = g_regex_new ("\\R+", 0, G_REGEX_MATCH_NEWLINE_ANY, NULL); + + list_view->details->tag_manager = nautilus_tag_manager_get (); + list_view->details->favorite_cancellable = g_cancellable_new (); + + g_signal_connect (list_view->details->tag_manager, + "favorites-changed", + (GCallback) on_favorites_files_changed, + list_view); } NautilusFilesView * diff --git a/src/nautilus-pathbar.c b/src/nautilus-pathbar.c index 4217913d8..6250a8ec6 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)) @@ -434,6 +435,11 @@ get_dir_name (ButtonData *button_data) return _("Other Locations"); } + case FAVORITE_LOCATION_BUTTON: + { + return _("Starred"); + } + default: return button_data->dir_name; } @@ -1826,6 +1832,10 @@ setup_button_type (ButtonData *button_data, g_object_unref (mount); } + else if (nautilus_is_favorite_directory (location)) + { + 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 59600d195..62f7badbf 100644 --- a/src/nautilus-query.c +++ b/src/nautilus-query.c @@ -46,6 +46,7 @@ struct _NautilusQuery GPtrArray *date_range; NautilusQuerySearchType search_type; NautilusQuerySearchContent search_content; + gboolean search_favorite; gboolean searching; gboolean recursive; @@ -343,6 +344,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); } @@ -550,6 +552,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-engine-model.c b/src/nautilus-search-engine-model.c index b00f3f7c5..f6330daaf 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" @@ -139,6 +140,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->query); @@ -191,12 +193,27 @@ model_directory_ready_cb (NautilusDirectory *directory, g_ptr_array_unref (date_range); } + if (nautilus_query_get_search_favorite (model->query)) + { + tag_manager = nautilus_tag_manager_get (); + + 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..fee7d2633 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_get (); + + 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 4c917ddf5..5954c1fa6 100644 --- a/src/nautilus-search-engine-tracker.c +++ b/src/nautilus-search-engine-tracker.c @@ -353,6 +353,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->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-tag-manager.c b/src/nautilus-tag-manager.c new file mode 100644 index 000000000..9fdad5b52 --- /dev/null +++ b/src/nautilus-tag-manager.c @@ -0,0 +1,887 @@ +/* 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 "nautilus-file-undo-operations.h" +#include "nautilus-file-undo-manager.h" + +#include + +struct _NautilusTagManager +{ + GObject object; + + TrackerNotifier *notifier; + GError *notifier_error; + + GHashTable *favorite_files; + GCancellable *cancellable; +}; + +G_DEFINE_TYPE (NautilusTagManager, nautilus_tag_manager, G_TYPE_OBJECT); + +static NautilusTagManager *tag_manager = NULL; + +typedef enum +{ + GET_FAVORITE_FILES, + GET_IDS_FOR_URLS +} OperationType; + +typedef struct +{ + GTask *task; + GList *selection; + GHashTable *ids; + GObject *object; + GAsyncReadyCallback callback; + GCancellable *cancellable; +} InsertTaskData; + +typedef struct +{ + NautilusTagManager *tag_manager; + GTask *task; + GList *selection; + gboolean star; + GHashTable *ids; +} UpdateData; + +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; +} + +void +destroy_insert_task_data (gpointer data) +{ + InsertTaskData *task_data; + + task_data = data; + + nautilus_file_list_free (task_data->selection); + g_free (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_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), NULL); + g_object_unref (task); + } + 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; + UpdateData *data; + gint64 *id; + GList *l; + gchar *uri; + + data = 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)) + { + for (l = data->selection; l != NULL; l = l->next) + { + uri = nautilus_file_get_uri (NAUTILUS_FILE (l->data)); + + if (data->star) + { + if (g_hash_table_contains (data->ids, uri)) + { + id = g_new0 (gint64, 1); + + *id = (gint64) g_hash_table_lookup (data->ids, uri); + g_hash_table_insert (data->tag_manager->favorite_files, + nautilus_file_get_uri (NAUTILUS_FILE (l->data)), + id); + } + } + else + { + g_hash_table_remove (data->tag_manager->favorite_files, uri); + } + + g_free (uri); + } + + if (!nautilus_file_undo_manager_is_operating ()) + { + NautilusFileUndoInfo *undo_info; + + undo_info = nautilus_file_undo_info_favorites_new (data->selection, data->star); + nautilus_file_undo_manager_set_action (undo_info); + + g_object_unref (undo_info); + } + + g_signal_emit_by_name (data->tag_manager, "favorites-changed", nautilus_file_list_copy (data->selection)); + + g_task_return_boolean (data->task, TRUE); + g_object_unref (data->task); + } + else if (error && error->code == G_IO_ERROR_CANCELLED) + { + g_error_free (error); + } + else + { + g_task_return_error (data->task, error); + g_object_unref (data->task); + g_warning ("error updating tags: %s", error->message); + } + + if (data->ids) + { + g_hash_table_destroy (data->ids); + } + nautilus_file_list_free (data->selection); + g_free (data); +} + +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_IDS_FOR_URLS) + { + g_task_return_pointer (task, g_task_get_task_data (task), NULL); + g_object_unref (task); + } + } + } + + return success; +} + +GList* +nautilus_tag_manager_get_favorite_files (NautilusTagManager *self) +{ + return g_hash_table_get_keys (self->favorite_files); +} + +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; + GList *changed_files; + NautilusFile *file; + + cursor = TRACKER_SPARQL_CURSOR (object); + + self = NAUTILUS_TAG_MANAGER (user_data); + + success = get_query_status (cursor, result, GET_FAVORITE_FILES, NULL); + if (!success) + { + return; + } + + id = g_new0 (gint64, 1); + + 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), + id); + + file = nautilus_file_get_by_uri (url); + changed_files = g_list_prepend (NULL, file); + + g_signal_emit_by_name (self, "favorites-changed", changed_files); + + nautilus_file_list_free (changed_files); + + tracker_sparql_cursor_next_async (cursor, + self->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->cancellable); +} + +static void +nautilus_tag_manager_query_favorite_files (NautilusTagManager *self, + GCancellable *cancellable) +{ + GString *query; + + self->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 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); +} + +static GString* +nautilus_tag_manager_delete_tag (NautilusTagManager *self, + GList *selection, + GString *query) +{ + + 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) +{ + 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_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; + } + + id = g_new0 (gint64, 1); + + 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), + 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_files_callback (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusTagManager *self; + GString *query; + InsertTaskData *data; + g_autoptr (GError) error = NULL; + GTask *task; + UpdateData *update_data; + + self = NAUTILUS_TAG_MANAGER (object); + + data = nautilus_tag_manager_gpointer_task_finish (object, res, &error); + + 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); + + update_data = g_new0 (UpdateData, 1); + update_data->task = task; + update_data->tag_manager = self; + update_data->selection = nautilus_file_list_copy (data->selection); + update_data->star = TRUE; + update_data->ids = data->ids; + + /* the ids hash table is now owned by the update_data, + * so it will be freed by it. + */ + destroy_insert_task_data (data); + + start_query_or_update (query, + on_update_callback, + update_data, + 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, + (GDestroyNotify) g_free); + data->callback = callback; + data->object = object; + data->cancellable = cancellable; + + task = g_task_new (self, cancellable, on_star_files_callback, NULL); + g_task_set_task_data (task, + data, + NULL); + + 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; + UpdateData *update_data; + + file = NAUTILUS_FILE (selection->data); + + task = g_task_new (object, cancellable, callback, NULL); + + query = g_string_new (""); + + query = nautilus_tag_manager_delete_tag (self, + selection, + query); + + update_data = g_new0 (UpdateData, 1); + update_data->task = task; + update_data->tag_manager = self; + update_data->selection = nautilus_file_list_copy (selection); + update_data->star = FALSE; + + start_query_or_update (query, + on_update_callback, + update_data, + 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; + gint64 *id; + GList *changed_files; + NautilusFile *file; + + 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) + { + id = g_new0 (gint64, 1); + *id = tracker_notifier_event_get_id (event); + + g_hash_table_remove (self->favorite_files, new_location_uri); + g_hash_table_insert (self->favorite_files, + g_strdup (location_uri), + id); + + file = nautilus_file_get_by_uri (location_uri); + changed_files = g_list_prepend (NULL, file); + + g_signal_emit_by_name (self, "favorites-changed", changed_files); + + nautilus_file_list_free (changed_files); + } + + 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); + + file = nautilus_file_get_by_uri (location_uri); + changed_files = g_list_prepend (NULL, file); + + g_signal_emit_by_name (self, "favorites-changed", changed_files); + + nautilus_file_list_free (changed_files); + } + else if (query_has_results && location_uri && !g_hash_table_contains (self->favorite_files, location_uri)) + { + id = g_new0 (gint64, 1); + *id = tracker_notifier_event_get_id (event); + + g_hash_table_insert (self->favorite_files, + g_strdup (location_uri), + id); + + file = nautilus_file_get_by_uri (location_uri); + changed_files = g_list_prepend (NULL, file); + + g_signal_emit_by_name (self, "favorites-changed", changed_files); + + nautilus_file_list_free (changed_files); + } + + 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); + + 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__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); +} + +NautilusTagManager* nautilus_tag_manager_get () +{ + 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); + + return tag_manager; +} + +void nautilus_tag_manager_set_cancellable (NautilusTagManager *tag_manager, + GCancellable *cancellable) +{ + nautilus_tag_manager_query_favorite_files (tag_manager, cancellable); + + tag_manager->notifier = tracker_notifier_new (NULL, + TRACKER_NOTIFIER_FLAG_QUERY_LOCATION, + cancellable, + &tag_manager->notifier_error); + + g_signal_connect (tag_manager->notifier, + "events", + G_CALLBACK (on_tracker_notifier_events), + 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, + (GDestroyNotify) g_free); +} diff --git a/src/nautilus-tag-manager.h b/src/nautilus-tag-manager.h new file mode 100644 index 000000000..4072dd083 --- /dev/null +++ b/src/nautilus-tag-manager.h @@ -0,0 +1,57 @@ +/* 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); + +NautilusTagManager* nautilus_tag_manager_get (); + +void nautilus_tag_manager_set_cancellable (NautilusTagManager *tag_manager, + GCancellable *cancellable); + +GList* nautilus_tag_manager_get_favorite_files (NautilusTagManager *self); + +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); + +G_END_DECLS + +#endif \ No newline at end of file diff --git a/src/nautilus-window.c b/src/nautilus-window.c index 653b9cfc1..df669f81a 100644 --- a/src/nautilus-window.c +++ b/src/nautilus-window.c @@ -1140,6 +1140,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) { @@ -2947,6 +2960,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 3dd60ba6d..de42f6e9d 100644 --- a/src/resources/nautilus.gresource.xml +++ b/src/resources/nautilus.gresource.xml @@ -18,8 +18,10 @@ ui/nautilus-no-search-results.ui ui/nautilus-folder-is-empty.ui ui/nautilus-trash-is-empty.ui + ui/nautilus-starred-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-starred-is-empty.ui b/src/resources/ui/nautilus-starred-is-empty.ui new file mode 100644 index 000000000..a0482a05a --- /dev/null +++ b/src/resources/ui/nautilus-starred-is-empty.ui @@ -0,0 +1,42 @@ + + + + False + 12 + True + True + center + center + + + + True + starred-symbolic + 72 + + + + 0 + 0 + + + + + True + Starred files will appear here + + + + + + + 0 + 1 + + + + 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