summaryrefslogtreecommitdiffstats
path: root/src/gtk
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/gtk/.editorconfig30
-rw-r--r--src/gtk/nautilusgtkbookmarksmanager.c643
-rw-r--r--src/gtk/nautilusgtkbookmarksmanagerprivate.h89
-rw-r--r--src/gtk/nautilusgtkplacessidebar.c5142
-rw-r--r--src/gtk/nautilusgtkplacessidebarprivate.h148
-rw-r--r--src/gtk/nautilusgtkplacesview.c2635
-rw-r--r--src/gtk/nautilusgtkplacesview.ui261
-rw-r--r--src/gtk/nautilusgtkplacesviewprivate.h55
-rw-r--r--src/gtk/nautilusgtkplacesviewrow.c508
-rw-r--r--src/gtk/nautilusgtkplacesviewrow.ui83
-rw-r--r--src/gtk/nautilusgtkplacesviewrowprivate.h59
-rw-r--r--src/gtk/nautilusgtksidebarrow.c692
-rw-r--r--src/gtk/nautilusgtksidebarrow.ui69
-rw-r--r--src/gtk/nautilusgtksidebarrowprivate.h60
14 files changed, 10474 insertions, 0 deletions
diff --git a/src/gtk/.editorconfig b/src/gtk/.editorconfig
new file mode 100644
index 0000000..e1ee505
--- /dev/null
+++ b/src/gtk/.editorconfig
@@ -0,0 +1,30 @@
+# SPDX-FileCopyrightText: 2021 The GTK Authors
+# SPDX-License-Identifier: CC0-1.0
+
+[*]
+charset = utf-8
+end_of_line = lf
+trim_trailing_whitespace = true
+
+[*.[ch]]
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+# For the legacy tabs which still exist in the code:
+tab_width = 8
+
+[*.ui]
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+
+[*.xml]
+indent_size = 2
+indent_style = space
+
+[meson.build]
+indent_size = 2
+indent_style = space
+
+[*.md]
+max_line_length = 80
diff --git a/src/gtk/nautilusgtkbookmarksmanager.c b/src/gtk/nautilusgtkbookmarksmanager.c
new file mode 100644
index 0000000..86f3bb4
--- /dev/null
+++ b/src/gtk/nautilusgtkbookmarksmanager.c
@@ -0,0 +1,643 @@
+/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
+/* GTK - The GIMP Toolkit
+ * nautilusgtkbookmarksmanager.c: Utilities to manage and monitor ~/.gtk-bookmarks
+ * Copyright (C) 2003, Red Hat, Inc.
+ * Copyright (C) 2007-2008 Carlos Garnacho
+ * Copyright (C) 2011 Suse
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Federico Mena Quintero <federico@gnome.org>
+ */
+
+#include "config.h"
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "nautilus-enum-types.h"
+
+#include <string.h>
+
+#include "nautilusgtkbookmarksmanagerprivate.h"
+
+static void
+_gtk_bookmark_free (gpointer data)
+{
+ GtkBookmark *bookmark = data;
+
+ g_object_unref (bookmark->file);
+ g_free (bookmark->label);
+ g_slice_free (GtkBookmark, bookmark);
+}
+
+static void
+set_error_bookmark_doesnt_exist (GFile *file, GError **error)
+{
+ char *uri = g_file_get_uri (file);
+
+ g_set_error (error,
+ GTK_FILE_CHOOSER_ERROR,
+ GTK_FILE_CHOOSER_ERROR_NONEXISTENT,
+ _("%s does not exist in the bookmarks list"),
+ uri);
+
+ g_free (uri);
+}
+
+static GFile *
+get_legacy_bookmarks_file (void)
+{
+ GFile *file;
+ char *filename;
+
+ filename = g_build_filename (g_get_home_dir (), ".gtk-bookmarks", NULL);
+ file = g_file_new_for_path (filename);
+ g_free (filename);
+
+ return file;
+}
+
+static GFile *
+get_bookmarks_file (void)
+{
+ GFile *file;
+ char *filename;
+
+ /* Use gtk-3.0's bookmarks file as the format didn't change.
+ * Add the 3.0 file format to get_legacy_bookmarks_file() when
+ * the format does change.
+ */
+ filename = g_build_filename (g_get_user_config_dir (), "gtk-3.0", "bookmarks", NULL);
+ file = g_file_new_for_path (filename);
+ g_free (filename);
+
+ return file;
+}
+
+static GSList *
+parse_bookmarks (const char *contents)
+{
+ char **lines, *space;
+ GSList *bookmarks = NULL;
+ int i;
+
+ lines = g_strsplit (contents, "\n", -1);
+
+ for (i = 0; lines[i]; i++)
+ {
+ GtkBookmark *bookmark;
+
+ if (!*lines[i])
+ continue;
+
+ if (!g_utf8_validate (lines[i], -1, NULL))
+ continue;
+
+ bookmark = g_slice_new0 (GtkBookmark);
+
+ if ((space = strchr (lines[i], ' ')) != NULL)
+ {
+ space[0] = '\0';
+ bookmark->label = g_strdup (space + 1);
+ }
+
+ bookmark->file = g_file_new_for_uri (lines[i]);
+ bookmarks = g_slist_prepend (bookmarks, bookmark);
+ }
+
+ bookmarks = g_slist_reverse (bookmarks);
+ g_strfreev (lines);
+
+ return bookmarks;
+}
+
+static GSList *
+read_bookmarks (GFile *file)
+{
+ char *contents;
+ GSList *bookmarks = NULL;
+
+ if (!g_file_load_contents (file, NULL, &contents,
+ NULL, NULL, NULL))
+ return NULL;
+
+ bookmarks = parse_bookmarks (contents);
+
+ g_free (contents);
+
+ return bookmarks;
+}
+
+static void
+notify_changed (NautilusGtkBookmarksManager *manager)
+{
+ if (manager->changed_func)
+ manager->changed_func (manager->changed_func_data);
+}
+
+static void
+read_bookmarks_finish (GObject *source,
+ GAsyncResult *result,
+ gpointer data)
+{
+ GFile *file = G_FILE (source);
+ NautilusGtkBookmarksManager *manager = data;
+ char *contents = NULL;
+ GError *error = NULL;
+
+ if (!g_file_load_contents_finish (file, result, &contents, NULL, NULL, &error))
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to load '%s': %s", g_file_peek_path (file), error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_slist_free_full (manager->bookmarks, _gtk_bookmark_free);
+ manager->bookmarks = parse_bookmarks (contents);
+
+ g_free (contents);
+
+ notify_changed (manager);
+}
+
+static void
+save_bookmarks (GFile *bookmarks_file,
+ GSList *bookmarks)
+{
+ GError *error = NULL;
+ GString *contents;
+ GSList *l;
+ GFile *parent = NULL;
+
+ contents = g_string_new ("");
+
+ for (l = bookmarks; l; l = l->next)
+ {
+ GtkBookmark *bookmark = l->data;
+ char *uri;
+
+ uri = g_file_get_uri (bookmark->file);
+ if (!uri)
+ continue;
+
+ g_string_append (contents, uri);
+
+ if (bookmark->label && g_utf8_validate (bookmark->label, -1, NULL))
+ g_string_append_printf (contents, " %s", bookmark->label);
+
+ g_string_append_c (contents, '\n');
+ g_free (uri);
+ }
+
+ parent = g_file_get_parent (bookmarks_file);
+ if (!g_file_make_directory_with_parents (parent, NULL, &error))
+ {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+ g_clear_error (&error);
+ else
+ goto out;
+ }
+ if (!g_file_replace_contents (bookmarks_file,
+ contents->str,
+ contents->len,
+ NULL, FALSE, 0, NULL,
+ NULL, &error))
+ goto out;
+
+ out:
+ if (error)
+ {
+ g_critical ("%s", error->message);
+ g_error_free (error);
+ }
+ g_clear_object (&parent);
+ g_string_free (contents, TRUE);
+}
+
+static void
+bookmarks_file_changed (GFileMonitor *monitor,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event,
+ gpointer data)
+{
+ NautilusGtkBookmarksManager *manager = data;
+
+ switch (event)
+ {
+ case G_FILE_MONITOR_EVENT_CHANGED:
+ case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
+ case G_FILE_MONITOR_EVENT_CREATED:
+ g_file_load_contents_async (file, manager->cancellable, read_bookmarks_finish, manager);
+ break;
+
+ case G_FILE_MONITOR_EVENT_DELETED:
+ case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
+ case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
+ case G_FILE_MONITOR_EVENT_UNMOUNTED:
+ case G_FILE_MONITOR_EVENT_MOVED:
+ case G_FILE_MONITOR_EVENT_RENAMED:
+ case G_FILE_MONITOR_EVENT_MOVED_IN:
+ case G_FILE_MONITOR_EVENT_MOVED_OUT:
+ default:
+ /* ignore at the moment */
+ break;
+ }
+}
+
+NautilusGtkBookmarksManager *
+_nautilus_gtk_bookmarks_manager_new (GtkBookmarksChangedFunc changed_func, gpointer changed_func_data)
+{
+ NautilusGtkBookmarksManager *manager;
+ GFile *bookmarks_file;
+ GError *error;
+
+ manager = g_new0 (NautilusGtkBookmarksManager, 1);
+
+ manager->changed_func = changed_func;
+ manager->changed_func_data = changed_func_data;
+
+ manager->cancellable = g_cancellable_new ();
+
+ bookmarks_file = get_bookmarks_file ();
+ if (!g_file_query_exists (bookmarks_file, NULL))
+ {
+ GFile *legacy_bookmarks_file;
+
+ /* Read the legacy one and write it to the new one */
+ legacy_bookmarks_file = get_legacy_bookmarks_file ();
+ manager->bookmarks = read_bookmarks (legacy_bookmarks_file);
+ if (manager->bookmarks)
+ save_bookmarks (bookmarks_file, manager->bookmarks);
+
+ g_object_unref (legacy_bookmarks_file);
+ }
+ else
+ g_file_load_contents_async (bookmarks_file, manager->cancellable, read_bookmarks_finish, manager);
+
+ error = NULL;
+ manager->bookmarks_monitor = g_file_monitor_file (bookmarks_file,
+ G_FILE_MONITOR_NONE,
+ NULL, &error);
+ if (error)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+ else
+ manager->bookmarks_monitor_changed_id = g_signal_connect (manager->bookmarks_monitor, "changed",
+ G_CALLBACK (bookmarks_file_changed), manager);
+
+
+ g_object_unref (bookmarks_file);
+
+ return manager;
+}
+
+void
+_nautilus_gtk_bookmarks_manager_free (NautilusGtkBookmarksManager *manager)
+{
+ g_return_if_fail (manager != NULL);
+
+ g_cancellable_cancel (manager->cancellable);
+ g_object_unref (manager->cancellable);
+
+ if (manager->bookmarks_monitor)
+ {
+ g_file_monitor_cancel (manager->bookmarks_monitor);
+ g_signal_handler_disconnect (manager->bookmarks_monitor, manager->bookmarks_monitor_changed_id);
+ manager->bookmarks_monitor_changed_id = 0;
+ g_object_unref (manager->bookmarks_monitor);
+ }
+
+ g_slist_free_full (manager->bookmarks, _gtk_bookmark_free);
+
+ g_free (manager);
+}
+
+GSList *
+_nautilus_gtk_bookmarks_manager_list_bookmarks (NautilusGtkBookmarksManager *manager)
+{
+ GSList *bookmarks, *files = NULL;
+
+ g_return_val_if_fail (manager != NULL, NULL);
+
+ bookmarks = manager->bookmarks;
+
+ while (bookmarks)
+ {
+ GtkBookmark *bookmark;
+
+ bookmark = bookmarks->data;
+ bookmarks = bookmarks->next;
+
+ files = g_slist_prepend (files, g_object_ref (bookmark->file));
+ }
+
+ return g_slist_reverse (files);
+}
+
+static GSList *
+find_bookmark_link_for_file (GSList *bookmarks, GFile *file, int *position_ret)
+{
+ int pos;
+
+ pos = 0;
+ for (; bookmarks; bookmarks = bookmarks->next)
+ {
+ GtkBookmark *bookmark = bookmarks->data;
+
+ if (g_file_equal (file, bookmark->file))
+ {
+ if (position_ret)
+ *position_ret = pos;
+
+ return bookmarks;
+ }
+
+ pos++;
+ }
+
+ if (position_ret)
+ *position_ret = -1;
+
+ return NULL;
+}
+
+gboolean
+_nautilus_gtk_bookmarks_manager_has_bookmark (NautilusGtkBookmarksManager *manager,
+ GFile *file)
+{
+ GSList *link;
+
+ link = find_bookmark_link_for_file (manager->bookmarks, file, NULL);
+ return (link != NULL);
+}
+
+gboolean
+_nautilus_gtk_bookmarks_manager_insert_bookmark (NautilusGtkBookmarksManager *manager,
+ GFile *file,
+ int position,
+ GError **error)
+{
+ GSList *link;
+ GtkBookmark *bookmark;
+ GFile *bookmarks_file;
+
+ g_return_val_if_fail (manager != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ link = find_bookmark_link_for_file (manager->bookmarks, file, NULL);
+
+ if (link)
+ {
+ char *uri;
+ bookmark = link->data;
+ uri = g_file_get_uri (bookmark->file);
+
+ g_set_error (error,
+ GTK_FILE_CHOOSER_ERROR,
+ GTK_FILE_CHOOSER_ERROR_ALREADY_EXISTS,
+ _("%s already exists in the bookmarks list"),
+ uri);
+
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ bookmark = g_slice_new0 (GtkBookmark);
+ bookmark->file = g_object_ref (file);
+
+ manager->bookmarks = g_slist_insert (manager->bookmarks, bookmark, position);
+
+ bookmarks_file = get_bookmarks_file ();
+ save_bookmarks (bookmarks_file, manager->bookmarks);
+ g_object_unref (bookmarks_file);
+
+ notify_changed (manager);
+
+ return TRUE;
+}
+
+gboolean
+_nautilus_gtk_bookmarks_manager_remove_bookmark (NautilusGtkBookmarksManager *manager,
+ GFile *file,
+ GError **error)
+{
+ GSList *link;
+ GFile *bookmarks_file;
+
+ g_return_val_if_fail (manager != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (!manager->bookmarks)
+ return FALSE;
+
+ link = find_bookmark_link_for_file (manager->bookmarks, file, NULL);
+ if (link)
+ {
+ GtkBookmark *bookmark = link->data;
+
+ manager->bookmarks = g_slist_remove_link (manager->bookmarks, link);
+ _gtk_bookmark_free (bookmark);
+ g_slist_free_1 (link);
+ }
+ else
+ {
+ set_error_bookmark_doesnt_exist (file, error);
+ return FALSE;
+ }
+
+ bookmarks_file = get_bookmarks_file ();
+ save_bookmarks (bookmarks_file, manager->bookmarks);
+ g_object_unref (bookmarks_file);
+
+ notify_changed (manager);
+
+ return TRUE;
+}
+
+gboolean
+_nautilus_gtk_bookmarks_manager_reorder_bookmark (NautilusGtkBookmarksManager *manager,
+ GFile *file,
+ int new_position,
+ GError **error)
+{
+ GSList *link;
+ GFile *bookmarks_file;
+ int old_position;
+
+ g_return_val_if_fail (manager != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+ g_return_val_if_fail (new_position >= 0, FALSE);
+
+ if (!manager->bookmarks)
+ return FALSE;
+
+ link = find_bookmark_link_for_file (manager->bookmarks, file, &old_position);
+ if (new_position == old_position)
+ return TRUE;
+
+ if (link)
+ {
+ GtkBookmark *bookmark = link->data;
+
+ manager->bookmarks = g_slist_remove_link (manager->bookmarks, link);
+ g_slist_free_1 (link);
+
+ if (new_position > old_position)
+ new_position--;
+
+ manager->bookmarks = g_slist_insert (manager->bookmarks, bookmark, new_position);
+ }
+ else
+ {
+ set_error_bookmark_doesnt_exist (file, error);
+ return FALSE;
+ }
+
+ bookmarks_file = get_bookmarks_file ();
+ save_bookmarks (bookmarks_file, manager->bookmarks);
+ g_object_unref (bookmarks_file);
+
+ notify_changed (manager);
+
+ return TRUE;
+}
+
+char *
+_nautilus_gtk_bookmarks_manager_get_bookmark_label (NautilusGtkBookmarksManager *manager,
+ GFile *file)
+{
+ GSList *bookmarks;
+ char *label = NULL;
+
+ g_return_val_if_fail (manager != NULL, NULL);
+ g_return_val_if_fail (file != NULL, NULL);
+
+ bookmarks = manager->bookmarks;
+
+ while (bookmarks)
+ {
+ GtkBookmark *bookmark;
+
+ bookmark = bookmarks->data;
+ bookmarks = bookmarks->next;
+
+ if (g_file_equal (file, bookmark->file))
+ {
+ label = g_strdup (bookmark->label);
+ break;
+ }
+ }
+
+ return label;
+}
+
+gboolean
+_nautilus_gtk_bookmarks_manager_set_bookmark_label (NautilusGtkBookmarksManager *manager,
+ GFile *file,
+ const char *label,
+ GError **error)
+{
+ GFile *bookmarks_file;
+ GSList *link;
+
+ g_return_val_if_fail (manager != NULL, FALSE);
+ g_return_val_if_fail (file != NULL, FALSE);
+
+ link = find_bookmark_link_for_file (manager->bookmarks, file, NULL);
+ if (link)
+ {
+ GtkBookmark *bookmark = link->data;
+
+ g_free (bookmark->label);
+ bookmark->label = g_strdup (label);
+ }
+ else
+ {
+ set_error_bookmark_doesnt_exist (file, error);
+ return FALSE;
+ }
+
+ bookmarks_file = get_bookmarks_file ();
+ save_bookmarks (bookmarks_file, manager->bookmarks);
+ g_object_unref (bookmarks_file);
+
+ notify_changed (manager);
+
+ return TRUE;
+}
+
+static gboolean
+_nautilus_gtk_bookmarks_manager_get_xdg_type (NautilusGtkBookmarksManager *manager,
+ GFile *file,
+ GUserDirectory *directory)
+{
+ GSList *link;
+ gboolean match;
+ GFile *location;
+ const char *path;
+ GUserDirectory dir;
+ GtkBookmark *bookmark;
+
+ link = find_bookmark_link_for_file (manager->bookmarks, file, NULL);
+ if (!link)
+ return FALSE;
+
+ match = FALSE;
+ bookmark = link->data;
+
+ for (dir = 0; dir < G_USER_N_DIRECTORIES; dir++)
+ {
+ path = g_get_user_special_dir (dir);
+ if (!path)
+ continue;
+
+ location = g_file_new_for_path (path);
+ match = g_file_equal (location, bookmark->file);
+ g_object_unref (location);
+
+ if (match)
+ break;
+ }
+
+ if (match && directory != NULL)
+ *directory = dir;
+
+ return match;
+}
+
+gboolean
+_nautilus_gtk_bookmarks_manager_get_is_builtin (NautilusGtkBookmarksManager *manager,
+ GFile *file)
+{
+ GUserDirectory xdg_type;
+
+ /* if this is not an XDG dir, it's never builtin */
+ if (!_nautilus_gtk_bookmarks_manager_get_xdg_type (manager, file, &xdg_type))
+ return FALSE;
+
+ /* exclude XDG locations we don't display by default */
+ return _nautilus_gtk_bookmarks_manager_get_is_xdg_dir_builtin (xdg_type);
+}
+
+gboolean
+_nautilus_gtk_bookmarks_manager_get_is_xdg_dir_builtin (GUserDirectory xdg_type)
+{
+ return (xdg_type != G_USER_DIRECTORY_DESKTOP) &&
+ (xdg_type != G_USER_DIRECTORY_TEMPLATES) &&
+ (xdg_type != G_USER_DIRECTORY_PUBLIC_SHARE);
+}
diff --git a/src/gtk/nautilusgtkbookmarksmanagerprivate.h b/src/gtk/nautilusgtkbookmarksmanagerprivate.h
new file mode 100644
index 0000000..99890a3
--- /dev/null
+++ b/src/gtk/nautilusgtkbookmarksmanagerprivate.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
+/* GTK - The GIMP Toolkit
+ * nautilusgtkbookmarksmanager.h: Utilities to manage and monitor ~/.gtk-bookmarks
+ * Copyright (C) 2003, Red Hat, Inc.
+ * Copyright (C) 2007-2008 Carlos Garnacho
+ * Copyright (C) 2011 Suse
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Federico Mena Quintero <federico@gnome.org>
+ */
+
+#ifndef __NAUTILUS_GTK_BOOKMARKS_MANAGER_H__
+#define __NAUTILUS_GTK_BOOKMARKS_MANAGER_H__
+
+#include <gio/gio.h>
+
+typedef void (* GtkBookmarksChangedFunc) (gpointer data);
+
+typedef struct
+{
+ /* This list contains GtkBookmark structs */
+ GSList *bookmarks;
+
+ GFileMonitor *bookmarks_monitor;
+ gulong bookmarks_monitor_changed_id;
+
+ gpointer changed_func_data;
+ GtkBookmarksChangedFunc changed_func;
+
+ GCancellable *cancellable;
+} NautilusGtkBookmarksManager;
+
+typedef struct
+{
+ GFile *file;
+ char *label;
+} GtkBookmark;
+
+NautilusGtkBookmarksManager *_nautilus_gtk_bookmarks_manager_new (GtkBookmarksChangedFunc changed_func,
+ gpointer changed_func_data);
+
+
+void _nautilus_gtk_bookmarks_manager_free (NautilusGtkBookmarksManager *manager);
+
+GSList *_nautilus_gtk_bookmarks_manager_list_bookmarks (NautilusGtkBookmarksManager *manager);
+
+gboolean _nautilus_gtk_bookmarks_manager_insert_bookmark (NautilusGtkBookmarksManager *manager,
+ GFile *file,
+ int position,
+ GError **error);
+
+gboolean _nautilus_gtk_bookmarks_manager_remove_bookmark (NautilusGtkBookmarksManager *manager,
+ GFile *file,
+ GError **error);
+
+gboolean _nautilus_gtk_bookmarks_manager_reorder_bookmark (NautilusGtkBookmarksManager *manager,
+ GFile *file,
+ int new_position,
+ GError **error);
+
+gboolean _nautilus_gtk_bookmarks_manager_has_bookmark (NautilusGtkBookmarksManager *manager,
+ GFile *file);
+
+char * _nautilus_gtk_bookmarks_manager_get_bookmark_label (NautilusGtkBookmarksManager *manager,
+ GFile *file);
+
+gboolean _nautilus_gtk_bookmarks_manager_set_bookmark_label (NautilusGtkBookmarksManager *manager,
+ GFile *file,
+ const char *label,
+ GError **error);
+
+gboolean _nautilus_gtk_bookmarks_manager_get_is_builtin (NautilusGtkBookmarksManager *manager,
+ GFile *file);
+
+gboolean _nautilus_gtk_bookmarks_manager_get_is_xdg_dir_builtin (GUserDirectory xdg_type);
+
+#endif /* __NAUTILUS_GTK_BOOKMARKS_MANAGER_H__ */
diff --git a/src/gtk/nautilusgtkplacessidebar.c b/src/gtk/nautilusgtkplacessidebar.c
new file mode 100644
index 0000000..c536e89
--- /dev/null
+++ b/src/gtk/nautilusgtkplacessidebar.c
@@ -0,0 +1,5142 @@
+/* NautilusGtkPlacesSidebar - sidebar widget for places in the filesystem
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is originally from Nautilus.
+ *
+ * Authors : Mr Jamie McCracken (jamiemcc at blueyonder dot co dot uk)
+ * Cosimo Cecchi <cosimoc@gnome.org>
+ * Federico Mena Quintero <federico@gnome.org>
+ * Carlos Soriano <csoriano@gnome.org>
+ */
+
+#include "config.h"
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "nautilus-enum-types.h"
+
+#include <gio/gio.h>
+#include <cloudproviders.h>
+
+#include "nautilusgtkplacessidebarprivate.h"
+#include "nautilusgtksidebarrowprivate.h"
+#include "gdk/gdkkeysyms.h"
+#include "nautilusgtkbookmarksmanagerprivate.h"
+#include "nautilus-dnd.h"
+#include "nautilus-dbus-launcher.h"
+#include "nautilus-file.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-properties-window.h"
+#include "nautilus-trash-monitor.h"
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/x11/gdkx.h>
+#endif
+
+#pragma GCC diagnostic ignored "-Wshadow"
+
+/*< private >
+ * NautilusGtkPlacesSidebar:
+ *
+ * NautilusGtkPlacesSidebar is a widget that displays a list of frequently-used places in the
+ * file system: the user’s home directory, the user’s bookmarks, and volumes and drives.
+ * This widget is used as a sidebar in GtkFileChooser and may be used by file managers
+ * and similar programs.
+ *
+ * The places sidebar displays drives and volumes, and will automatically mount
+ * or unmount them when the user selects them.
+ *
+ * Applications can hook to various signals in the places sidebar to customize
+ * its behavior. For example, they can add extra commands to the context menu
+ * of the sidebar.
+ *
+ * While bookmarks are completely in control of the user, the places sidebar also
+ * allows individual applications to provide extra shortcut folders that are unique
+ * to each application. For example, a Paint program may want to add a shortcut
+ * for a Clipart folder. You can do this with nautilus_gtk_places_sidebar_add_shortcut().
+ *
+ * To make use of the places sidebar, an application at least needs to connect
+ * to the NautilusGtkPlacesSidebar::open-location signal. This is emitted when the
+ * user selects in the sidebar a location to open. The application should also
+ * call nautilus_gtk_places_sidebar_set_location() when it changes the currently-viewed
+ * location.
+ *
+ * # CSS nodes
+ *
+ * NautilusGtkPlacesSidebar uses a single CSS node with name placessidebar and style
+ * class .sidebar.
+ *
+ * Among the children of the places sidebar, the following style classes can
+ * be used:
+ * - .sidebar-new-bookmark-row for the 'Add new bookmark' row
+ * - .sidebar-placeholder-row for a row that is a placeholder
+ * - .has-open-popup when a popup is open for a row
+ */
+
+/* These are used when a destination-side DND operation is taking place.
+ * Normally, when a common drag action is taking place, the state will be
+ * DROP_STATE_NEW_BOOKMARK_ARMED, however, if the client of NautilusGtkPlacesSidebar
+ * wants to show hints about the valid targets, we sill set it as
+ * DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT, so the sidebar will show drop hints
+ * until the client says otherwise
+ */
+typedef enum {
+ DROP_STATE_NORMAL,
+ DROP_STATE_NEW_BOOKMARK_ARMED,
+ DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT,
+} DropState;
+
+struct _NautilusGtkPlacesSidebar {
+ GtkWidget parent;
+
+ GtkWidget *swin;
+ GtkWidget *list_box;
+ GtkWidget *new_bookmark_row;
+
+ NautilusGtkBookmarksManager *bookmarks_manager;
+
+ GActionGroup *row_actions;
+
+ CloudProvidersCollector *cloud_manager;
+ GList *unready_accounts;
+
+ GVolumeMonitor *volume_monitor;
+ GtkSettings *gtk_settings;
+ GFile *current_location;
+
+ GtkWidget *rename_popover;
+ GtkWidget *rename_entry;
+ GtkWidget *rename_button;
+ GtkWidget *rename_error;
+ char *rename_uri;
+
+ GtkWidget *trash_row;
+
+ /* DND */
+ gboolean dragging_over;
+ GtkWidget *drag_row;
+ int drag_row_height;
+ int drag_row_x;
+ int drag_row_y;
+ GtkWidget *row_placeholder;
+ DropState drop_state;
+ guint hover_timer_id;
+ graphene_point_t hover_start_point;
+ GtkListBoxRow *hover_row;
+
+ /* volume mounting - delayed open process */
+ NautilusGtkPlacesOpenFlags go_to_after_mount_open_flags;
+ GCancellable *cancellable;
+
+ GtkWidget *popover;
+ NautilusGtkSidebarRow *context_row;
+ GListStore *shortcuts;
+
+ GDBusProxy *hostnamed_proxy;
+ GCancellable *hostnamed_cancellable;
+ char *hostname;
+
+ NautilusGtkPlacesOpenFlags open_flags;
+
+ guint mounting : 1;
+ guint show_recent_set : 1;
+ guint show_recent : 1;
+ guint show_desktop_set : 1;
+ guint show_desktop : 1;
+ guint show_enter_location : 1;
+ guint show_other_locations : 1;
+ guint show_trash : 1;
+ guint show_starred_location : 1;
+};
+
+struct _NautilusGtkPlacesSidebarClass {
+ GtkWidgetClass parent_class;
+
+ void (* open_location) (NautilusGtkPlacesSidebar *sidebar,
+ GFile *location,
+ NautilusGtkPlacesOpenFlags open_flags);
+ void (* show_error_message) (NautilusGtkPlacesSidebar *sidebar,
+ const char *primary,
+ const char *secondary);
+ GdkDragAction (* drag_action_requested) (NautilusGtkPlacesSidebar *sidebar,
+ GFile *dest_file,
+ GSList *source_file_list);
+ GdkDragAction (* drag_action_ask) (NautilusGtkPlacesSidebar *sidebar,
+ GdkDragAction actions);
+ void (* drag_perform_drop) (NautilusGtkPlacesSidebar *sidebar,
+ GFile *dest_file,
+ GList *source_file_list,
+ GdkDragAction action);
+ void (* show_enter_location) (NautilusGtkPlacesSidebar *sidebar);
+
+ void (* show_other_locations_with_flags) (NautilusGtkPlacesSidebar *sidebar,
+ NautilusGtkPlacesOpenFlags open_flags);
+
+ void (* show_starred_location) (NautilusGtkPlacesSidebar *sidebar);
+
+ void (* mount) (NautilusGtkPlacesSidebar *sidebar,
+ GMountOperation *mount_operation);
+ void (* unmount) (NautilusGtkPlacesSidebar *sidebar,
+ GMountOperation *unmount_operation);
+};
+
+enum {
+ OPEN_LOCATION,
+ SHOW_ERROR_MESSAGE,
+ SHOW_ENTER_LOCATION,
+ DRAG_ACTION_REQUESTED,
+ DRAG_ACTION_ASK,
+ DRAG_PERFORM_DROP,
+ SHOW_OTHER_LOCATIONS_WITH_FLAGS,
+ SHOW_STARRED_LOCATION,
+ MOUNT,
+ UNMOUNT,
+ LAST_SIGNAL
+};
+
+enum {
+ PROP_LOCATION = 1,
+ PROP_OPEN_FLAGS,
+ PROP_SHOW_RECENT,
+ PROP_SHOW_DESKTOP,
+ PROP_SHOW_ENTER_LOCATION,
+ PROP_SHOW_TRASH,
+ PROP_SHOW_STARRED_LOCATION,
+ PROP_SHOW_OTHER_LOCATIONS,
+ NUM_PROPERTIES
+};
+
+/* Names for themed icons */
+#define ICON_NAME_HOME "user-home-symbolic"
+#define ICON_NAME_DESKTOP "user-desktop-symbolic"
+#define ICON_NAME_FILESYSTEM "drive-harddisk-symbolic"
+#define ICON_NAME_EJECT "media-eject-symbolic"
+#define ICON_NAME_NETWORK "network-workgroup-symbolic"
+#define ICON_NAME_NETWORK_SERVER "network-server-symbolic"
+#define ICON_NAME_FOLDER_NETWORK "folder-remote-symbolic"
+#define ICON_NAME_OTHER_LOCATIONS "list-add-symbolic"
+
+#define ICON_NAME_FOLDER "folder-symbolic"
+#define ICON_NAME_FOLDER_DESKTOP "user-desktop-symbolic"
+#define ICON_NAME_FOLDER_DOCUMENTS "folder-documents-symbolic"
+#define ICON_NAME_FOLDER_DOWNLOAD "folder-download-symbolic"
+#define ICON_NAME_FOLDER_MUSIC "folder-music-symbolic"
+#define ICON_NAME_FOLDER_PICTURES "folder-pictures-symbolic"
+#define ICON_NAME_FOLDER_PUBLIC_SHARE "folder-publicshare-symbolic"
+#define ICON_NAME_FOLDER_TEMPLATES "folder-templates-symbolic"
+#define ICON_NAME_FOLDER_VIDEOS "folder-videos-symbolic"
+#define ICON_NAME_FOLDER_SAVED_SEARCH "folder-saved-search-symbolic"
+
+static guint places_sidebar_signals [LAST_SIGNAL] = { 0 };
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+static gboolean eject_or_unmount_bookmark (NautilusGtkSidebarRow *row);
+static gboolean eject_or_unmount_selection (NautilusGtkPlacesSidebar *sidebar);
+static void check_unmount_and_eject (GMount *mount,
+ GVolume *volume,
+ GDrive *drive,
+ gboolean *show_unmount,
+ gboolean *show_eject);
+static void on_row_pressed (GtkGestureClick *gesture,
+ int n_press,
+ double x,
+ double y,
+ NautilusGtkSidebarRow *row);
+static void on_row_released (GtkGestureClick *gesture,
+ int n_press,
+ double x,
+ double y,
+ NautilusGtkSidebarRow *row);
+static void on_row_dragged (GtkGestureDrag *gesture,
+ double x,
+ double y,
+ NautilusGtkSidebarRow *row);
+
+static void popup_menu_cb (NautilusGtkSidebarRow *row);
+static void long_press_cb (GtkGesture *gesture,
+ double x,
+ double y,
+ NautilusGtkPlacesSidebar *sidebar);
+static void stop_drop_feedback (NautilusGtkPlacesSidebar *sidebar);
+static GMountOperation * get_mount_operation (NautilusGtkPlacesSidebar *sidebar);
+static GMountOperation * get_unmount_operation (NautilusGtkPlacesSidebar *sidebar);
+
+
+G_DEFINE_TYPE (NautilusGtkPlacesSidebar, nautilus_gtk_places_sidebar, GTK_TYPE_WIDGET);
+
+static void
+emit_open_location (NautilusGtkPlacesSidebar *sidebar,
+ GFile *location,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ if ((open_flags & sidebar->open_flags) == 0)
+ open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL;
+
+ g_signal_emit (sidebar, places_sidebar_signals[OPEN_LOCATION], 0,
+ location, open_flags);
+}
+
+static void
+emit_show_error_message (NautilusGtkPlacesSidebar *sidebar,
+ const char *primary,
+ const char *secondary)
+{
+ g_signal_emit (sidebar, places_sidebar_signals[SHOW_ERROR_MESSAGE], 0,
+ primary, secondary);
+}
+
+static void
+emit_show_enter_location (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_signal_emit (sidebar, places_sidebar_signals[SHOW_ENTER_LOCATION], 0);
+}
+
+static void
+emit_show_other_locations_with_flags (NautilusGtkPlacesSidebar *sidebar,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ g_signal_emit (sidebar, places_sidebar_signals[SHOW_OTHER_LOCATIONS_WITH_FLAGS],
+ 0, open_flags);
+}
+
+static void
+emit_show_starred_location (NautilusGtkPlacesSidebar *sidebar,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ g_signal_emit (sidebar, places_sidebar_signals[SHOW_STARRED_LOCATION], 0,
+ open_flags);
+}
+
+
+static void
+emit_mount_operation (NautilusGtkPlacesSidebar *sidebar,
+ GMountOperation *mount_op)
+{
+ g_signal_emit (sidebar, places_sidebar_signals[MOUNT], 0, mount_op);
+}
+
+static void
+emit_unmount_operation (NautilusGtkPlacesSidebar *sidebar,
+ GMountOperation *mount_op)
+{
+ g_signal_emit (sidebar, places_sidebar_signals[UNMOUNT], 0, mount_op);
+}
+
+static GdkDragAction
+emit_drag_action_requested (NautilusGtkPlacesSidebar *sidebar,
+ NautilusFile *dest_file,
+ GSList *source_file_list)
+{
+ GdkDragAction ret_action = 0;
+
+ g_signal_emit (sidebar, places_sidebar_signals[DRAG_ACTION_REQUESTED], 0,
+ dest_file, source_file_list, &ret_action);
+
+ return ret_action;
+}
+
+static void
+emit_drag_perform_drop (NautilusGtkPlacesSidebar *sidebar,
+ GFile *dest_file,
+ GSList *source_file_list,
+ GdkDragAction action)
+{
+ g_signal_emit (sidebar, places_sidebar_signals[DRAG_PERFORM_DROP], 0,
+ dest_file, source_file_list, action);
+}
+static void
+list_box_header_func (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSectionType row_section_type;
+ NautilusGtkPlacesSectionType before_section_type;
+ GtkWidget *separator;
+
+ gtk_list_box_row_set_header (row, NULL);
+
+ g_object_get (row, "section-type", &row_section_type, NULL);
+ if (before)
+ {
+ g_object_get (before, "section-type", &before_section_type, NULL);
+ }
+ else
+ {
+ before_section_type = NAUTILUS_GTK_PLACES_SECTION_INVALID;
+ }
+
+ if (before && before_section_type != row_section_type)
+ {
+ separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_list_box_row_set_header (row, separator);
+ }
+}
+
+static GtkWidget*
+add_place (NautilusGtkPlacesSidebar *sidebar,
+ NautilusGtkPlacesPlaceType place_type,
+ NautilusGtkPlacesSectionType section_type,
+ const char *name,
+ GIcon *start_icon,
+ GIcon *end_icon,
+ const char *uri,
+ GDrive *drive,
+ GVolume *volume,
+ GMount *mount,
+ CloudProvidersAccount *cloud_provider_account,
+ const int index,
+ const char *tooltip)
+{
+ gboolean show_eject, show_unmount;
+ gboolean show_eject_button;
+ GtkWidget *row;
+ GtkWidget *eject_button;
+ GtkGesture *gesture;
+ char *eject_tooltip;
+
+ check_unmount_and_eject (mount, volume, drive,
+ &show_unmount, &show_eject);
+
+ if (show_unmount || show_eject)
+ g_assert (place_type != NAUTILUS_GTK_PLACES_BOOKMARK);
+
+ show_eject_button = (show_unmount || show_eject);
+ if (mount != NULL && volume == NULL && drive == NULL)
+ eject_tooltip = _("Disconnect");
+ else if (show_eject)
+ eject_tooltip = _("Eject");
+ else
+ eject_tooltip = _("Unmount");
+
+ row = g_object_new (NAUTILUS_TYPE_GTK_SIDEBAR_ROW,
+ "sidebar", sidebar,
+ "start-icon", start_icon,
+ "end-icon", end_icon,
+ "label", name,
+ "tooltip", tooltip,
+ "ejectable", show_eject_button,
+ "eject-tooltip", eject_tooltip,
+ "order-index", index,
+ "section-type", section_type,
+ "place-type", place_type,
+ "uri", uri,
+ "drive", drive,
+ "volume", volume,
+ "mount", mount,
+ "cloud-provider-account", cloud_provider_account,
+ NULL);
+
+ eject_button = nautilus_gtk_sidebar_row_get_eject_button (NAUTILUS_GTK_SIDEBAR_ROW (row));
+
+ g_signal_connect_swapped (eject_button, "clicked",
+ G_CALLBACK (eject_or_unmount_bookmark), row);
+
+ gesture = gtk_gesture_click_new ();
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 0);
+ g_signal_connect (gesture, "pressed",
+ G_CALLBACK (on_row_pressed), row);
+ g_signal_connect (gesture, "released",
+ G_CALLBACK (on_row_released), row);
+ gtk_widget_add_controller (row, GTK_EVENT_CONTROLLER (gesture));
+
+ gesture = gtk_gesture_drag_new ();
+ g_signal_connect (gesture, "drag-update",
+ G_CALLBACK (on_row_dragged), row);
+ gtk_widget_add_controller (row, GTK_EVENT_CONTROLLER (gesture));
+
+ gtk_list_box_insert (GTK_LIST_BOX (sidebar->list_box), GTK_WIDGET (row), -1);
+
+ return row;
+}
+
+static GIcon *
+special_directory_get_gicon (GUserDirectory directory)
+{
+#define ICON_CASE(x) \
+ case G_USER_DIRECTORY_ ## x: \
+ return g_themed_icon_new_with_default_fallbacks (ICON_NAME_FOLDER_ ## x);
+
+ switch (directory)
+ {
+
+ ICON_CASE (DESKTOP);
+ ICON_CASE (DOCUMENTS);
+ ICON_CASE (DOWNLOAD);
+ ICON_CASE (MUSIC);
+ ICON_CASE (PICTURES);
+ ICON_CASE (PUBLIC_SHARE);
+ ICON_CASE (TEMPLATES);
+ ICON_CASE (VIDEOS);
+
+ case G_USER_N_DIRECTORIES:
+ default:
+ return g_themed_icon_new_with_default_fallbacks (ICON_NAME_FOLDER);
+ }
+
+#undef ICON_CASE
+}
+
+static gboolean
+recent_files_setting_is_enabled (NautilusGtkPlacesSidebar *sidebar)
+{
+ GtkSettings *settings;
+ gboolean enabled;
+
+ settings = gtk_widget_get_settings (GTK_WIDGET (sidebar));
+ g_object_get (settings, "gtk-recent-files-enabled", &enabled, NULL);
+
+ return enabled;
+}
+
+static gboolean
+recent_scheme_is_supported (void)
+{
+ const char * const *supported;
+
+ supported = g_vfs_get_supported_uri_schemes (g_vfs_get_default ());
+ if (supported != NULL)
+ return g_strv_contains (supported, "recent");
+
+ return FALSE;
+}
+
+static gboolean
+should_show_recent (NautilusGtkPlacesSidebar *sidebar)
+{
+ return recent_files_setting_is_enabled (sidebar) &&
+ ((sidebar->show_recent_set && sidebar->show_recent) ||
+ (!sidebar->show_recent_set && recent_scheme_is_supported ()));
+}
+
+static gboolean
+path_is_home_dir (const char *path)
+{
+ GFile *home_dir;
+ GFile *location;
+ const char *home_path;
+ gboolean res;
+
+ home_path = g_get_home_dir ();
+ if (!home_path)
+ return FALSE;
+
+ home_dir = g_file_new_for_path (home_path);
+ location = g_file_new_for_path (path);
+ res = g_file_equal (home_dir, location);
+
+ g_object_unref (home_dir);
+ g_object_unref (location);
+
+ return res;
+}
+
+static void
+open_home (NautilusGtkPlacesSidebar *sidebar)
+{
+ const char *home_path;
+ GFile *home_dir;
+
+ home_path = g_get_home_dir ();
+ if (!home_path)
+ return;
+
+ home_dir = g_file_new_for_path (home_path);
+ emit_open_location (sidebar, home_dir, 0);
+
+ g_object_unref (home_dir);
+}
+
+static void
+add_special_dirs (NautilusGtkPlacesSidebar *sidebar)
+{
+ GList *dirs;
+ int index;
+
+ dirs = NULL;
+ for (index = 0; index < G_USER_N_DIRECTORIES; index++)
+ {
+ const char *path;
+ GFile *root;
+ GIcon *start_icon;
+ char *name;
+ char *mount_uri;
+ char *tooltip;
+
+ if (!_nautilus_gtk_bookmarks_manager_get_is_xdg_dir_builtin (index))
+ continue;
+
+ path = g_get_user_special_dir (index);
+
+ /* XDG resets special dirs to the home directory in case
+ * it's not finiding what it expects. We don't want the home
+ * to be added multiple times in that weird configuration.
+ */
+ if (path == NULL ||
+ path_is_home_dir (path) ||
+ g_list_find_custom (dirs, path, (GCompareFunc) g_strcmp0) != NULL)
+ continue;
+
+ root = g_file_new_for_path (path);
+
+ name = _nautilus_gtk_bookmarks_manager_get_bookmark_label (sidebar->bookmarks_manager, root);
+ if (!name)
+ name = g_file_get_basename (root);
+
+ start_icon = special_directory_get_gicon (index);
+ mount_uri = g_file_get_uri (root);
+ tooltip = g_file_get_parse_name (root);
+
+ add_place (sidebar, NAUTILUS_GTK_PLACES_XDG_DIR,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ name, start_icon, NULL, mount_uri,
+ NULL, NULL, NULL, NULL, 0,
+ tooltip);
+ g_free (name);
+ g_object_unref (root);
+ g_object_unref (start_icon);
+ g_free (mount_uri);
+ g_free (tooltip);
+
+ dirs = g_list_prepend (dirs, (char *)path);
+ }
+
+ g_list_free (dirs);
+}
+
+static char *
+get_home_directory_uri (void)
+{
+ const char *home;
+
+ home = g_get_home_dir ();
+ if (!home)
+ return NULL;
+
+ return g_filename_to_uri (home, NULL, NULL);
+}
+
+static char *
+get_desktop_directory_uri (void)
+{
+ const char *name;
+
+ name = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);
+
+ /* "To disable a directory, point it to the homedir."
+ * See http://freedesktop.org/wiki/Software/xdg-user-dirs
+ */
+ if (path_is_home_dir (name))
+ return NULL;
+
+ return g_filename_to_uri (name, NULL, NULL);
+}
+
+static gboolean
+file_is_shown (NautilusGtkPlacesSidebar *sidebar,
+ GFile *file)
+{
+ char *uri;
+ GtkWidget *row;
+ gboolean found = FALSE;
+
+ for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box));
+ row != NULL && !found;
+ row = gtk_widget_get_next_sibling (row))
+ {
+ if (!GTK_IS_LIST_BOX_ROW (row))
+ continue;
+
+ g_object_get (row, "uri", &uri, NULL);
+ if (uri)
+ {
+ GFile *other;
+ other = g_file_new_for_uri (uri);
+ found = g_file_equal (file, other);
+ g_object_unref (other);
+ g_free (uri);
+ }
+ }
+
+ return found;
+}
+
+typedef struct
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ guint position;
+} ShortcutData;
+
+static void
+on_app_shortcuts_query_complete (GObject *source,
+ GAsyncResult *result,
+ gpointer data)
+{
+ ShortcutData *sdata = data;
+ NautilusGtkPlacesSidebar *sidebar = sdata->sidebar;
+ guint pos = sdata->position;
+ GFile *file = G_FILE (source);
+ GFileInfo *info;
+
+ g_free (sdata);
+
+ info = g_file_query_info_finish (file, result, NULL);
+
+ if (info)
+ {
+ char *uri;
+ char *tooltip;
+ const char *name;
+ GIcon *start_icon;
+
+ name = g_file_info_get_display_name (info);
+ start_icon = g_file_info_get_symbolic_icon (info);
+ uri = g_file_get_uri (file);
+ tooltip = g_file_get_parse_name (file);
+
+ add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ name, start_icon, NULL, uri,
+ NULL, NULL, NULL, NULL,
+ pos,
+ tooltip);
+
+ g_free (uri);
+ g_free (tooltip);
+
+ g_object_unref (info);
+ }
+}
+
+static void
+add_application_shortcuts (NautilusGtkPlacesSidebar *sidebar)
+{
+ guint i, n;
+
+ n = g_list_model_get_n_items (G_LIST_MODEL (sidebar->shortcuts));
+ for (i = 0; i < n; i++)
+ {
+ GFile *file = g_list_model_get_item (G_LIST_MODEL (sidebar->shortcuts), i);
+ ShortcutData *data;
+
+ g_object_unref (file);
+
+ if (file_is_shown (sidebar, file))
+ continue;
+
+ data = g_new (ShortcutData, 1);
+ data->sidebar = sidebar;
+ data->position = i;
+ g_file_query_info_async (file,
+ "standard::display-name,standard::symbolic-icon",
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ sidebar->cancellable,
+ on_app_shortcuts_query_complete,
+ data);
+ }
+}
+
+typedef struct {
+ NautilusGtkPlacesSidebar *sidebar;
+ int index;
+ gboolean is_native;
+} BookmarkQueryClosure;
+
+static void
+on_bookmark_query_info_complete (GObject *source,
+ GAsyncResult *result,
+ gpointer data)
+{
+ BookmarkQueryClosure *clos = data;
+ NautilusGtkPlacesSidebar *sidebar = clos->sidebar;
+ GFile *root = G_FILE (source);
+ GError *error = NULL;
+ GFileInfo *info;
+ char *bookmark_name;
+ char *mount_uri;
+ char *tooltip;
+ GIcon *start_icon;
+
+ info = g_file_query_info_finish (root, result, &error);
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ goto out;
+
+ bookmark_name = _nautilus_gtk_bookmarks_manager_get_bookmark_label (sidebar->bookmarks_manager, root);
+ if (bookmark_name == NULL && info != NULL)
+ bookmark_name = g_strdup (g_file_info_get_display_name (info));
+ else if (bookmark_name == NULL)
+ {
+ /* Don't add non-UTF-8 bookmarks */
+ bookmark_name = g_file_get_basename (root);
+ if (bookmark_name == NULL)
+ goto out;
+
+ if (!g_utf8_validate (bookmark_name, -1, NULL))
+ {
+ g_free (bookmark_name);
+ goto out;
+ }
+ }
+
+ if (info)
+ start_icon = g_object_ref (g_file_info_get_symbolic_icon (info));
+ else
+ start_icon = g_themed_icon_new_with_default_fallbacks (clos->is_native ? ICON_NAME_FOLDER : ICON_NAME_FOLDER_NETWORK);
+
+ mount_uri = g_file_get_uri (root);
+ tooltip = clos->is_native ? g_file_get_path (root) : g_uri_unescape_string (mount_uri, NULL);
+
+ add_place (sidebar, NAUTILUS_GTK_PLACES_BOOKMARK,
+ NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS,
+ bookmark_name, start_icon, NULL, mount_uri,
+ NULL, NULL, NULL, NULL, clos->index,
+ tooltip);
+
+ g_free (mount_uri);
+ g_free (tooltip);
+ g_free (bookmark_name);
+ g_object_unref (start_icon);
+
+out:
+ g_clear_object (&info);
+ g_clear_error (&error);
+ g_slice_free (BookmarkQueryClosure, clos);
+}
+
+static gboolean
+is_external_volume (GVolume *volume)
+{
+ gboolean is_external;
+ GDrive *drive;
+ char *id;
+
+ drive = g_volume_get_drive (volume);
+ id = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);
+
+ is_external = g_volume_can_eject (volume);
+
+ /* NULL volume identifier only happens on removable devices */
+ is_external |= !id;
+
+ if (drive)
+ is_external |= g_drive_is_removable (drive);
+
+ g_clear_object (&drive);
+ g_free (id);
+
+ return is_external;
+}
+
+static void
+update_trash_icon (NautilusGtkPlacesSidebar *sidebar)
+{
+ if (sidebar->trash_row)
+ {
+ GIcon *icon;
+
+ icon = nautilus_trash_monitor_get_symbolic_icon ();
+ nautilus_gtk_sidebar_row_set_start_icon (NAUTILUS_GTK_SIDEBAR_ROW (sidebar->trash_row), icon);
+ g_object_unref (icon);
+ }
+}
+
+static gboolean
+create_cloud_provider_account_row (NautilusGtkPlacesSidebar *sidebar,
+ CloudProvidersAccount *account)
+{
+ GIcon *end_icon;
+ GIcon *start_icon;
+ const char *mount_path;
+ const char *name;
+ char *mount_uri;
+ char *tooltip;
+ guint provider_account_status;
+
+ start_icon = cloud_providers_account_get_icon (account);
+ name = cloud_providers_account_get_name (account);
+ provider_account_status = cloud_providers_account_get_status (account);
+ mount_path = cloud_providers_account_get_path (account);
+ if (start_icon != NULL
+ && name != NULL
+ && provider_account_status != CLOUD_PROVIDERS_ACCOUNT_STATUS_INVALID
+ && mount_path != NULL)
+ {
+ switch (provider_account_status)
+ {
+ case CLOUD_PROVIDERS_ACCOUNT_STATUS_IDLE:
+ end_icon = NULL;
+ break;
+
+ case CLOUD_PROVIDERS_ACCOUNT_STATUS_SYNCING:
+ end_icon = g_themed_icon_new ("emblem-synchronizing-symbolic");
+ break;
+
+ case CLOUD_PROVIDERS_ACCOUNT_STATUS_ERROR:
+ end_icon = g_themed_icon_new ("dialog-warning-symbolic");
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ mount_uri = g_strconcat ("file://", mount_path, NULL);
+
+ /* translators: %s is the name of a cloud provider for files */
+ tooltip = g_strdup_printf (_("Open %s"), name);
+
+ add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN,
+ NAUTILUS_GTK_PLACES_SECTION_CLOUD,
+ name, start_icon, end_icon, mount_uri,
+ NULL, NULL, NULL, account, 0,
+ tooltip);
+
+ g_free (tooltip);
+ g_free (mount_uri);
+ g_clear_object (&end_icon);
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+static void
+on_account_updated (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ CloudProvidersAccount *account = CLOUD_PROVIDERS_ACCOUNT (object);
+ NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (user_data);
+
+ if (create_cloud_provider_account_row (sidebar, account))
+ {
+ g_signal_handlers_disconnect_by_data (account, sidebar);
+ sidebar->unready_accounts = g_list_remove (sidebar->unready_accounts, account);
+ g_object_unref (account);
+ }
+}
+
+static void
+update_places (NautilusGtkPlacesSidebar *sidebar)
+{
+ GList *mounts, *l, *ll;
+ GMount *mount;
+ GList *drives;
+ GDrive *drive;
+ GList *volumes;
+ GVolume *volume;
+ GSList *bookmarks, *sl;
+ int index;
+ char *original_uri, *name, *identifier;
+ GtkListBoxRow *selected;
+ char *home_uri;
+ GIcon *start_icon;
+ GFile *root;
+ char *tooltip;
+ GList *network_mounts, *network_volumes;
+ GIcon *new_bookmark_icon;
+ GtkWidget *child;
+ GList *cloud_providers;
+ GList *cloud_providers_accounts;
+ CloudProvidersAccount *cloud_provider_account;
+ CloudProvidersProvider *cloud_provider;
+
+ /* save original selection */
+ selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box));
+ if (selected)
+ g_object_get (selected, "uri", &original_uri, NULL);
+ else
+ original_uri = NULL;
+
+ g_cancellable_cancel (sidebar->cancellable);
+
+ g_object_unref (sidebar->cancellable);
+ sidebar->cancellable = g_cancellable_new ();
+
+ /* Reset drag state, just in case we update the places while dragging or
+ * ending a drag */
+ stop_drop_feedback (sidebar);
+ while ((child = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box))))
+ gtk_list_box_remove (GTK_LIST_BOX (sidebar->list_box), child);
+
+ network_mounts = network_volumes = NULL;
+
+ /* add built-in places */
+ if (should_show_recent (sidebar))
+ {
+ start_icon = g_themed_icon_new_with_default_fallbacks ("document-open-recent-symbolic");
+ add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ _("Recent"), start_icon, NULL, "recent:///",
+ NULL, NULL, NULL, NULL, 0,
+ _("Recent files"));
+ g_object_unref (start_icon);
+ }
+
+ if (sidebar->show_starred_location)
+ {
+ start_icon = g_themed_icon_new_with_default_fallbacks ("starred-symbolic");
+ add_place (sidebar, NAUTILUS_GTK_PLACES_STARRED_LOCATION,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ _("Starred"), start_icon, NULL, "starred:///",
+ NULL, NULL, NULL, NULL, 0,
+ _("Starred files"));
+ g_object_unref (start_icon);
+ }
+
+ /* home folder */
+ home_uri = get_home_directory_uri ();
+ start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_HOME);
+ add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ _("Home"), start_icon, NULL, home_uri,
+ NULL, NULL, NULL, NULL, 0,
+ _("Open your personal folder"));
+ g_object_unref (start_icon);
+ g_free (home_uri);
+
+ /* desktop */
+ if (sidebar->show_desktop)
+ {
+ char *mount_uri = get_desktop_directory_uri ();
+ if (mount_uri)
+ {
+ start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_DESKTOP);
+ add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ _("Desktop"), start_icon, NULL, mount_uri,
+ NULL, NULL, NULL, NULL, 0,
+ _("Open the contents of your desktop in a folder"));
+ g_object_unref (start_icon);
+ g_free (mount_uri);
+ }
+ }
+
+ /* XDG directories */
+ add_special_dirs (sidebar);
+
+ if (sidebar->show_enter_location)
+ {
+ start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_NETWORK_SERVER);
+ add_place (sidebar, NAUTILUS_GTK_PLACES_ENTER_LOCATION,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ _("Enter Location"), start_icon, NULL, NULL,
+ NULL, NULL, NULL, NULL, 0,
+ _("Manually enter a location"));
+ g_object_unref (start_icon);
+ }
+
+ /* Trash */
+ if (sidebar->show_trash)
+ {
+ start_icon = nautilus_trash_monitor_get_symbolic_icon ();
+ sidebar->trash_row = add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ _("Trash"), start_icon, NULL, "trash:///",
+ NULL, NULL, NULL, NULL, 0,
+ _("Open the trash"));
+ g_object_add_weak_pointer (G_OBJECT (sidebar->trash_row),
+ (gpointer *) &sidebar->trash_row);
+ g_object_unref (start_icon);
+ }
+
+ /* Application-side shortcuts */
+ add_application_shortcuts (sidebar);
+
+ /* Cloud providers */
+ cloud_providers = cloud_providers_collector_get_providers (sidebar->cloud_manager);
+ for (l = sidebar->unready_accounts; l != NULL; l = l->next)
+ {
+ g_signal_handlers_disconnect_by_data (l->data, sidebar);
+ }
+ g_list_free_full (sidebar->unready_accounts, g_object_unref);
+ sidebar->unready_accounts = NULL;
+ for (l = cloud_providers; l != NULL; l = l->next)
+ {
+ cloud_provider = CLOUD_PROVIDERS_PROVIDER (l->data);
+ g_signal_connect_swapped (cloud_provider, "accounts-changed",
+ G_CALLBACK (update_places), sidebar);
+ cloud_providers_accounts = cloud_providers_provider_get_accounts (cloud_provider);
+ for (ll = cloud_providers_accounts; ll != NULL; ll = ll->next)
+ {
+ cloud_provider_account = CLOUD_PROVIDERS_ACCOUNT (ll->data);
+ if (!create_cloud_provider_account_row (sidebar, cloud_provider_account))
+ {
+
+ g_signal_connect (cloud_provider_account, "notify::name",
+ G_CALLBACK (on_account_updated), sidebar);
+ g_signal_connect (cloud_provider_account, "notify::status",
+ G_CALLBACK (on_account_updated), sidebar);
+ g_signal_connect (cloud_provider_account, "notify::status-details",
+ G_CALLBACK (on_account_updated), sidebar);
+ g_signal_connect (cloud_provider_account, "notify::path",
+ G_CALLBACK (on_account_updated), sidebar);
+ sidebar->unready_accounts = g_list_append (sidebar->unready_accounts,
+ g_object_ref (cloud_provider_account));
+ continue;
+ }
+
+ }
+ }
+
+ /* go through all connected drives */
+ drives = g_volume_monitor_get_connected_drives (sidebar->volume_monitor);
+
+ for (l = drives; l != NULL; l = l->next)
+ {
+ drive = l->data;
+
+ volumes = g_drive_get_volumes (drive);
+ if (volumes != NULL)
+ {
+ for (ll = volumes; ll != NULL; ll = ll->next)
+ {
+ volume = ll->data;
+ identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);
+
+ if (g_strcmp0 (identifier, "network") == 0)
+ {
+ g_free (identifier);
+ network_volumes = g_list_prepend (network_volumes, volume);
+ continue;
+ }
+ g_free (identifier);
+
+ if (sidebar->show_other_locations && !is_external_volume (volume))
+ {
+ g_object_unref (volume);
+ continue;
+ }
+
+ mount = g_volume_get_mount (volume);
+ if (mount != NULL)
+ {
+ char *mount_uri;
+
+ /* Show mounted volume in the sidebar */
+ start_icon = g_mount_get_symbolic_icon (mount);
+ root = g_mount_get_default_location (mount);
+ mount_uri = g_file_get_uri (root);
+ name = g_mount_get_name (mount);
+ tooltip = g_file_get_parse_name (root);
+
+ add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME,
+ NAUTILUS_GTK_PLACES_SECTION_MOUNTS,
+ name, start_icon, NULL, mount_uri,
+ drive, volume, mount, NULL, 0, tooltip);
+ g_object_unref (root);
+ g_object_unref (mount);
+ g_object_unref (start_icon);
+ g_free (tooltip);
+ g_free (name);
+ g_free (mount_uri);
+ }
+ else
+ {
+ /* Do show the unmounted volumes in the sidebar;
+ * this is so the user can mount it (in case automounting
+ * is off).
+ *
+ * Also, even if automounting is enabled, this gives a visual
+ * cue that the user should remember to yank out the media if
+ * he just unmounted it.
+ */
+ start_icon = g_volume_get_symbolic_icon (volume);
+ name = g_volume_get_name (volume);
+ tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
+
+ add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME,
+ NAUTILUS_GTK_PLACES_SECTION_MOUNTS,
+ name, start_icon, NULL, NULL,
+ drive, volume, NULL, NULL, 0, tooltip);
+ g_object_unref (start_icon);
+ g_free (name);
+ g_free (tooltip);
+ }
+ g_object_unref (volume);
+ }
+ g_list_free (volumes);
+ }
+ else
+ {
+ if (g_drive_is_media_removable (drive) && !g_drive_is_media_check_automatic (drive))
+ {
+ /* If the drive has no mountable volumes and we cannot detect media change.. we
+ * display the drive in the sidebar so the user can manually poll the drive by
+ * right clicking and selecting "Rescan..."
+ *
+ * This is mainly for drives like floppies where media detection doesn't
+ * work.. but it's also for human beings who like to turn off media detection
+ * in the OS to save battery juice.
+ */
+ start_icon = g_drive_get_symbolic_icon (drive);
+ name = g_drive_get_name (drive);
+ tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
+
+ add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN,
+ NAUTILUS_GTK_PLACES_SECTION_MOUNTS,
+ name, start_icon, NULL, NULL,
+ drive, NULL, NULL, NULL, 0, tooltip);
+ g_object_unref (start_icon);
+ g_free (tooltip);
+ g_free (name);
+ }
+ }
+ }
+ g_list_free_full (drives, g_object_unref);
+
+ /* add all network volumes that are not associated with a drive, and
+ * loop devices
+ */
+ volumes = g_volume_monitor_get_volumes (sidebar->volume_monitor);
+ for (l = volumes; l != NULL; l = l->next)
+ {
+ gboolean is_loop = FALSE;
+ volume = l->data;
+ drive = g_volume_get_drive (volume);
+ if (drive != NULL)
+ {
+ g_object_unref (volume);
+ g_object_unref (drive);
+ continue;
+ }
+
+ identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);
+
+ if (g_strcmp0 (identifier, "network") == 0)
+ {
+ g_free (identifier);
+ network_volumes = g_list_prepend (network_volumes, volume);
+ continue;
+ }
+ else if (g_strcmp0 (identifier, "loop") == 0)
+ is_loop = TRUE;
+ g_free (identifier);
+
+ if (sidebar->show_other_locations &&
+ !is_external_volume (volume) &&
+ !is_loop)
+ {
+ g_object_unref (volume);
+ continue;
+ }
+
+ mount = g_volume_get_mount (volume);
+ if (mount != NULL)
+ {
+ char *mount_uri;
+
+ start_icon = g_mount_get_symbolic_icon (mount);
+ root = g_mount_get_default_location (mount);
+ mount_uri = g_file_get_uri (root);
+ tooltip = g_file_get_parse_name (root);
+ name = g_mount_get_name (mount);
+ add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME,
+ NAUTILUS_GTK_PLACES_SECTION_MOUNTS,
+ name, start_icon, NULL, mount_uri,
+ NULL, volume, mount, NULL, 0, tooltip);
+ g_object_unref (mount);
+ g_object_unref (root);
+ g_object_unref (start_icon);
+ g_free (name);
+ g_free (tooltip);
+ g_free (mount_uri);
+ }
+ else
+ {
+ /* see comment above in why we add an icon for an unmounted mountable volume */
+ start_icon = g_volume_get_symbolic_icon (volume);
+ name = g_volume_get_name (volume);
+ add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME,
+ NAUTILUS_GTK_PLACES_SECTION_MOUNTS,
+ name, start_icon, NULL, NULL,
+ NULL, volume, NULL, NULL, 0, name);
+ g_object_unref (start_icon);
+ g_free (name);
+ }
+ g_object_unref (volume);
+ }
+ g_list_free (volumes);
+
+ /* file system root */
+ if (!sidebar->show_other_locations)
+ {
+ start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_FILESYSTEM);
+ add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN,
+ NAUTILUS_GTK_PLACES_SECTION_MOUNTS,
+ sidebar->hostname, start_icon, NULL, "file:///",
+ NULL, NULL, NULL, NULL, 0,
+ _("Open the contents of the file system"));
+ g_object_unref (start_icon);
+ }
+
+ /* add mounts that has no volume (/etc/mtab mounts, ftp, sftp,...) */
+ mounts = g_volume_monitor_get_mounts (sidebar->volume_monitor);
+
+ for (l = mounts; l != NULL; l = l->next)
+ {
+ char *mount_uri;
+
+ mount = l->data;
+ if (g_mount_is_shadowed (mount))
+ {
+ g_object_unref (mount);
+ continue;
+ }
+ volume = g_mount_get_volume (mount);
+ if (volume != NULL)
+ {
+ g_object_unref (volume);
+ g_object_unref (mount);
+ continue;
+ }
+ root = g_mount_get_default_location (mount);
+
+ if (!g_file_is_native (root))
+ {
+ network_mounts = g_list_prepend (network_mounts, mount);
+ g_object_unref (root);
+ continue;
+ }
+
+ start_icon = g_mount_get_symbolic_icon (mount);
+ mount_uri = g_file_get_uri (root);
+ name = g_mount_get_name (mount);
+ tooltip = g_file_get_parse_name (root);
+ add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ name, start_icon, NULL, mount_uri,
+ NULL, NULL, mount, NULL, 0, tooltip);
+ g_object_unref (root);
+ g_object_unref (mount);
+ g_object_unref (start_icon);
+ g_free (name);
+ g_free (mount_uri);
+ g_free (tooltip);
+ }
+ g_list_free (mounts);
+
+ /* add bookmarks */
+ bookmarks = _nautilus_gtk_bookmarks_manager_list_bookmarks (sidebar->bookmarks_manager);
+
+ for (sl = bookmarks, index = 0; sl; sl = sl->next, index++)
+ {
+ gboolean is_native;
+ BookmarkQueryClosure *clos;
+
+ root = sl->data;
+ is_native = g_file_is_native (root);
+
+ if (_nautilus_gtk_bookmarks_manager_get_is_builtin (sidebar->bookmarks_manager, root))
+ continue;
+
+ clos = g_slice_new (BookmarkQueryClosure);
+ clos->sidebar = sidebar;
+ clos->index = index;
+ clos->is_native = is_native;
+ g_file_query_info_async (root,
+ "standard::display-name,standard::symbolic-icon",
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ sidebar->cancellable,
+ on_bookmark_query_info_complete,
+ clos);
+ }
+
+ g_slist_free_full (bookmarks, g_object_unref);
+
+ /* Add new bookmark row */
+ new_bookmark_icon = g_themed_icon_new ("bookmark-new-symbolic");
+ sidebar->new_bookmark_row = add_place (sidebar, NAUTILUS_GTK_PLACES_DROP_FEEDBACK,
+ NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS,
+ _("New bookmark"), new_bookmark_icon, NULL, NULL,
+ NULL, NULL, NULL, NULL, 0,
+ _("Add a new bookmark"));
+ gtk_widget_add_css_class (sidebar->new_bookmark_row, "sidebar-new-bookmark-row");
+ g_object_unref (new_bookmark_icon);
+
+ /* network */
+ network_volumes = g_list_reverse (network_volumes);
+ for (l = network_volumes; l != NULL; l = l->next)
+ {
+ volume = l->data;
+ mount = g_volume_get_mount (volume);
+
+ if (mount != NULL)
+ {
+ network_mounts = g_list_prepend (network_mounts, mount);
+ continue;
+ }
+ else
+ {
+ start_icon = g_volume_get_symbolic_icon (volume);
+ name = g_volume_get_name (volume);
+ tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
+
+ add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME,
+ NAUTILUS_GTK_PLACES_SECTION_MOUNTS,
+ name, start_icon, NULL, NULL,
+ NULL, volume, NULL, NULL, 0, tooltip);
+ g_object_unref (start_icon);
+ g_free (name);
+ g_free (tooltip);
+ }
+ }
+
+ network_mounts = g_list_reverse (network_mounts);
+ for (l = network_mounts; l != NULL; l = l->next)
+ {
+ char *mount_uri;
+
+ mount = l->data;
+ root = g_mount_get_default_location (mount);
+ start_icon = g_mount_get_symbolic_icon (mount);
+ mount_uri = g_file_get_uri (root);
+ name = g_mount_get_name (mount);
+ tooltip = g_file_get_parse_name (root);
+ add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME,
+ NAUTILUS_GTK_PLACES_SECTION_MOUNTS,
+ name, start_icon, NULL, mount_uri,
+ NULL, NULL, mount, NULL, 0, tooltip);
+ g_object_unref (root);
+ g_object_unref (start_icon);
+ g_free (name);
+ g_free (mount_uri);
+ g_free (tooltip);
+ }
+
+
+ g_list_free_full (network_volumes, g_object_unref);
+ g_list_free_full (network_mounts, g_object_unref);
+
+ /* Other locations */
+ if (sidebar->show_other_locations)
+ {
+ start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_OTHER_LOCATIONS);
+
+ add_place (sidebar, NAUTILUS_GTK_PLACES_OTHER_LOCATIONS,
+ NAUTILUS_GTK_PLACES_SECTION_OTHER_LOCATIONS,
+ _("Other Locations"), start_icon, NULL, "other-locations:///",
+ NULL, NULL, NULL, NULL, 0, _("Show other locations"));
+
+ g_object_unref (start_icon);
+ }
+
+ gtk_widget_show (GTK_WIDGET (sidebar));
+ /* We want this hidden by default, but need to do it after the show_all call */
+ nautilus_gtk_sidebar_row_hide (NAUTILUS_GTK_SIDEBAR_ROW (sidebar->new_bookmark_row), TRUE);
+
+ /* restore original selection */
+ if (original_uri)
+ {
+ GFile *restore;
+
+ restore = g_file_new_for_uri (original_uri);
+ nautilus_gtk_places_sidebar_set_location (sidebar, restore);
+ g_object_unref (restore);
+ g_free (original_uri);
+ }
+}
+
+static gboolean
+hover_timer (gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar = user_data;
+ gboolean open_folder_on_hover;
+ g_autofree gchar *uri = NULL;
+ g_autoptr (GFile) location = NULL;
+
+ open_folder_on_hover = g_settings_get_boolean (nautilus_preferences,
+ NAUTILUS_PREFERENCES_OPEN_FOLDER_ON_DND_HOVER);
+ sidebar->hover_timer_id = 0;
+
+ if (open_folder_on_hover && sidebar->hover_row != NULL)
+ {
+ g_object_get (sidebar->hover_row, "uri", &uri, NULL);
+ if (uri != NULL)
+ {
+ location = g_file_new_for_uri (uri);
+ emit_open_location (sidebar, location, 0);
+ }
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+check_valid_drop_target (NautilusGtkPlacesSidebar *sidebar,
+ NautilusGtkSidebarRow *row,
+ const GValue *value)
+{
+ NautilusGtkPlacesPlaceType place_type;
+ NautilusGtkPlacesSectionType section_type;
+ g_autoptr (NautilusFile) dest_file = NULL;
+ gboolean valid = FALSE;
+ char *uri;
+ int drag_action;
+
+ g_return_val_if_fail (value != NULL, TRUE);
+
+ if (row == NULL)
+ return FALSE;
+
+ g_object_get (row,
+ "place-type", &place_type,
+ "section_type", &section_type,
+ "uri", &uri,
+ "file", &dest_file,
+ NULL);
+
+ if (place_type == NAUTILUS_GTK_PLACES_CONNECT_TO_SERVER)
+ {
+ g_free (uri);
+ return FALSE;
+ }
+
+ if (place_type == NAUTILUS_GTK_PLACES_OTHER_LOCATIONS)
+ {
+ g_free (uri);
+ return FALSE;
+ }
+
+ if (place_type == NAUTILUS_GTK_PLACES_DROP_FEEDBACK)
+ {
+ g_free (uri);
+ return TRUE;
+ }
+
+ /* Disallow drops on recent:/// */
+ if (place_type == NAUTILUS_GTK_PLACES_BUILT_IN)
+ {
+ if (g_strcmp0 (uri, "recent:///") == 0)
+ {
+ g_free (uri);
+ return FALSE;
+ }
+ }
+
+ /* Dragging a bookmark? */
+ if (G_VALUE_HOLDS (value, NAUTILUS_TYPE_GTK_SIDEBAR_ROW))
+ {
+ /* Don't allow reordering bookmarks into non-bookmark areas */
+ valid = section_type == NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS;
+ }
+ else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
+ {
+ /* Dragging a file */
+ if (uri != NULL)
+ {
+ drag_action = emit_drag_action_requested (sidebar, dest_file, g_value_get_boxed (value));
+ valid = drag_action > 0;
+ }
+ else
+ {
+ valid = FALSE;
+ }
+ }
+ else
+ {
+ g_assert_not_reached ();
+ valid = TRUE;
+ }
+
+ g_free (uri);
+ return valid;
+}
+
+static void
+update_possible_drop_targets (NautilusGtkPlacesSidebar *sidebar,
+ const GValue *value)
+{
+ GtkWidget *row;
+
+ for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box));
+ row != NULL;
+ row = gtk_widget_get_next_sibling (row))
+ {
+ gboolean sensitive;
+
+ if (!GTK_IS_LIST_BOX_ROW (row))
+ continue;
+
+ sensitive = value == NULL ||
+ check_valid_drop_target (sidebar, NAUTILUS_GTK_SIDEBAR_ROW (row), value);
+ gtk_widget_set_sensitive (row, sensitive);
+ }
+}
+
+static void
+start_drop_feedback (NautilusGtkPlacesSidebar *sidebar,
+ const GValue *value)
+{
+ if (value != NULL && G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
+ {
+ GSList *source_list = g_value_get_boxed (value);
+ if (g_slist_length (source_list) == 1)
+ {
+ g_autoptr (NautilusFile) file = NULL;
+ file = nautilus_file_get (source_list->data);
+ if (nautilus_file_is_directory (file))
+ nautilus_gtk_sidebar_row_reveal (NAUTILUS_GTK_SIDEBAR_ROW (sidebar->new_bookmark_row));
+ }
+ }
+ if (value && !G_VALUE_HOLDS (value, NAUTILUS_TYPE_GTK_SIDEBAR_ROW))
+ {
+ /* If the state is permanent, don't change it. The application controls it. */
+ if (sidebar->drop_state != DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT)
+ sidebar->drop_state = DROP_STATE_NEW_BOOKMARK_ARMED;
+ }
+
+ update_possible_drop_targets (sidebar, value);
+}
+
+static void
+stop_drop_feedback (NautilusGtkPlacesSidebar *sidebar)
+{
+ update_possible_drop_targets (sidebar, NULL);
+
+ if (sidebar->drop_state != DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT &&
+ sidebar->new_bookmark_row != NULL)
+ {
+ nautilus_gtk_sidebar_row_hide (NAUTILUS_GTK_SIDEBAR_ROW (sidebar->new_bookmark_row), FALSE);
+ sidebar->drop_state = DROP_STATE_NORMAL;
+ }
+
+ if (sidebar->drag_row != NULL)
+ {
+ gtk_widget_show (sidebar->drag_row);
+ sidebar->drag_row = NULL;
+ }
+
+ if (sidebar->row_placeholder != NULL)
+ {
+ if (gtk_widget_get_parent (sidebar->row_placeholder) != NULL)
+ gtk_list_box_remove (GTK_LIST_BOX (sidebar->list_box), sidebar->row_placeholder);
+ sidebar->row_placeholder = NULL;
+ }
+
+ sidebar->dragging_over = FALSE;
+}
+
+static GtkWidget *
+create_placeholder_row (NautilusGtkPlacesSidebar *sidebar)
+{
+ return g_object_new (NAUTILUS_TYPE_GTK_SIDEBAR_ROW, "placeholder", TRUE, NULL);
+}
+
+static GdkDragAction
+drag_motion_callback (GtkDropTarget *target,
+ double x,
+ double y,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ GdkDragAction action;
+ GtkListBoxRow *row;
+ NautilusGtkPlacesPlaceType place_type;
+ char *drop_target_uri = NULL;
+ int row_index;
+ int row_placeholder_index;
+ const GValue *value;
+ graphene_point_t start;
+
+ sidebar->dragging_over = TRUE;
+ action = 0;
+ row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (sidebar->list_box), y);
+
+ start = sidebar->hover_start_point;
+ if (row != sidebar->hover_row ||
+ gtk_drag_check_threshold (GTK_WIDGET (sidebar), start.x, start.y, x, y))
+ {
+ g_clear_handle_id (&sidebar->hover_timer_id, g_source_remove);
+ sidebar->hover_row = row;
+ sidebar->hover_timer_id = g_timeout_add (HOVER_TIMEOUT, hover_timer, sidebar);
+ sidebar->hover_start_point.x = x;
+ sidebar->hover_start_point.y = y;
+ }
+
+ /* Workaround https://gitlab.gnome.org/GNOME/gtk/-/issues/5023 */
+ gtk_list_box_drag_unhighlight_row (GTK_LIST_BOX (sidebar->list_box));
+
+ /* Nothing to do if no value yet */
+ value = gtk_drop_target_get_value (target);
+ if (value == NULL)
+ goto out;
+
+ /* Nothing to do if the target is not valid drop destination */
+ if (!check_valid_drop_target (sidebar, NAUTILUS_GTK_SIDEBAR_ROW (row), value))
+ goto out;
+
+ if (G_VALUE_HOLDS (value, NAUTILUS_TYPE_GTK_SIDEBAR_ROW))
+ {
+ /* Dragging bookmarks always moves them to another position in the bookmarks list */
+ action = GDK_ACTION_MOVE;
+ if (sidebar->row_placeholder == NULL)
+ {
+ sidebar->row_placeholder = create_placeholder_row (sidebar);
+ g_object_ref_sink (sidebar->row_placeholder);
+ }
+ else if (GTK_WIDGET (row) == sidebar->row_placeholder)
+ {
+ goto out;
+ }
+
+ if (gtk_widget_get_parent (sidebar->row_placeholder) != NULL)
+ gtk_list_box_remove (GTK_LIST_BOX (sidebar->list_box), sidebar->row_placeholder);
+
+ if (row != NULL)
+ {
+ g_object_get (row, "order-index", &row_index, NULL);
+ g_object_get (sidebar->row_placeholder, "order-index", &row_placeholder_index, NULL);
+ /* We order the bookmarks sections based on the bookmark index that we
+ * set on the row as order-index property, but we have to deal with
+ * the placeholder row wanting to be between two consecutive bookmarks,
+ * with two consecutive order-index values which is the usual case.
+ * For that, in the list box sort func we give priority to the placeholder row,
+ * that means that if the index-order is the same as another bookmark
+ * the placeholder row goes before. However if we want to show it after
+ * the current row, for instance when the cursor is in the lower half
+ * of the row, we need to increase the order-index.
+ */
+ row_placeholder_index = row_index;
+ gtk_widget_translate_coordinates (GTK_WIDGET (sidebar), GTK_WIDGET (row),
+ x, y,
+ &x, &y);
+
+ if (y > sidebar->drag_row_height / 2 && row_index > 0)
+ row_placeholder_index++;
+ }
+ else
+ {
+ /* If the user is dragging over an area that has no row, place the row
+ * placeholder in the last position
+ */
+ row_placeholder_index = G_MAXINT32;
+ }
+
+ g_object_set (sidebar->row_placeholder, "order-index", row_placeholder_index, NULL);
+
+ gtk_list_box_prepend (GTK_LIST_BOX (sidebar->list_box),
+ sidebar->row_placeholder);
+ }
+ else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
+ {
+ NautilusFile *file;
+ gtk_list_box_drag_highlight_row (GTK_LIST_BOX (sidebar->list_box), row);
+
+ g_object_get (row,
+ "place-type", &place_type,
+ "uri", &drop_target_uri,
+ "file", &file,
+ NULL);
+ /* URIs are being dragged. See if the caller wants to handle a
+ * file move/copy operation itself, or if we should only try to
+ * create bookmarks out of the dragged URIs.
+ */
+ if (place_type == NAUTILUS_GTK_PLACES_DROP_FEEDBACK)
+ {
+ action = GDK_ACTION_COPY;
+ }
+ else
+ {
+ /* uri may be NULL for unmounted volumes, for example, so we don't allow drops there */
+ if (drop_target_uri != NULL)
+ {
+ GFile *dest_file = g_file_new_for_uri (drop_target_uri);
+
+ action = emit_drag_action_requested (sidebar, file, g_value_get_boxed (value));
+
+ g_object_unref (dest_file);
+ }
+ }
+
+ nautilus_file_unref (file);
+ g_free (drop_target_uri);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ out:
+ start_drop_feedback (sidebar, value);
+ return action;
+}
+
+/* Reorders the bookmark to the specified position */
+static void
+reorder_bookmarks (NautilusGtkPlacesSidebar *sidebar,
+ NautilusGtkSidebarRow *row,
+ int new_position)
+{
+ char *uri;
+ GFile *file;
+
+ g_object_get (row, "uri", &uri, NULL);
+ file = g_file_new_for_uri (uri);
+ _nautilus_gtk_bookmarks_manager_reorder_bookmark (sidebar->bookmarks_manager, file, new_position, NULL);
+
+ g_object_unref (file);
+ g_free (uri);
+}
+
+/* Creates bookmarks for the specified files at the given position in the bookmarks list */
+static void
+drop_files_as_bookmarks (NautilusGtkPlacesSidebar *sidebar,
+ GSList *files,
+ int position)
+{
+ GSList *l;
+
+ for (l = files; l; l = l->next)
+ {
+ GFile *f = G_FILE (l->data);
+ GFileInfo *info = g_file_query_info (f,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ NULL,
+ NULL);
+
+ if (info)
+ {
+ if ((g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY ||
+ g_file_info_get_file_type (info) == G_FILE_TYPE_MOUNTABLE ||
+ g_file_info_get_file_type (info) == G_FILE_TYPE_SHORTCUT ||
+ g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK))
+ _nautilus_gtk_bookmarks_manager_insert_bookmark (sidebar->bookmarks_manager, f, position++, NULL);
+
+ g_object_unref (info);
+ }
+ }
+}
+
+static gboolean
+drag_drop_callback (GtkDropTarget *target,
+ const GValue *value,
+ double x,
+ double y,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ int target_order_index;
+ NautilusGtkPlacesPlaceType target_place_type;
+ NautilusGtkPlacesSectionType target_section_type;
+ char *target_uri;
+ GtkListBoxRow *target_row;
+ gboolean result;
+
+ target_row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (sidebar->list_box), y);
+ if (target_row == NULL)
+ return FALSE;
+
+ if (!check_valid_drop_target (sidebar, NAUTILUS_GTK_SIDEBAR_ROW (target_row), value))
+ return FALSE;
+
+ g_object_get (target_row,
+ "place-type", &target_place_type,
+ "section-type", &target_section_type,
+ "order-index", &target_order_index,
+ "uri", &target_uri,
+ NULL);
+ result = FALSE;
+
+ if (G_VALUE_HOLDS (value, NAUTILUS_TYPE_GTK_SIDEBAR_ROW))
+ {
+ GtkWidget *source_row;
+ /* A bookmark got reordered */
+ if (target_section_type != NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS)
+ goto out;
+
+ source_row = g_value_get_object (value);
+
+ if (sidebar->row_placeholder != NULL)
+ g_object_get (sidebar->row_placeholder, "order-index", &target_order_index, NULL);
+
+ reorder_bookmarks (sidebar, NAUTILUS_GTK_SIDEBAR_ROW (source_row), target_order_index);
+ result = TRUE;
+ }
+ else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
+ {
+ /* Dropping URIs! */
+ if (target_place_type == NAUTILUS_GTK_PLACES_DROP_FEEDBACK)
+ {
+ drop_files_as_bookmarks (sidebar, g_value_get_boxed (value), target_order_index);
+ }
+ else
+ {
+ GFile *dest_file = g_file_new_for_uri (target_uri);
+ GdkDragAction actions;
+
+ actions = gdk_drop_get_actions (gtk_drop_target_get_current_drop (target));
+
+ #ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (sidebar))))
+ {
+ /* Temporary workaround until the below GTK MR (or equivalend fix)
+ * is merged. Without this fix, the preferred action isn't set correctly.
+ * https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4982 */
+ GdkDrag *drag = gdk_drop_get_drag (gtk_drop_target_get_current_drop (target));
+ actions = gdk_drag_get_selected_action (drag);
+ }
+ #endif
+
+ emit_drag_perform_drop (sidebar,
+ dest_file,
+ g_value_get_boxed (value),
+ actions);
+
+ g_object_unref (dest_file);
+ }
+ result = TRUE;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+out:
+ stop_drop_feedback (sidebar);
+ g_free (target_uri);
+ return result;
+}
+
+static void
+dnd_finished_cb (GdkDrag *drag,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ stop_drop_feedback (sidebar);
+}
+
+static void
+dnd_cancel_cb (GdkDrag *drag,
+ GdkDragCancelReason reason,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ stop_drop_feedback (sidebar);
+}
+
+/* This functions is called every time the drag source leaves
+ * the sidebar widget.
+ * The problem is that, we start showing hints for drop when the source
+ * start being above the sidebar or when the application request so show
+ * drop hints, but at some moment we need to restore to normal
+ * state.
+ * One could think that here we could simply call stop_drop_feedback,
+ * but that's not true, because this function is called also before drag_drop,
+ * which needs the data from the drag so we cannot free the drag data here.
+ * So now one could think we could just do nothing here, and wait for
+ * drag-end or drag-cancel signals and just stop_drop_feedback there. But that
+ * is also not true, since when the drag comes from a different widget than the
+ * sidebar, when the drag stops the last drag signal we receive is drag-leave.
+ * So here what we will do is restore the state of the sidebar as if no drag
+ * is being done (and if the application didn't request for permanent hints with
+ * nautilus_gtk_places_sidebar_show_drop_hints) and we will free the drag data next time
+ * we build new drag data in drag_data_received.
+ */
+static void
+drag_leave_callback (GtkDropTarget *dest,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (user_data);
+
+ gtk_list_box_drag_unhighlight_row (GTK_LIST_BOX (sidebar->list_box));
+
+ if (sidebar->drop_state != DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT)
+ {
+ update_possible_drop_targets (sidebar, FALSE);
+ nautilus_gtk_sidebar_row_hide (NAUTILUS_GTK_SIDEBAR_ROW (sidebar->new_bookmark_row), FALSE);
+ sidebar->drop_state = DROP_STATE_NORMAL;
+ }
+
+ g_clear_handle_id (&sidebar->hover_timer_id, g_source_remove);
+ sidebar->dragging_over = FALSE;
+}
+
+static void
+check_unmount_and_eject (GMount *mount,
+ GVolume *volume,
+ GDrive *drive,
+ gboolean *show_unmount,
+ gboolean *show_eject)
+{
+ *show_unmount = FALSE;
+ *show_eject = FALSE;
+
+ if (drive != NULL)
+ *show_eject = g_drive_can_eject (drive);
+
+ if (volume != NULL)
+ *show_eject |= g_volume_can_eject (volume);
+
+ if (mount != NULL)
+ {
+ *show_eject |= g_mount_can_eject (mount);
+ *show_unmount = g_mount_can_unmount (mount) && !*show_eject;
+ }
+}
+
+static void
+drive_start_from_bookmark_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ GError *error;
+ char *primary;
+ char *name;
+
+ sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (user_data);
+
+ error = NULL;
+ if (!g_drive_poll_for_media_finish (G_DRIVE (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_drive_get_name (G_DRIVE (source_object));
+ primary = g_strdup_printf (_("Unable to start “%s”"), name);
+ g_free (name);
+ emit_show_error_message (sidebar, primary, error->message);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+}
+
+static void
+volume_mount_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ NautilusGtkSidebarRow *row = NAUTILUS_GTK_SIDEBAR_ROW (user_data);
+ NautilusGtkPlacesSidebar *sidebar;
+ GVolume *volume;
+ GError *error;
+ char *primary;
+ char *name;
+ GMount *mount;
+
+ volume = G_VOLUME (source_object);
+ g_object_get (row, "sidebar", &sidebar, NULL);
+
+ error = NULL;
+ if (!g_volume_mount_finish (volume, result, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED &&
+ error->code != G_IO_ERROR_ALREADY_MOUNTED)
+ {
+ name = g_volume_get_name (G_VOLUME (source_object));
+ if (g_str_has_prefix (error->message, "Error unlocking"))
+ /* Translators: This means that unlocking an encrypted storage
+ * device failed. %s is the name of the device.
+ */
+ primary = g_strdup_printf (_("Error unlocking “%s”"), name);
+ else
+ primary = g_strdup_printf (_("Unable to access “%s”"), name);
+ g_free (name);
+ emit_show_error_message (sidebar, primary, error->message);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+
+ sidebar->mounting = FALSE;
+ nautilus_gtk_sidebar_row_set_busy (row, FALSE);
+
+ mount = g_volume_get_mount (volume);
+ if (mount != NULL)
+ {
+ GFile *location;
+
+ location = g_mount_get_default_location (mount);
+ emit_open_location (sidebar, location, sidebar->go_to_after_mount_open_flags);
+
+ g_object_unref (G_OBJECT (location));
+ g_object_unref (G_OBJECT (mount));
+ }
+
+ g_object_unref (row);
+ g_object_unref (sidebar);
+}
+
+static void
+mount_volume (NautilusGtkSidebarRow *row,
+ GVolume *volume)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ GMountOperation *mount_op;
+
+ g_object_get (row, "sidebar", &sidebar, NULL);
+
+ mount_op = get_mount_operation (sidebar);
+ g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION);
+
+ g_object_ref (row);
+ g_object_ref (sidebar);
+ g_volume_mount (volume, 0, mount_op, NULL, volume_mount_cb, row);
+}
+
+static void
+open_drive (NautilusGtkSidebarRow *row,
+ GDrive *drive,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+
+ g_object_get (row, "sidebar", &sidebar, NULL);
+
+ if (drive != NULL &&
+ (g_drive_can_start (drive) || g_drive_can_start_degraded (drive)))
+ {
+ GMountOperation *mount_op;
+
+ nautilus_gtk_sidebar_row_set_busy (row, TRUE);
+ mount_op = get_mount_operation (sidebar);
+ g_drive_start (drive, G_DRIVE_START_NONE, mount_op, NULL, drive_start_from_bookmark_cb, NULL);
+ g_object_unref (mount_op);
+ }
+}
+
+static void
+open_volume (NautilusGtkSidebarRow *row,
+ GVolume *volume,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+
+ g_object_get (row, "sidebar", &sidebar, NULL);
+
+ if (volume != NULL && !sidebar->mounting)
+ {
+ sidebar->mounting = TRUE;
+ sidebar->go_to_after_mount_open_flags = open_flags;
+ nautilus_gtk_sidebar_row_set_busy (row, TRUE);
+ mount_volume (row, volume);
+ }
+}
+
+static void
+open_uri (NautilusGtkPlacesSidebar *sidebar,
+ const char *uri,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ GFile *location;
+
+ location = g_file_new_for_uri (uri);
+ emit_open_location (sidebar, location, open_flags);
+ g_object_unref (location);
+}
+
+static void
+open_row (NautilusGtkSidebarRow *row,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ char *uri;
+ GDrive *drive;
+ GVolume *volume;
+ NautilusGtkPlacesPlaceType place_type;
+ NautilusGtkPlacesSidebar *sidebar;
+
+ g_object_get (row,
+ "sidebar", &sidebar,
+ "uri", &uri,
+ "place-type", &place_type,
+ "drive", &drive,
+ "volume", &volume,
+ NULL);
+
+ if (place_type == NAUTILUS_GTK_PLACES_OTHER_LOCATIONS)
+ {
+ emit_show_other_locations_with_flags (sidebar, open_flags);
+ }
+ else if (place_type == NAUTILUS_GTK_PLACES_STARRED_LOCATION)
+ {
+ emit_show_starred_location (sidebar, open_flags);
+ }
+ else if (uri != NULL)
+ {
+ open_uri (sidebar, uri, open_flags);
+ }
+ else if (place_type == NAUTILUS_GTK_PLACES_ENTER_LOCATION)
+ {
+ emit_show_enter_location (sidebar);
+ }
+ else if (volume != NULL)
+ {
+ open_volume (row, volume, open_flags);
+ }
+ else if (drive != NULL)
+ {
+ open_drive (row, drive, open_flags);
+ }
+
+ g_object_unref (sidebar);
+ if (drive)
+ g_object_unref (drive);
+ if (volume)
+ g_object_unref (volume);
+ g_free (uri);
+}
+
+/* Callback used for the "Open" menu items in the context menu */
+static void
+open_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ NautilusGtkPlacesOpenFlags flags;
+
+ flags = (NautilusGtkPlacesOpenFlags)g_variant_get_int32 (parameter);
+ open_row (sidebar->context_row, flags);
+}
+
+/* Add bookmark for the selected item - just used from mount points */
+static void
+add_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ char *uri;
+ char *name;
+ GFile *location;
+
+ g_object_get (sidebar->context_row,
+ "uri", &uri,
+ "label", &name,
+ NULL);
+
+ if (uri != NULL)
+ {
+ location = g_file_new_for_uri (uri);
+ if (_nautilus_gtk_bookmarks_manager_insert_bookmark (sidebar->bookmarks_manager, location, -1, NULL))
+ _nautilus_gtk_bookmarks_manager_set_bookmark_label (sidebar->bookmarks_manager, location, name, NULL);
+ g_object_unref (location);
+ }
+
+ g_free (uri);
+ g_free (name);
+}
+
+static void
+rename_entry_changed (GtkEntry *entry,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ NautilusGtkPlacesPlaceType type;
+ char *name;
+ char *uri;
+ const char *new_name;
+ gboolean found = FALSE;
+ GtkWidget *row;
+
+ new_name = gtk_editable_get_text (GTK_EDITABLE (sidebar->rename_entry));
+
+ if (strcmp (new_name, "") == 0)
+ {
+ gtk_widget_set_sensitive (sidebar->rename_button, FALSE);
+ gtk_label_set_label (GTK_LABEL (sidebar->rename_error), "");
+ return;
+ }
+
+ for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box));
+ row != NULL && !found;
+ row = gtk_widget_get_next_sibling (row))
+ {
+ if (!GTK_IS_LIST_BOX_ROW (row))
+ continue;
+
+ g_object_get (row,
+ "place-type", &type,
+ "uri", &uri,
+ "label", &name,
+ NULL);
+
+ if ((type == NAUTILUS_GTK_PLACES_XDG_DIR || type == NAUTILUS_GTK_PLACES_BOOKMARK) &&
+ strcmp (uri, sidebar->rename_uri) != 0 &&
+ strcmp (new_name, name) == 0)
+ found = TRUE;
+
+ g_free (uri);
+ g_free (name);
+ }
+
+ gtk_widget_set_sensitive (sidebar->rename_button, !found);
+ gtk_label_set_label (GTK_LABEL (sidebar->rename_error),
+ found ? _("This name is already taken") : "");
+}
+
+static void
+do_rename (GtkButton *button,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ char *new_text;
+ GFile *file;
+
+ new_text = g_strdup (gtk_editable_get_text (GTK_EDITABLE (sidebar->rename_entry)));
+
+ file = g_file_new_for_uri (sidebar->rename_uri);
+ if (!_nautilus_gtk_bookmarks_manager_has_bookmark (sidebar->bookmarks_manager, file))
+ _nautilus_gtk_bookmarks_manager_insert_bookmark (sidebar->bookmarks_manager, file, -1, NULL);
+
+ if (sidebar->rename_popover)
+ {
+ gtk_popover_popdown (GTK_POPOVER (sidebar->rename_popover));
+ }
+
+ _nautilus_gtk_bookmarks_manager_set_bookmark_label (sidebar->bookmarks_manager, file, new_text, NULL);
+
+ g_object_unref (file);
+ g_free (new_text);
+
+ g_clear_pointer (&sidebar->rename_uri, g_free);
+
+}
+
+static void
+on_rename_popover_destroy (GtkWidget *rename_popover,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ if (sidebar)
+ {
+ sidebar->rename_popover = NULL;
+ sidebar->rename_entry = NULL;
+ sidebar->rename_button = NULL;
+ sidebar->rename_error = NULL;
+ }
+}
+
+static void
+create_rename_popover (NautilusGtkPlacesSidebar *sidebar)
+{
+ GtkWidget *popover;
+ GtkWidget *grid;
+ GtkWidget *label;
+ GtkWidget *entry;
+ GtkWidget *button;
+ GtkWidget *error;
+ char *str;
+
+ if (sidebar->rename_popover)
+ return;
+
+ popover = gtk_popover_new ();
+ gtk_widget_set_parent (popover, GTK_WIDGET (sidebar));
+ /* Clean sidebar pointer when its destroyed, most of the times due to its
+ * relative_to associated row being destroyed */
+ g_signal_connect (popover, "destroy", G_CALLBACK (on_rename_popover_destroy), sidebar);
+ gtk_popover_set_position (GTK_POPOVER (popover), GTK_POS_RIGHT);
+ grid = gtk_grid_new ();
+ gtk_popover_set_child (GTK_POPOVER (popover), grid);
+ g_object_set (grid,
+ "margin-start", 10,
+ "margin-end", 10,
+ "margin-top", 10,
+ "margin-bottom", 10,
+ "row-spacing", 6,
+ "column-spacing", 6,
+ NULL);
+ entry = gtk_entry_new ();
+ gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+ g_signal_connect (entry, "changed", G_CALLBACK (rename_entry_changed), sidebar);
+ str = g_strdup_printf ("<b>%s</b>", _("Name"));
+ label = gtk_label_new (str);
+ gtk_widget_set_halign (label, GTK_ALIGN_START);
+ gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
+ g_free (str);
+ button = gtk_button_new_with_mnemonic (_("_Rename"));
+ gtk_widget_add_css_class (button, "suggested-action");
+ g_signal_connect (button, "clicked", G_CALLBACK (do_rename), sidebar);
+ error = gtk_label_new ("");
+ gtk_widget_set_halign (error, GTK_ALIGN_START);
+ gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 2, 1);
+ gtk_grid_attach (GTK_GRID (grid), entry, 0, 1, 1, 1);
+ gtk_grid_attach (GTK_GRID (grid), button,1, 1, 1, 1);
+ gtk_grid_attach (GTK_GRID (grid), error, 0, 2, 2, 1);
+ gtk_popover_set_default_widget (GTK_POPOVER (popover), button);
+
+ sidebar->rename_popover = popover;
+ sidebar->rename_entry = entry;
+ sidebar->rename_button = button;
+ sidebar->rename_error = error;
+}
+
+/* Style the row differently while we show a popover for it.
+ * Otherwise, the popover is 'pointing to nothing'. Since the
+ * main popover and the rename popover interleave their hiding
+ * and showing, we have to count to ensure that we don't loose
+ * the state before the last popover is gone.
+ *
+ * This would be nicer as a state, but reusing hover for this
+ * interferes with the normal handling of this state, so just
+ * use a style class.
+ */
+static void
+update_popover_shadowing (GtkWidget *row,
+ gboolean shown)
+{
+ int count;
+
+ count = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "popover-count"));
+ count = shown ? count + 1 : count - 1;
+ g_object_set_data (G_OBJECT (row), "popover-count", GINT_TO_POINTER (count));
+
+ if (count > 0)
+ gtk_widget_add_css_class (row, "has-open-popup");
+ else
+ gtk_widget_remove_css_class (row, "has-open-popup");
+}
+
+static void
+set_prelight (NautilusGtkPlacesSidebar *sidebar)
+{
+ update_popover_shadowing (GTK_WIDGET (sidebar->context_row), TRUE);
+}
+
+static void
+unset_prelight (NautilusGtkPlacesSidebar *sidebar)
+{
+ update_popover_shadowing (GTK_WIDGET (sidebar->context_row), FALSE);
+}
+
+static void
+setup_popover_shadowing (GtkWidget *popover,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ g_signal_connect_swapped (popover, "map", G_CALLBACK (set_prelight), sidebar);
+ g_signal_connect_swapped (popover, "unmap", G_CALLBACK (unset_prelight), sidebar);
+}
+
+static void
+_popover_set_pointing_to_widget (GtkPopover *popover,
+ GtkWidget *target)
+{
+ GtkWidget *parent;
+ double x, y, w, h;
+
+ parent = gtk_widget_get_parent (GTK_WIDGET (popover));
+
+ if (!gtk_widget_translate_coordinates (target, parent, 0, 0, &x, &y))
+ return;
+
+ w = gtk_widget_get_allocated_width (GTK_WIDGET (target));
+ h = gtk_widget_get_allocated_height (GTK_WIDGET (target));
+
+ gtk_popover_set_pointing_to (popover, &(GdkRectangle){x, y, w, h});
+}
+
+static void
+show_rename_popover (NautilusGtkSidebarRow *row)
+{
+ char *name;
+ char *uri;
+ NautilusGtkPlacesSidebar *sidebar;
+
+ g_object_get (row,
+ "sidebar", &sidebar,
+ "label", &name,
+ "uri", &uri,
+ NULL);
+
+ create_rename_popover (sidebar);
+
+ if (sidebar->rename_uri)
+ g_free (sidebar->rename_uri);
+ sidebar->rename_uri = g_strdup (uri);
+
+ gtk_editable_set_text (GTK_EDITABLE (sidebar->rename_entry), name);
+
+ _popover_set_pointing_to_widget (GTK_POPOVER (sidebar->rename_popover),
+ GTK_WIDGET (row));
+
+ setup_popover_shadowing (sidebar->rename_popover, sidebar);
+
+ gtk_popover_popup (GTK_POPOVER (sidebar->rename_popover));
+ gtk_widget_grab_focus (sidebar->rename_entry);
+
+ g_free (name);
+ g_free (uri);
+ g_object_unref (sidebar);
+}
+
+static void
+rename_bookmark (NautilusGtkSidebarRow *row)
+{
+ NautilusGtkPlacesPlaceType type;
+
+ g_object_get (row, "place-type", &type, NULL);
+
+ if (type != NAUTILUS_GTK_PLACES_BOOKMARK && type != NAUTILUS_GTK_PLACES_XDG_DIR)
+ return;
+
+ show_rename_popover (row);
+}
+
+static void
+rename_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+
+ rename_bookmark (sidebar->context_row);
+}
+
+static void
+properties_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ GList *list;
+ NautilusFile *file;
+
+ g_object_get (sidebar->context_row, "file", &file, NULL);
+
+ list = g_list_append (NULL, file);
+ nautilus_properties_window_present (list, GTK_WIDGET (sidebar), NULL, NULL, NULL);
+
+ nautilus_file_list_free (list);
+}
+
+static void
+empty_trash_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ nautilus_file_operations_empty_trash (GTK_WIDGET (sidebar), TRUE, NULL);
+}
+
+static void
+remove_bookmark (NautilusGtkSidebarRow *row)
+{
+ NautilusGtkPlacesPlaceType type;
+ char *uri;
+ GFile *file;
+ NautilusGtkPlacesSidebar *sidebar;
+
+ g_object_get (row,
+ "sidebar", &sidebar,
+ "place-type", &type,
+ "uri", &uri,
+ NULL);
+
+ if (type == NAUTILUS_GTK_PLACES_BOOKMARK)
+ {
+ file = g_file_new_for_uri (uri);
+ _nautilus_gtk_bookmarks_manager_remove_bookmark (sidebar->bookmarks_manager, file, NULL);
+ g_object_unref (file);
+ }
+
+ g_free (uri);
+ g_object_unref (sidebar);
+}
+
+static void
+remove_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+
+ remove_bookmark (sidebar->context_row);
+}
+
+static void
+mount_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ GVolume *volume;
+
+ g_object_get (sidebar->context_row,
+ "volume", &volume,
+ NULL);
+
+ if (volume != NULL)
+ mount_volume (sidebar->context_row, volume);
+
+ g_object_unref (volume);
+}
+
+static GMountOperation *
+get_mount_operation (NautilusGtkPlacesSidebar *sidebar)
+{
+ GMountOperation *mount_op;
+
+ mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (sidebar))));
+
+ emit_mount_operation (sidebar, mount_op);
+
+ return mount_op;
+}
+
+static GMountOperation *
+get_unmount_operation (NautilusGtkPlacesSidebar *sidebar)
+{
+ GMountOperation *mount_op;
+
+ mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (sidebar))));
+
+ emit_unmount_operation (sidebar, mount_op);
+
+ return mount_op;
+}
+
+/* Returns TRUE if file1 is prefix of file2 or if both files have the
+ * same path
+ */
+static gboolean
+file_prefix_or_same (GFile *file1,
+ GFile *file2)
+{
+ return g_file_has_prefix (file1, file2) ||
+ g_file_equal (file1, file2);
+}
+
+static gboolean
+is_current_location_on_volume (NautilusGtkPlacesSidebar *sidebar,
+ GMount *mount,
+ GVolume *volume,
+ GDrive *drive)
+{
+ gboolean current_location_on_volume;
+ GFile *mount_default_location;
+ GMount *mount_for_volume;
+ GList *volumes_for_drive;
+ GList *volume_for_drive;
+
+ current_location_on_volume = FALSE;
+
+ if (sidebar->current_location != NULL)
+ {
+ if (mount != NULL)
+ {
+ mount_default_location = g_mount_get_default_location (mount);
+ current_location_on_volume = file_prefix_or_same (sidebar->current_location,
+ mount_default_location);
+
+ g_object_unref (mount_default_location);
+ }
+ /* This code path is probably never reached since mount always exists,
+ * and if it doesn't exists we don't offer a way to eject a volume or
+ * drive in the UI. Do it for defensive programming
+ */
+ else if (volume != NULL)
+ {
+ mount_for_volume = g_volume_get_mount (volume);
+ if (mount_for_volume != NULL)
+ {
+ mount_default_location = g_mount_get_default_location (mount_for_volume);
+ current_location_on_volume = file_prefix_or_same (sidebar->current_location,
+ mount_default_location);
+
+ g_object_unref (mount_default_location);
+ g_object_unref (mount_for_volume);
+ }
+ }
+ /* This code path is probably never reached since mount always exists,
+ * and if it doesn't exists we don't offer a way to eject a volume or
+ * drive in the UI. Do it for defensive programming
+ */
+ else if (drive != NULL)
+ {
+ volumes_for_drive = g_drive_get_volumes (drive);
+ for (volume_for_drive = volumes_for_drive; volume_for_drive != NULL; volume_for_drive = volume_for_drive->next)
+ {
+ mount_for_volume = g_volume_get_mount (volume_for_drive->data);
+ if (mount_for_volume != NULL)
+ {
+ mount_default_location = g_mount_get_default_location (mount_for_volume);
+ current_location_on_volume = file_prefix_or_same (sidebar->current_location,
+ mount_default_location);
+
+ g_object_unref (mount_default_location);
+ g_object_unref (mount_for_volume);
+
+ if (current_location_on_volume)
+ break;
+ }
+ }
+ g_list_free_full (volumes_for_drive, g_object_unref);
+ }
+ }
+
+ return current_location_on_volume;
+}
+
+static void
+do_unmount (GMount *mount,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ if (mount != NULL)
+ {
+ GMountOperation *mount_op;
+ GtkWindow *parent;
+
+ if (is_current_location_on_volume (sidebar, mount, NULL, NULL))
+ open_home (sidebar);
+
+ mount_op = get_unmount_operation (sidebar);
+ parent = gtk_mount_operation_get_parent (GTK_MOUNT_OPERATION (mount_op));
+ nautilus_file_operations_unmount_mount_full (parent, mount, mount_op,
+ FALSE, TRUE, NULL, NULL);
+ g_object_unref (mount_op);
+ }
+}
+
+static void
+unmount_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ GMount *mount;
+
+ g_object_get (sidebar->context_row,
+ "mount", &mount,
+ NULL);
+
+ do_unmount (mount, sidebar);
+
+ if (mount)
+ g_object_unref (mount);
+}
+
+static void
+drive_stop_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ GError *error;
+ char *primary;
+ char *name;
+
+ sidebar = user_data;
+
+ error = NULL;
+ if (!g_drive_stop_finish (G_DRIVE (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_drive_get_name (G_DRIVE (source_object));
+ primary = g_strdup_printf (_("Unable to stop “%s”"), name);
+ g_free (name);
+ emit_show_error_message (sidebar, primary, error->message);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+
+ g_object_unref (sidebar);
+}
+
+static void
+drive_eject_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ GError *error;
+ char *primary;
+ char *name;
+
+ sidebar = user_data;
+
+ error = NULL;
+ if (!g_drive_eject_with_operation_finish (G_DRIVE (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_drive_get_name (G_DRIVE (source_object));
+ primary = g_strdup_printf (_("Unable to eject “%s”"), name);
+ g_free (name);
+ emit_show_error_message (sidebar, primary, error->message);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+
+ g_object_unref (sidebar);
+}
+
+static void
+volume_eject_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ GError *error;
+ char *primary;
+ char *name;
+
+ sidebar = user_data;
+
+ error = NULL;
+ if (!g_volume_eject_with_operation_finish (G_VOLUME (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_volume_get_name (G_VOLUME (source_object));
+ primary = g_strdup_printf (_("Unable to eject %s"), name);
+ g_free (name);
+ emit_show_error_message (sidebar, primary, error->message);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+
+ g_object_unref (sidebar);
+}
+
+static void
+do_eject (GMount *mount,
+ GVolume *volume,
+ GDrive *drive,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ GMountOperation *mount_op;
+ GtkWindow *parent;
+
+ mount_op = get_unmount_operation (sidebar);
+
+ if (is_current_location_on_volume (sidebar, mount, volume, drive))
+ open_home (sidebar);
+
+ if (mount != NULL)
+ {
+ parent = gtk_mount_operation_get_parent (GTK_MOUNT_OPERATION (mount_op));
+ nautilus_file_operations_unmount_mount_full (parent, mount, mount_op,
+ TRUE, TRUE, NULL, NULL);
+ }
+ /* This code path is probably never reached since mount always exists,
+ * and if it doesn't exists we don't offer a way to eject a volume or
+ * drive in the UI. Do it for defensive programming
+ */
+ else if (volume != NULL)
+ g_volume_eject_with_operation (volume, 0, mount_op, NULL, volume_eject_cb,
+ g_object_ref (sidebar));
+ /* This code path is probably never reached since mount always exists,
+ * and if it doesn't exists we don't offer a way to eject a volume or
+ * drive in the UI. Do it for defensive programming
+ */
+ else if (drive != NULL)
+ {
+ if (g_drive_can_stop (drive))
+ g_drive_stop (drive, 0, mount_op, NULL, drive_stop_cb,
+ g_object_ref (sidebar));
+ else
+ g_drive_eject_with_operation (drive, 0, mount_op, NULL, drive_eject_cb,
+ g_object_ref (sidebar));
+ }
+ g_object_unref (mount_op);
+}
+
+static void
+eject_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ GMount *mount;
+ GVolume *volume;
+ GDrive *drive;
+
+ g_object_get (sidebar->context_row,
+ "mount", &mount,
+ "volume", &volume,
+ "drive", &drive,
+ NULL);
+
+ do_eject (mount, volume, drive, sidebar);
+
+ if (mount)
+ g_object_unref (mount);
+ if (volume)
+ g_object_unref (volume);
+ if (drive)
+ g_object_unref (drive);
+}
+
+static gboolean
+eject_or_unmount_bookmark (NautilusGtkSidebarRow *row)
+{
+ gboolean can_unmount, can_eject;
+ GMount *mount;
+ GVolume *volume;
+ GDrive *drive;
+ gboolean ret;
+ NautilusGtkPlacesSidebar *sidebar;
+
+ g_object_get (row,
+ "sidebar", &sidebar,
+ "mount", &mount,
+ "volume", &volume,
+ "drive", &drive,
+ NULL);
+ ret = FALSE;
+
+ check_unmount_and_eject (mount, volume, drive, &can_unmount, &can_eject);
+ /* if we can eject, it has priority over unmount */
+ if (can_eject)
+ {
+ do_eject (mount, volume, drive, sidebar);
+ ret = TRUE;
+ }
+ else if (can_unmount)
+ {
+ do_unmount (mount, sidebar);
+ ret = TRUE;
+ }
+
+ g_object_unref (sidebar);
+ if (mount)
+ g_object_unref (mount);
+ if (volume)
+ g_object_unref (volume);
+ if (drive)
+ g_object_unref (drive);
+
+ return ret;
+}
+
+static gboolean
+eject_or_unmount_selection (NautilusGtkPlacesSidebar *sidebar)
+{
+ gboolean ret;
+ GtkListBoxRow *row;
+
+ row = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box));
+ ret = eject_or_unmount_bookmark (NAUTILUS_GTK_SIDEBAR_ROW (row));
+
+ return ret;
+}
+
+static void
+drive_poll_for_media_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ GError *error;
+ char *primary;
+ char *name;
+
+ sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (user_data);
+
+ error = NULL;
+ if (!g_drive_poll_for_media_finish (G_DRIVE (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_drive_get_name (G_DRIVE (source_object));
+ primary = g_strdup_printf (_("Unable to poll “%s” for media changes"), name);
+ g_free (name);
+ emit_show_error_message (sidebar, primary, error->message);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+
+ g_object_unref (sidebar);
+}
+
+static void
+rescan_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ GDrive *drive;
+
+ g_object_get (sidebar->context_row,
+ "drive", &drive,
+ NULL);
+
+ if (drive != NULL)
+ {
+ g_drive_poll_for_media (drive, NULL, drive_poll_for_media_cb, g_object_ref (sidebar));
+ g_object_unref (drive);
+ }
+}
+
+static void
+drive_start_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ GError *error;
+ char *primary;
+ char *name;
+
+ sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (user_data);
+
+ error = NULL;
+ if (!g_drive_start_finish (G_DRIVE (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_drive_get_name (G_DRIVE (source_object));
+ primary = g_strdup_printf (_("Unable to start “%s”"), name);
+ g_free (name);
+ emit_show_error_message (sidebar, primary, error->message);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+
+ g_object_unref (sidebar);
+}
+
+static void
+start_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ GDrive *drive;
+
+ g_object_get (sidebar->context_row,
+ "drive", &drive,
+ NULL);
+
+ if (drive != NULL)
+ {
+ GMountOperation *mount_op;
+
+ mount_op = get_mount_operation (sidebar);
+
+ g_drive_start (drive, G_DRIVE_START_NONE, mount_op, NULL, drive_start_cb, g_object_ref (sidebar));
+
+ g_object_unref (mount_op);
+ g_object_unref (drive);
+ }
+}
+
+static void
+stop_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ GDrive *drive;
+
+ g_object_get (sidebar->context_row,
+ "drive", &drive,
+ NULL);
+
+ if (drive != NULL)
+ {
+ GMountOperation *mount_op;
+
+ mount_op = get_unmount_operation (sidebar);
+ g_drive_stop (drive, G_MOUNT_UNMOUNT_NONE, mount_op, NULL, drive_stop_cb,
+ g_object_ref (sidebar));
+
+ g_object_unref (mount_op);
+ g_object_unref (drive);
+ }
+}
+
+static gboolean
+on_key_pressed (GtkEventControllerKey *controller,
+ guint keyval,
+ guint keycode,
+ GdkModifierType state,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ guint modifiers;
+ GtkListBoxRow *row;
+
+ row = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box));
+ if (row)
+ {
+ modifiers = gtk_accelerator_get_default_mod_mask ();
+
+ if (keyval == GDK_KEY_Return ||
+ keyval == GDK_KEY_KP_Enter ||
+ keyval == GDK_KEY_ISO_Enter ||
+ keyval == GDK_KEY_space)
+ {
+ NautilusGtkPlacesOpenFlags open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL;
+
+ if ((state & modifiers) == GDK_SHIFT_MASK)
+ open_flags = NAUTILUS_GTK_PLACES_OPEN_NEW_TAB;
+ else if ((state & modifiers) == GDK_CONTROL_MASK)
+ open_flags = NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW;
+
+ open_row (NAUTILUS_GTK_SIDEBAR_ROW (row), open_flags);
+
+ return TRUE;
+ }
+
+ if (keyval == GDK_KEY_Down &&
+ (state & modifiers) == GDK_ALT_MASK)
+ return eject_or_unmount_selection (sidebar);
+
+ if ((keyval == GDK_KEY_Delete ||
+ keyval == GDK_KEY_KP_Delete) &&
+ (state & modifiers) == 0)
+ {
+ remove_bookmark (NAUTILUS_GTK_SIDEBAR_ROW (row));
+ return TRUE;
+ }
+
+ if ((keyval == GDK_KEY_F2) &&
+ (state & modifiers) == 0)
+ {
+ rename_bookmark (NAUTILUS_GTK_SIDEBAR_ROW (row));
+ return TRUE;
+ }
+
+ if ((keyval == GDK_KEY_Menu) ||
+ ((keyval == GDK_KEY_F10) &&
+ (state & modifiers) == GDK_SHIFT_MASK))
+ {
+ popup_menu_cb (NAUTILUS_GTK_SIDEBAR_ROW (row));
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+format_cb (GSimpleAction *action,
+ GVariant *variant,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ g_autoptr (GVolume) volume = NULL;
+ g_autofree gchar *device_identifier = NULL;
+ GVariant *parameters;
+
+ g_object_get (sidebar->context_row, "volume", &volume, NULL);
+ device_identifier = g_volume_get_identifier (volume,
+ G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+
+ parameters = g_variant_new_parsed ("(objectpath '/org/gnome/DiskUtility', @aay [], "
+ "{'options': <{'block-device': <%s>, "
+ "'format-device': <true> }> })", device_identifier);
+
+ nautilus_dbus_launcher_call (nautilus_dbus_launcher_get(),
+ NAUTILUS_DBUS_LAUNCHER_DISKS,
+ "CommandLine",
+ parameters,
+ GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (sidebar))));
+
+}
+
+static GActionEntry entries[] = {
+ { "open", open_shortcut_cb, "i", NULL, NULL },
+ { "open-other", open_shortcut_cb, "i", NULL, NULL },
+ { "bookmark", add_shortcut_cb, NULL, NULL, NULL },
+ { "remove", remove_shortcut_cb, NULL, NULL, NULL },
+ { "rename", rename_shortcut_cb, NULL, NULL, NULL },
+ { "mount", mount_shortcut_cb, NULL, NULL, NULL },
+ { "unmount", unmount_shortcut_cb, NULL, NULL, NULL },
+ { "eject", eject_shortcut_cb, NULL, NULL, NULL },
+ { "rescan", rescan_shortcut_cb, NULL, NULL, NULL },
+ { "start", start_shortcut_cb, NULL, NULL, NULL },
+ { "stop", stop_shortcut_cb, NULL, NULL, NULL },
+ { "properties", properties_cb, NULL, NULL, NULL },
+ { "empty-trash", empty_trash_cb, NULL, NULL, NULL },
+ { "format", format_cb, NULL, NULL, NULL },
+};
+
+static gboolean
+should_show_format_command (GVolume *volume,
+ gchar *uri)
+{
+ g_autofree gchar *unix_device_id = NULL;
+ gboolean disks_available;
+
+ if (volume == NULL || !G_IS_VOLUME (volume) || g_str_has_prefix (uri, "mtp://"))
+ return FALSE;
+
+ unix_device_id = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+ disks_available = nautilus_dbus_launcher_is_available (nautilus_dbus_launcher_get(),
+ NAUTILUS_DBUS_LAUNCHER_DISKS);
+
+ return unix_device_id != NULL && disks_available;
+}
+
+static void
+on_row_popover_destroy (GtkWidget *row_popover,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ if (sidebar)
+ sidebar->popover = NULL;
+}
+
+static void
+build_popup_menu_using_gmenu (NautilusGtkSidebarRow *row)
+{
+ CloudProvidersAccount *cloud_provider_account;
+ NautilusGtkPlacesSidebar *sidebar;
+ GMenuModel *cloud_provider_menu;
+ GActionGroup *cloud_provider_action_group;
+
+ g_object_get (row,
+ "sidebar", &sidebar,
+ "cloud-provider-account", &cloud_provider_account,
+ NULL);
+
+ /* Cloud provider account */
+ if (cloud_provider_account)
+ {
+ GMenu *menu = g_menu_new ();
+ GMenuItem *item;
+ item = g_menu_item_new (_("_Open"), "row.open");
+ g_menu_item_set_action_and_target_value (item, "row.open",
+ g_variant_new_int32 (NAUTILUS_GTK_PLACES_OPEN_NORMAL));
+ g_menu_append_item (menu, item);
+ g_object_unref (item);
+
+ if (sidebar->open_flags & NAUTILUS_GTK_PLACES_OPEN_NEW_TAB)
+ {
+ item = g_menu_item_new (_("Open in New _Tab"), "row.open-other");
+ g_menu_item_set_action_and_target_value (item, "row.open-other", g_variant_new_int32(NAUTILUS_GTK_PLACES_OPEN_NEW_TAB));
+ g_menu_append_item (menu, item);
+ g_object_unref (item);
+ }
+ if (sidebar->open_flags & NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW)
+ {
+ item = g_menu_item_new (_("Open in New _Window"), "row.open-other");
+ g_menu_item_set_action_and_target_value (item, "row.open-other", g_variant_new_int32(NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW));
+ g_menu_append_item (menu, item);
+ g_object_unref (item);
+ }
+ cloud_provider_menu = cloud_providers_account_get_menu_model (cloud_provider_account);
+ cloud_provider_action_group = cloud_providers_account_get_action_group (cloud_provider_account);
+ if (cloud_provider_menu != NULL && cloud_provider_action_group != NULL)
+ {
+ g_menu_append_section (menu, NULL, cloud_provider_menu);
+ gtk_widget_insert_action_group (GTK_WIDGET (sidebar),
+ "cloudprovider",
+ G_ACTION_GROUP (cloud_provider_action_group));
+ }
+ if (sidebar->popover)
+ gtk_widget_unparent (sidebar->popover);
+
+ sidebar->popover = gtk_popover_menu_new_from_model_full (G_MENU_MODEL (menu),
+ GTK_POPOVER_MENU_NESTED);
+ g_object_unref (menu);
+ gtk_widget_set_parent (sidebar->popover, GTK_WIDGET (sidebar));
+ gtk_widget_set_halign (sidebar->popover, GTK_ALIGN_START);
+ gtk_popover_set_has_arrow (GTK_POPOVER (sidebar->popover), FALSE);
+ g_signal_connect (sidebar->popover, "destroy",
+ G_CALLBACK (on_row_popover_destroy), sidebar);
+
+ setup_popover_shadowing (sidebar->popover, sidebar);
+
+ g_object_unref (sidebar);
+ g_object_unref (cloud_provider_account);
+ }
+}
+
+/* Constructs the popover for the sidebar row if needed */
+static void
+create_row_popover (NautilusGtkPlacesSidebar *sidebar,
+ NautilusGtkSidebarRow *row)
+{
+ NautilusGtkPlacesPlaceType type;
+ GMenu *menu, *section;
+ GMenuItem *item;
+ GMount *mount;
+ GVolume *volume;
+ GDrive *drive;
+ GAction *action;
+ gboolean show_unmount, show_eject;
+ gboolean show_stop;
+ g_autofree gchar *uri = NULL;
+ g_autoptr (GFile) file = NULL;
+ gboolean show_properties;
+ g_autoptr (GFile) trash = NULL;
+ gboolean is_trash;
+ CloudProvidersAccount *cloud_provider_account;
+
+ g_object_get (row,
+ "place-type", &type,
+ "drive", &drive,
+ "volume", &volume,
+ "mount", &mount,
+ "uri", &uri,
+ NULL);
+
+ check_unmount_and_eject (mount, volume, drive, &show_unmount, &show_eject);
+ if (uri != NULL)
+ {
+ file = g_file_new_for_uri (uri);
+ trash = g_file_new_for_uri("trash:///");
+ is_trash = g_file_equal (trash, file);
+ show_properties = (g_file_is_native (file) || is_trash || mount != NULL);
+ }
+ else
+ {
+ show_properties = FALSE;
+ is_trash = FALSE;
+ }
+
+ g_object_get (row, "cloud-provider-account", &cloud_provider_account, NULL);
+
+ if (cloud_provider_account)
+ {
+ build_popup_menu_using_gmenu (row);
+ return;
+ }
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (sidebar->row_actions), "remove");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), (type == NAUTILUS_GTK_PLACES_BOOKMARK));
+ action = g_action_map_lookup_action (G_ACTION_MAP (sidebar->row_actions), "rename");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), (type == NAUTILUS_GTK_PLACES_BOOKMARK ||
+ type == NAUTILUS_GTK_PLACES_XDG_DIR));
+ action = g_action_map_lookup_action (G_ACTION_MAP (sidebar->row_actions), "bookmark");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), (type == NAUTILUS_GTK_PLACES_MOUNTED_VOLUME));
+ action = g_action_map_lookup_action (G_ACTION_MAP (sidebar->row_actions), "open");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !gtk_list_box_row_is_selected (GTK_LIST_BOX_ROW (row)));
+ action = g_action_map_lookup_action (G_ACTION_MAP (sidebar->row_actions), "empty-trash");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !nautilus_trash_monitor_is_empty());
+
+ menu = g_menu_new ();
+ section = g_menu_new ();
+
+ item = g_menu_item_new (_("_Open"), "row.open");
+ g_menu_item_set_action_and_target_value (item, "row.open",
+ g_variant_new_int32 (NAUTILUS_GTK_PLACES_OPEN_NORMAL));
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ if (sidebar->open_flags & NAUTILUS_GTK_PLACES_OPEN_NEW_TAB)
+ {
+ item = g_menu_item_new (_("Open in New _Tab"), "row.open-other");
+ g_menu_item_set_action_and_target_value (item, "row.open-other",
+ g_variant_new_int32 (NAUTILUS_GTK_PLACES_OPEN_NEW_TAB));
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+ }
+
+ if (sidebar->open_flags & NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW)
+ {
+ item = g_menu_item_new (_("Open in New _Window"), "row.open-other");
+ g_menu_item_set_action_and_target_value (item, "row.open-other",
+ g_variant_new_int32 (NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW));
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+ }
+
+ g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
+ g_object_unref (section);
+
+ section = g_menu_new ();
+ item = g_menu_item_new (_("Add to _Bookmarks"), "row.bookmark");
+ g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ item = g_menu_item_new (_("_Remove from Bookmarks"), "row.remove");
+ g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ item = g_menu_item_new (_("_Rename"), "row.rename");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
+ g_object_unref (section);
+
+ if (is_trash) {
+ section = g_menu_new ();
+ item = g_menu_item_new (_("Empty Trash"), "row.empty-trash");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
+ g_object_unref (section);
+ }
+
+ section = g_menu_new ();
+
+ if (volume != NULL && mount == NULL &&
+ g_volume_can_mount (volume))
+ {
+ item = g_menu_item_new (_("_Mount"), "row.mount");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+ }
+
+ show_stop = (drive != NULL && g_drive_can_stop (drive));
+
+ if (show_unmount && !show_stop)
+ {
+ item = g_menu_item_new (_("_Unmount"), "row.unmount");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+ }
+
+ if (show_eject)
+ {
+ item = g_menu_item_new (_("_Eject"), "row.eject");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+ }
+
+ if (drive != NULL &&
+ g_drive_is_media_removable (drive) &&
+ !g_drive_is_media_check_automatic (drive) &&
+ g_drive_can_poll_for_media (drive))
+ {
+ item = g_menu_item_new (_("_Detect Media"), "row.rescan");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+ }
+
+ if (drive != NULL &&
+ (g_drive_can_start (drive) || g_drive_can_start_degraded (drive)))
+ {
+ const guint ss_type = g_drive_get_start_stop_type (drive);
+ const char *start_label = _("_Start");
+
+ if (ss_type == G_DRIVE_START_STOP_TYPE_SHUTDOWN) start_label = _("_Power On");
+ else if (ss_type == G_DRIVE_START_STOP_TYPE_NETWORK) start_label = _("_Connect Drive");
+ else if (ss_type == G_DRIVE_START_STOP_TYPE_MULTIDISK) start_label = _("_Start Multi-disk Device");
+ else if (ss_type == G_DRIVE_START_STOP_TYPE_PASSWORD) start_label = _("_Unlock Device");
+
+ item = g_menu_item_new (start_label, "row.start");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+ }
+
+ if (show_stop && !show_unmount)
+ {
+ const guint ss_type = g_drive_get_start_stop_type (drive);
+ const char *stop_label = _("_Stop");
+
+ if (ss_type == G_DRIVE_START_STOP_TYPE_SHUTDOWN) stop_label = _("_Safely Remove Drive");
+ else if (ss_type == G_DRIVE_START_STOP_TYPE_NETWORK) stop_label = _("_Disconnect Drive");
+ else if (ss_type == G_DRIVE_START_STOP_TYPE_MULTIDISK) stop_label = _("_Stop Multi-disk Device");
+ else if (ss_type == G_DRIVE_START_STOP_TYPE_PASSWORD) stop_label = _("_Lock Device");
+
+ item = g_menu_item_new (stop_label, "row.stop");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+ }
+
+ if (should_show_format_command (volume, uri))
+ {
+ item = g_menu_item_new (_("Format…"), "row.format");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+ }
+
+ g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
+ g_object_unref (section);
+
+ if (show_properties) {
+ section = g_menu_new ();
+ item = g_menu_item_new (_("Properties"), "row.properties");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
+ g_object_unref (section);
+ }
+
+ sidebar->popover = gtk_popover_menu_new_from_model (G_MENU_MODEL (menu));
+ g_object_unref (menu);
+ gtk_widget_set_parent (sidebar->popover, GTK_WIDGET (sidebar));
+ gtk_widget_set_halign (sidebar->popover, GTK_ALIGN_START);
+ gtk_popover_set_has_arrow (GTK_POPOVER (sidebar->popover), FALSE);
+ g_signal_connect (sidebar->popover, "destroy", G_CALLBACK (on_row_popover_destroy), sidebar);
+
+ setup_popover_shadowing (sidebar->popover, sidebar);
+}
+
+static void
+show_row_popover (NautilusGtkSidebarRow *row,
+ double x,
+ double y)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ double x_in_sidebar, y_in_sidebar;
+
+ g_object_get (row, "sidebar", &sidebar, NULL);
+
+ g_clear_pointer (&sidebar->popover, gtk_widget_unparent);
+
+ create_row_popover (sidebar, row);
+
+ if (x == -1 && y == -1)
+ _popover_set_pointing_to_widget (GTK_POPOVER (sidebar->popover), GTK_WIDGET (row));
+ else
+ {
+ gtk_widget_translate_coordinates (GTK_WIDGET (row), GTK_WIDGET (sidebar),
+ x, y, &x_in_sidebar, &y_in_sidebar);
+ gtk_popover_set_pointing_to (GTK_POPOVER (sidebar->popover),
+ &(GdkRectangle){x_in_sidebar, y_in_sidebar, 0, 0});
+ }
+
+ sidebar->context_row = row;
+ gtk_popover_popup (GTK_POPOVER (sidebar->popover));
+
+ g_object_unref (sidebar);
+}
+
+static void
+on_row_activated (GtkListBox *list_box,
+ GtkListBoxRow *row,
+ gpointer user_data)
+{
+ NautilusGtkSidebarRow *selected_row;
+
+ /* Avoid to open a location if the user is dragging. Changing the location
+ * while dragging usually makes clients changing the view of the files, which
+ * is confusing while the user has the attention on the drag
+ */
+ if (NAUTILUS_GTK_PLACES_SIDEBAR (user_data)->dragging_over)
+ return;
+
+ selected_row = NAUTILUS_GTK_SIDEBAR_ROW (gtk_list_box_get_selected_row (list_box));
+ open_row (selected_row, 0);
+}
+
+static void
+on_row_pressed (GtkGestureClick *gesture,
+ int n_press,
+ double x,
+ double y,
+ NautilusGtkSidebarRow *row)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ NautilusGtkPlacesSectionType section_type;
+ NautilusGtkPlacesPlaceType row_type;
+
+ g_object_get (row,
+ "sidebar", &sidebar,
+ "section_type", &section_type,
+ "place-type", &row_type,
+ NULL);
+
+ if (section_type == NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS)
+ {
+ sidebar->drag_row = GTK_WIDGET (row);
+ sidebar->drag_row_x = (int)x;
+ sidebar->drag_row_y = (int)y;
+ }
+
+ g_object_unref (sidebar);
+}
+
+static void
+on_row_released (GtkGestureClick *gesture,
+ int n_press,
+ double x,
+ double y,
+ NautilusGtkSidebarRow *row)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ NautilusGtkPlacesSectionType section_type;
+ NautilusGtkPlacesPlaceType row_type;
+ guint button, state;
+
+ g_object_get (row,
+ "sidebar", &sidebar,
+ "section_type", &section_type,
+ "place-type", &row_type,
+ NULL);
+
+ button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+ state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture));
+
+ if (row)
+ {
+ if (button == 2)
+ {
+ NautilusGtkPlacesOpenFlags open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL;
+
+ open_flags = (state & GDK_CONTROL_MASK) ?
+ NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW :
+ NAUTILUS_GTK_PLACES_OPEN_NEW_TAB;
+
+ open_row (NAUTILUS_GTK_SIDEBAR_ROW (row), open_flags);
+ gtk_gesture_set_state (GTK_GESTURE (gesture),
+ GTK_EVENT_SEQUENCE_CLAIMED);
+ }
+ else if (button == 3)
+ {
+ if (row_type != NAUTILUS_GTK_PLACES_CONNECT_TO_SERVER)
+ show_row_popover (NAUTILUS_GTK_SIDEBAR_ROW (row), x, y);
+ }
+ }
+}
+
+static void
+on_row_dragged (GtkGestureDrag *gesture,
+ double x,
+ double y,
+ NautilusGtkSidebarRow *row)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+
+ g_object_get (row, "sidebar", &sidebar, NULL);
+
+ if (sidebar->drag_row == NULL || sidebar->dragging_over)
+ {
+ g_object_unref (sidebar);
+ return;
+ }
+
+ if (gtk_drag_check_threshold (GTK_WIDGET (row), 0, 0, x, y))
+ {
+ double start_x, start_y;
+ double drag_x, drag_y;
+ GdkContentProvider *content;
+ GdkSurface *surface;
+ GdkDevice *device;
+ GtkAllocation allocation;
+ GtkWidget *drag_widget;
+ GdkDrag *drag;
+
+ gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
+ gtk_widget_translate_coordinates (GTK_WIDGET (row),
+ GTK_WIDGET (sidebar),
+ start_x, start_y,
+ &drag_x, &drag_y);
+
+ sidebar->dragging_over = TRUE;
+
+ content = gdk_content_provider_new_typed (NAUTILUS_TYPE_GTK_SIDEBAR_ROW, sidebar->drag_row);
+
+ surface = gtk_native_get_surface (gtk_widget_get_native (GTK_WIDGET (sidebar)));
+ device = gtk_gesture_get_device (GTK_GESTURE (gesture));
+
+ drag = gdk_drag_begin (surface, device, content, GDK_ACTION_MOVE, drag_x, drag_y);
+
+ g_object_unref (content);
+
+ g_signal_connect (drag, "dnd-finished", G_CALLBACK (dnd_finished_cb), sidebar);
+ g_signal_connect (drag, "cancel", G_CALLBACK (dnd_cancel_cb), sidebar);
+
+ gtk_widget_get_allocation (sidebar->drag_row, &allocation);
+ gtk_widget_hide (sidebar->drag_row);
+
+ drag_widget = GTK_WIDGET (nautilus_gtk_sidebar_row_clone (NAUTILUS_GTK_SIDEBAR_ROW (sidebar->drag_row)));
+ sidebar->drag_row_height = allocation.height;
+ gtk_widget_set_size_request (drag_widget, allocation.width, allocation.height);
+ gtk_widget_set_opacity (drag_widget, 0.8);
+
+ gtk_drag_icon_set_child (GTK_DRAG_ICON (gtk_drag_icon_get_for_drag (drag)), drag_widget);
+
+ g_object_unref (drag);
+ }
+
+ g_object_unref (sidebar);
+}
+
+static void
+popup_menu_cb (NautilusGtkSidebarRow *row)
+{
+ NautilusGtkPlacesPlaceType row_type;
+
+ g_object_get (row, "place-type", &row_type, NULL);
+
+ if (row_type != NAUTILUS_GTK_PLACES_CONNECT_TO_SERVER)
+ show_row_popover (row, -1, -1);
+}
+
+static void
+long_press_cb (GtkGesture *gesture,
+ double x,
+ double y,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ GtkWidget *row;
+
+ row = GTK_WIDGET (gtk_list_box_get_row_at_y (GTK_LIST_BOX (sidebar->list_box), y));
+ if (NAUTILUS_IS_GTK_SIDEBAR_ROW (row))
+ popup_menu_cb (NAUTILUS_GTK_SIDEBAR_ROW (row));
+}
+
+static int
+list_box_sort_func (GtkListBoxRow *row1,
+ GtkListBoxRow *row2,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSectionType section_type_1, section_type_2;
+ NautilusGtkPlacesPlaceType place_type_1, place_type_2;
+ char *label_1, *label_2;
+ int index_1, index_2;
+ int retval = 0;
+
+ g_object_get (row1,
+ "label", &label_1,
+ "place-type", &place_type_1,
+ "section-type", &section_type_1,
+ "order-index", &index_1,
+ NULL);
+ g_object_get (row2,
+ "label", &label_2,
+ "place-type", &place_type_2,
+ "section-type", &section_type_2,
+ "order-index", &index_2,
+ NULL);
+
+ /* Always last position for "connect to server" */
+ if (place_type_1 == NAUTILUS_GTK_PLACES_CONNECT_TO_SERVER)
+ {
+ retval = 1;
+ }
+ else if (place_type_2 == NAUTILUS_GTK_PLACES_CONNECT_TO_SERVER)
+ {
+ retval = -1;
+ }
+ else
+ {
+ if (section_type_1 == section_type_2)
+ {
+ if ((section_type_1 == NAUTILUS_GTK_PLACES_SECTION_COMPUTER &&
+ place_type_1 == place_type_2 &&
+ place_type_1 == NAUTILUS_GTK_PLACES_XDG_DIR) ||
+ section_type_1 == NAUTILUS_GTK_PLACES_SECTION_MOUNTS)
+ {
+ retval = g_utf8_collate (label_1, label_2);
+ }
+ else if ((place_type_1 == NAUTILUS_GTK_PLACES_BOOKMARK || place_type_2 == NAUTILUS_GTK_PLACES_DROP_FEEDBACK) &&
+ (place_type_1 == NAUTILUS_GTK_PLACES_DROP_FEEDBACK || place_type_2 == NAUTILUS_GTK_PLACES_BOOKMARK))
+ {
+ retval = index_1 - index_2;
+ }
+ /* We order the bookmarks sections based on the bookmark index that we
+ * set on the row as an order-index property, but we have to deal with
+ * the placeholder row wanted to be between two consecutive bookmarks,
+ * with two consecutive order-index values which is the usual case.
+ * For that, in the list box sort func we give priority to the placeholder row,
+ * that means that if the index-order is the same as another bookmark
+ * the placeholder row goes before. However if we want to show it after
+ * the current row, for instance when the cursor is in the lower half
+ * of the row, we need to increase the order-index.
+ */
+ else if (place_type_1 == NAUTILUS_GTK_PLACES_BOOKMARK_PLACEHOLDER && place_type_2 == NAUTILUS_GTK_PLACES_BOOKMARK)
+ {
+ if (index_1 == index_2)
+ retval = index_1 - index_2 - 1;
+ else
+ retval = index_1 - index_2;
+ }
+ else if (place_type_1 == NAUTILUS_GTK_PLACES_BOOKMARK && place_type_2 == NAUTILUS_GTK_PLACES_BOOKMARK_PLACEHOLDER)
+ {
+ if (index_1 == index_2)
+ retval = index_1 - index_2 + 1;
+ else
+ retval = index_1 - index_2;
+ }
+ }
+ else
+ {
+ /* Order by section. That means the order in the enum of section types
+ * define the actual order of them in the list */
+ retval = section_type_1 - section_type_2;
+ }
+ }
+
+ g_free (label_1);
+ g_free (label_2);
+
+ return retval;
+}
+
+static void
+update_hostname (NautilusGtkPlacesSidebar *sidebar)
+{
+ GVariant *variant;
+ gsize len;
+ const char *hostname;
+
+ if (sidebar->hostnamed_proxy == NULL)
+ return;
+
+ variant = g_dbus_proxy_get_cached_property (sidebar->hostnamed_proxy,
+ "PrettyHostname");
+ if (variant == NULL)
+ return;
+
+ hostname = g_variant_get_string (variant, &len);
+ if (len > 0 &&
+ g_strcmp0 (sidebar->hostname, hostname) != 0)
+ {
+ g_free (sidebar->hostname);
+ sidebar->hostname = g_strdup (hostname);
+ update_places (sidebar);
+ }
+
+ g_variant_unref (variant);
+}
+
+static void
+hostname_proxy_new_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar = user_data;
+ GError *error = NULL;
+ GDBusProxy *proxy;
+
+ proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ return;
+ }
+
+ sidebar->hostnamed_proxy = proxy;
+ g_clear_object (&sidebar->hostnamed_cancellable);
+
+ if (error != NULL)
+ {
+ g_debug ("Failed to create D-Bus proxy: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_signal_connect_swapped (sidebar->hostnamed_proxy,
+ "g-properties-changed",
+ G_CALLBACK (update_hostname),
+ sidebar);
+ update_hostname (sidebar);
+}
+
+static void
+create_volume_monitor (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_assert (sidebar->volume_monitor == NULL);
+
+ sidebar->volume_monitor = g_volume_monitor_get ();
+
+ g_signal_connect_object (sidebar->volume_monitor, "volume_added",
+ G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
+ g_signal_connect_object (sidebar->volume_monitor, "volume_removed",
+ G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
+ g_signal_connect_object (sidebar->volume_monitor, "volume_changed",
+ G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
+ g_signal_connect_object (sidebar->volume_monitor, "mount_added",
+ G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
+ g_signal_connect_object (sidebar->volume_monitor, "mount_removed",
+ G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
+ g_signal_connect_object (sidebar->volume_monitor, "mount_changed",
+ G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
+ g_signal_connect_object (sidebar->volume_monitor, "drive_disconnected",
+ G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
+ g_signal_connect_object (sidebar->volume_monitor, "drive_connected",
+ G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
+ g_signal_connect_object (sidebar->volume_monitor, "drive_changed",
+ G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
+}
+
+static void
+shell_shows_desktop_changed (GtkSettings *settings,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar = user_data;
+ gboolean show_desktop;
+
+ g_assert (settings == sidebar->gtk_settings);
+
+ /* Check if the user explicitly set this and, if so, don't change it. */
+ if (sidebar->show_desktop_set)
+ return;
+
+ g_object_get (settings, "gtk-shell-shows-desktop", &show_desktop, NULL);
+
+ if (show_desktop != sidebar->show_desktop)
+ {
+ sidebar->show_desktop = show_desktop;
+ update_places (sidebar);
+ g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_DESKTOP]);
+ }
+}
+
+static void
+nautilus_gtk_places_sidebar_init (NautilusGtkPlacesSidebar *sidebar)
+{
+ GtkDropTarget *target;
+ gboolean show_desktop;
+ GtkEventController *controller;
+ GtkGesture *gesture;
+
+ sidebar->cancellable = g_cancellable_new ();
+
+ sidebar->show_trash = TRUE;
+ sidebar->show_other_locations = TRUE;
+ sidebar->show_recent = TRUE;
+ sidebar->show_desktop = TRUE;
+
+ sidebar->shortcuts = g_list_store_new (G_TYPE_FILE);
+
+ create_volume_monitor (sidebar);
+
+ sidebar->open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL;
+
+ sidebar->bookmarks_manager = _nautilus_gtk_bookmarks_manager_new ((GtkBookmarksChangedFunc)update_places, sidebar);
+
+ g_signal_connect_object (nautilus_trash_monitor_get (), "trash-state-changed",
+ G_CALLBACK (update_trash_icon), sidebar,
+ G_CONNECT_SWAPPED);
+
+ sidebar->swin = gtk_scrolled_window_new ();
+ gtk_widget_set_parent (sidebar->swin, GTK_WIDGET (sidebar));
+ gtk_widget_set_size_request (sidebar->swin, 140, 280);
+
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sidebar->swin),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+
+ /* list box */
+ sidebar->list_box = gtk_list_box_new ();
+ gtk_widget_add_css_class (sidebar->list_box, "navigation-sidebar");
+
+ gtk_list_box_set_header_func (GTK_LIST_BOX (sidebar->list_box),
+ list_box_header_func, sidebar, NULL);
+ gtk_list_box_set_sort_func (GTK_LIST_BOX (sidebar->list_box),
+ list_box_sort_func, NULL, NULL);
+ gtk_list_box_set_selection_mode (GTK_LIST_BOX (sidebar->list_box), GTK_SELECTION_SINGLE);
+ gtk_list_box_set_activate_on_single_click (GTK_LIST_BOX (sidebar->list_box), TRUE);
+
+ g_signal_connect (sidebar->list_box, "row-activated",
+ G_CALLBACK (on_row_activated), sidebar);
+
+ controller = gtk_event_controller_key_new ();
+ g_signal_connect (controller, "key-pressed",
+ G_CALLBACK (on_key_pressed), sidebar);
+ gtk_widget_add_controller (sidebar->list_box, controller);
+
+ gesture = gtk_gesture_long_press_new ();
+ gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), TRUE);
+ g_signal_connect (gesture, "pressed",
+ G_CALLBACK (long_press_cb), sidebar);
+ gtk_widget_add_controller (GTK_WIDGET (sidebar), GTK_EVENT_CONTROLLER (gesture));
+
+ /* DND support */
+ target = gtk_drop_target_new (G_TYPE_INVALID, GDK_ACTION_ALL);
+ gtk_drop_target_set_preload (target, TRUE);
+ gtk_drop_target_set_gtypes (target, (GType[2]) { NAUTILUS_TYPE_GTK_SIDEBAR_ROW, GDK_TYPE_FILE_LIST }, 2);
+ g_signal_connect (target, "enter", G_CALLBACK (drag_motion_callback), sidebar);
+ g_signal_connect (target, "motion", G_CALLBACK (drag_motion_callback), sidebar);
+ g_signal_connect (target, "drop", G_CALLBACK (drag_drop_callback), sidebar);
+ g_signal_connect (target, "leave", G_CALLBACK (drag_leave_callback), sidebar);
+ gtk_widget_add_controller (sidebar->list_box, GTK_EVENT_CONTROLLER (target));
+
+ sidebar->drag_row = NULL;
+ sidebar->row_placeholder = NULL;
+ sidebar->dragging_over = FALSE;
+
+ gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sidebar->swin), sidebar->list_box);
+
+ sidebar->hostname = g_strdup (_("Computer"));
+ sidebar->hostnamed_cancellable = g_cancellable_new ();
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES,
+ NULL,
+ "org.freedesktop.hostname1",
+ "/org/freedesktop/hostname1",
+ "org.freedesktop.hostname1",
+ sidebar->hostnamed_cancellable,
+ hostname_proxy_new_cb,
+ sidebar);
+
+ sidebar->drop_state = DROP_STATE_NORMAL;
+
+ /* Don't bother trying to trace this across hierarchy changes... */
+ sidebar->gtk_settings = gtk_settings_get_default ();
+ g_signal_connect (sidebar->gtk_settings, "notify::gtk-shell-shows-desktop",
+ G_CALLBACK (shell_shows_desktop_changed), sidebar);
+ g_object_get (sidebar->gtk_settings, "gtk-shell-shows-desktop", &show_desktop, NULL);
+ sidebar->show_desktop = show_desktop;
+
+ /* Cloud providers */
+ sidebar->cloud_manager = cloud_providers_collector_dup_singleton ();
+ g_signal_connect_swapped (sidebar->cloud_manager,
+ "providers-changed",
+ G_CALLBACK (update_places),
+ sidebar);
+
+ /* populate the sidebar */
+ update_places (sidebar);
+
+ sidebar->row_actions = G_ACTION_GROUP (g_simple_action_group_new ());
+ g_action_map_add_action_entries (G_ACTION_MAP (sidebar->row_actions),
+ entries, G_N_ELEMENTS (entries),
+ sidebar);
+ gtk_widget_insert_action_group (GTK_WIDGET (sidebar), "row", sidebar->row_actions);
+
+ gtk_accessible_update_property (GTK_ACCESSIBLE (sidebar),
+ GTK_ACCESSIBLE_PROPERTY_LABEL, _("Sidebar"),
+ GTK_ACCESSIBLE_PROPERTY_DESCRIPTION,
+ _("List of common shortcuts, mountpoints, and bookmarks."), -1);
+}
+
+static void
+nautilus_gtk_places_sidebar_set_property (GObject *obj,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (obj);
+
+ switch (property_id)
+ {
+ case PROP_LOCATION:
+ nautilus_gtk_places_sidebar_set_location (sidebar, g_value_get_object (value));
+ break;
+
+ case PROP_OPEN_FLAGS:
+ nautilus_gtk_places_sidebar_set_open_flags (sidebar, g_value_get_flags (value));
+ break;
+
+ case PROP_SHOW_RECENT:
+ nautilus_gtk_places_sidebar_set_show_recent (sidebar, g_value_get_boolean (value));
+ break;
+
+ case PROP_SHOW_DESKTOP:
+ nautilus_gtk_places_sidebar_set_show_desktop (sidebar, g_value_get_boolean (value));
+ break;
+
+ case PROP_SHOW_ENTER_LOCATION:
+ nautilus_gtk_places_sidebar_set_show_enter_location (sidebar, g_value_get_boolean (value));
+ break;
+
+ case PROP_SHOW_OTHER_LOCATIONS:
+ nautilus_gtk_places_sidebar_set_show_other_locations (sidebar, g_value_get_boolean (value));
+ break;
+
+ case PROP_SHOW_TRASH:
+ nautilus_gtk_places_sidebar_set_show_trash (sidebar, g_value_get_boolean (value));
+ break;
+
+ case PROP_SHOW_STARRED_LOCATION:
+ nautilus_gtk_places_sidebar_set_show_starred_location (sidebar, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
+ break;
+ }
+}
+
+static void
+nautilus_gtk_places_sidebar_get_property (GObject *obj,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (obj);
+
+ switch (property_id)
+ {
+ case PROP_LOCATION:
+ g_value_take_object (value, nautilus_gtk_places_sidebar_get_location (sidebar));
+ break;
+
+ case PROP_OPEN_FLAGS:
+ g_value_set_flags (value, nautilus_gtk_places_sidebar_get_open_flags (sidebar));
+ break;
+
+ case PROP_SHOW_RECENT:
+ g_value_set_boolean (value, nautilus_gtk_places_sidebar_get_show_recent (sidebar));
+ break;
+
+ case PROP_SHOW_DESKTOP:
+ g_value_set_boolean (value, nautilus_gtk_places_sidebar_get_show_desktop (sidebar));
+ break;
+
+ case PROP_SHOW_ENTER_LOCATION:
+ g_value_set_boolean (value, nautilus_gtk_places_sidebar_get_show_enter_location (sidebar));
+ break;
+
+ case PROP_SHOW_OTHER_LOCATIONS:
+ g_value_set_boolean (value, nautilus_gtk_places_sidebar_get_show_other_locations (sidebar));
+ break;
+
+ case PROP_SHOW_TRASH:
+ g_value_set_boolean (value, nautilus_gtk_places_sidebar_get_show_trash (sidebar));
+ break;
+
+ case PROP_SHOW_STARRED_LOCATION:
+ g_value_set_boolean (value, nautilus_gtk_places_sidebar_get_show_starred_location (sidebar));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
+ break;
+ }
+}
+
+static void
+nautilus_gtk_places_sidebar_dispose (GObject *object)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ GList *l;
+
+ sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (object);
+
+ if (sidebar->cancellable)
+ {
+ g_cancellable_cancel (sidebar->cancellable);
+ g_object_unref (sidebar->cancellable);
+ sidebar->cancellable = NULL;
+ }
+
+ if (sidebar->bookmarks_manager != NULL)
+ {
+ _nautilus_gtk_bookmarks_manager_free (sidebar->bookmarks_manager);
+ sidebar->bookmarks_manager = NULL;
+ }
+
+ g_clear_pointer (&sidebar->popover, gtk_widget_unparent);
+
+ if (sidebar->rename_popover)
+ {
+ gtk_widget_unparent (sidebar->rename_popover);
+ sidebar->rename_popover = NULL;
+ sidebar->rename_entry = NULL;
+ sidebar->rename_button = NULL;
+ sidebar->rename_error = NULL;
+ }
+
+ if (sidebar->trash_row)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (sidebar->trash_row),
+ (gpointer *) &sidebar->trash_row);
+ sidebar->trash_row = NULL;
+ }
+
+ if (sidebar->volume_monitor != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (sidebar->volume_monitor,
+ update_places, sidebar);
+ g_clear_object (&sidebar->volume_monitor);
+ }
+
+ if (sidebar->hostnamed_cancellable != NULL)
+ {
+ g_cancellable_cancel (sidebar->hostnamed_cancellable);
+ g_clear_object (&sidebar->hostnamed_cancellable);
+ }
+
+ g_clear_object (&sidebar->hostnamed_proxy);
+ g_free (sidebar->hostname);
+ sidebar->hostname = NULL;
+
+ if (sidebar->gtk_settings)
+ {
+ g_signal_handlers_disconnect_by_func (sidebar->gtk_settings, shell_shows_desktop_changed, sidebar);
+ sidebar->gtk_settings = NULL;
+ }
+
+ g_clear_object (&sidebar->current_location);
+ g_clear_pointer (&sidebar->rename_uri, g_free);
+ g_clear_object (&sidebar->shortcuts);
+
+ g_clear_handle_id (&sidebar->hover_timer_id, g_source_remove);
+
+ for (l = sidebar->unready_accounts; l != NULL; l = l->next)
+ {
+ g_signal_handlers_disconnect_by_data (l->data, sidebar);
+ }
+ g_list_free_full (sidebar->unready_accounts, g_object_unref);
+ sidebar->unready_accounts = NULL;
+ if (sidebar->cloud_manager)
+ {
+ g_signal_handlers_disconnect_by_data (sidebar->cloud_manager, sidebar);
+ for (l = cloud_providers_collector_get_providers (sidebar->cloud_manager);
+ l != NULL; l = l->next)
+ {
+ g_signal_handlers_disconnect_by_data (l->data, sidebar);
+ }
+ g_object_unref (sidebar->cloud_manager);
+ sidebar->cloud_manager = NULL;
+ }
+
+ G_OBJECT_CLASS (nautilus_gtk_places_sidebar_parent_class)->dispose (object);
+}
+
+static void
+nautilus_gtk_places_sidebar_finalize (GObject *object)
+{
+ NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (object);
+
+ g_clear_object (&sidebar->row_actions);
+
+ g_clear_pointer (&sidebar->swin, gtk_widget_unparent);
+
+ G_OBJECT_CLASS (nautilus_gtk_places_sidebar_parent_class)->finalize (object);
+}
+
+static void
+nautilus_gtk_places_sidebar_measure (GtkWidget *widget,
+ GtkOrientation orientation,
+ int for_size,
+ int *minimum,
+ int *natural,
+ int *minimum_baseline,
+ int *natural_baseline)
+{
+ NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (widget);
+
+ gtk_widget_measure (sidebar->swin,
+ orientation,
+ for_size,
+ minimum, natural,
+ minimum_baseline, natural_baseline);
+}
+
+static void
+nautilus_gtk_places_sidebar_size_allocate (GtkWidget *widget,
+ int width,
+ int height,
+ int baseline)
+{
+ NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (widget);
+
+ gtk_widget_size_allocate (sidebar->swin,
+ &(GtkAllocation) { 0, 0, width, height },
+ baseline);
+
+ if (sidebar->popover)
+ gtk_popover_present (GTK_POPOVER (sidebar->popover));
+
+ if (sidebar->rename_popover)
+ gtk_popover_present (GTK_POPOVER (sidebar->rename_popover));
+}
+
+static void
+nautilus_gtk_places_sidebar_class_init (NautilusGtkPlacesSidebarClass *class)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+
+ gobject_class->dispose = nautilus_gtk_places_sidebar_dispose;
+ gobject_class->finalize = nautilus_gtk_places_sidebar_finalize;
+ gobject_class->set_property = nautilus_gtk_places_sidebar_set_property;
+ gobject_class->get_property = nautilus_gtk_places_sidebar_get_property;
+
+ widget_class->measure = nautilus_gtk_places_sidebar_measure;
+ widget_class->size_allocate = nautilus_gtk_places_sidebar_size_allocate;
+
+ /*
+ * NautilusGtkPlacesSidebar::open-location:
+ * @sidebar: the object which received the signal.
+ * @location: (type Gio.File): GFile to which the caller should switch.
+ * @open_flags: a single value from NautilusGtkPlacesOpenFlags specifying how the @location should be opened.
+ *
+ * The places sidebar emits this signal when the user selects a location
+ * in it. The calling application should display the contents of that
+ * location; for example, a file manager should show a list of files in
+ * the specified location.
+ */
+ places_sidebar_signals [OPEN_LOCATION] =
+ g_signal_new ("open-location",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, open_location),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 2,
+ G_TYPE_OBJECT,
+ NAUTILUS_TYPE_OPEN_FLAGS);
+
+ /*
+ * NautilusGtkPlacesSidebar::show-error-message:
+ * @sidebar: the object which received the signal.
+ * @primary: primary message with a summary of the error to show.
+ * @secondary: secondary message with details of the error to show.
+ *
+ * The places sidebar emits this signal when it needs the calling
+ * application to present an error message. Most of these messages
+ * refer to mounting or unmounting media, for example, when a drive
+ * cannot be started for some reason.
+ */
+ places_sidebar_signals [SHOW_ERROR_MESSAGE] =
+ g_signal_new ("show-error-message",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, show_error_message),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ /*
+ * NautilusGtkPlacesSidebar::show-enter-location:
+ * @sidebar: the object which received the signal.
+ *
+ * The places sidebar emits this signal when it needs the calling
+ * application to present a way to directly enter a location.
+ * For example, the application may bring up a dialog box asking for
+ * a URL like "http://http.example.com".
+ */
+ places_sidebar_signals [SHOW_ENTER_LOCATION] =
+ g_signal_new ("show-enter-location",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, show_enter_location),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 0);
+
+ /*
+ * NautilusGtkPlacesSidebar::drag-action-requested:
+ * @sidebar: the object which received the signal.
+ * @drop: (type Gdk.Drop): GdkDrop with information about the drag operation
+ * @dest_file: (type Gio.File): GFile with the tentative location that is being hovered for a drop
+ * @source_file_list: (type GLib.SList) (element-type GFile) (transfer none):
+ * List of GFile that are being dragged
+ *
+ * When the user starts a drag-and-drop operation and the sidebar needs
+ * to ask the application for which drag action to perform, then the
+ * sidebar will emit this signal.
+ *
+ * The application can evaluate the @context for customary actions, or
+ * it can check the type of the files indicated by @source_file_list against the
+ * possible actions for the destination @dest_file.
+ *
+ * The drag action to use must be the return value of the signal handler.
+ *
+ * Returns: The drag action to use, for example, GDK_ACTION_COPY
+ * or GDK_ACTION_MOVE, or 0 if no action is allowed here (i.e. drops
+ * are not allowed in the specified @dest_file).
+ */
+ places_sidebar_signals [DRAG_ACTION_REQUESTED] =
+ g_signal_new ("drag-action-requested",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, drag_action_requested),
+ NULL, NULL,
+ NULL,
+ GDK_TYPE_DRAG_ACTION, 2,
+ G_TYPE_OBJECT,
+ GDK_TYPE_FILE_LIST);
+
+ /*
+ * NautilusGtkPlacesSidebar::drag-action-ask:
+ * @sidebar: the object which received the signal.
+ * @actions: Possible drag actions that need to be asked for.
+ *
+ * The places sidebar emits this signal when it needs to ask the application
+ * to pop up a menu to ask the user for which drag action to perform.
+ *
+ * Returns: the final drag action that the sidebar should pass to the drag side
+ * of the drag-and-drop operation.
+ */
+ places_sidebar_signals [DRAG_ACTION_ASK] =
+ g_signal_new ("drag-action-ask",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, drag_action_ask),
+ NULL, NULL,
+ NULL,
+ GDK_TYPE_DRAG_ACTION, 1,
+ GDK_TYPE_DRAG_ACTION);
+
+ /*
+ * NautilusGtkPlacesSidebar::drag-perform-drop:
+ * @sidebar: the object which received the signal.
+ * @dest_file: (type Gio.File): Destination GFile.
+ * @source_file_list: (type GLib.SList) (element-type GFile) (transfer none):
+ * GSList of GFile that got dropped.
+ * @action: Drop action to perform.
+ *
+ * The places sidebar emits this signal when the user completes a
+ * drag-and-drop operation and one of the sidebar's items is the
+ * destination. This item is in the @dest_file, and the
+ * @source_file_list has the list of files that are dropped into it and
+ * which should be copied/moved/etc. based on the specified @action.
+ */
+ places_sidebar_signals [DRAG_PERFORM_DROP] =
+ g_signal_new ("drag-perform-drop",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, drag_perform_drop),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 3,
+ G_TYPE_OBJECT,
+ GDK_TYPE_FILE_LIST,
+ GDK_TYPE_DRAG_ACTION);
+
+ /*
+ * NautilusGtkPlacesSidebar::show-other-locations-with-flags:
+ * @sidebar: the object which received the signal.
+ * @open_flags: a single value from NautilusGtkPlacesOpenFlags specifying how it should be opened.
+ *
+ * The places sidebar emits this signal when it needs the calling
+ * application to present a way to show other locations e.g. drives
+ * and network access points.
+ * For example, the application may bring up a page showing persistent
+ * volumes and discovered network addresses.
+ */
+ places_sidebar_signals [SHOW_OTHER_LOCATIONS_WITH_FLAGS] =
+ g_signal_new ("show-other-locations-with-flags",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, show_other_locations_with_flags),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 1,
+ NAUTILUS_TYPE_OPEN_FLAGS);
+
+ /*
+ * NautilusGtkPlacesSidebar::mount:
+ * @sidebar: the object which received the signal.
+ * @mount_operation: the GMountOperation that is going to start.
+ *
+ * The places sidebar emits this signal when it starts a new operation
+ * because the user clicked on some location that needs mounting.
+ * In this way the application using the NautilusGtkPlacesSidebar can track the
+ * progress of the operation and, for example, show a notification.
+ */
+ places_sidebar_signals [MOUNT] =
+ g_signal_new ("mount",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, mount),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_MOUNT_OPERATION);
+ /*
+ * NautilusGtkPlacesSidebar::unmount:
+ * @sidebar: the object which received the signal.
+ * @mount_operation: the GMountOperation that is going to start.
+ *
+ * The places sidebar emits this signal when it starts a new operation
+ * because the user for example ejected some drive or unmounted a mount.
+ * In this way the application using the NautilusGtkPlacesSidebar can track the
+ * progress of the operation and, for example, show a notification.
+ */
+ places_sidebar_signals [UNMOUNT] =
+ g_signal_new ("unmount",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, unmount),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_MOUNT_OPERATION);
+
+ /*
+ * NautilusGtkPlacesSidebar::show-starred-location:
+ * @sidebar: the object which received the signal
+ * @flags: the flags for the operation
+ *
+ * The places sidebar emits this signal when it needs the calling
+ * application to present a way to show the starred files. In GNOME,
+ * starred files are implemented by setting the nao:predefined-tag-favorite
+ * tag in the tracker database.
+ */
+ places_sidebar_signals [SHOW_STARRED_LOCATION] =
+ g_signal_new ("show-starred-location",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, show_starred_location),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 1,
+ NAUTILUS_TYPE_OPEN_FLAGS);
+
+ properties[PROP_LOCATION] =
+ g_param_spec_object ("location",
+ "Location to Select",
+ "The location to highlight in the sidebar",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+ properties[PROP_OPEN_FLAGS] =
+ g_param_spec_flags ("open-flags",
+ "Open Flags",
+ "Modes in which the calling application can open locations selected in the sidebar",
+ NAUTILUS_TYPE_OPEN_FLAGS,
+ NAUTILUS_GTK_PLACES_OPEN_NORMAL,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+ properties[PROP_SHOW_RECENT] =
+ g_param_spec_boolean ("show-recent",
+ "Show recent files",
+ "Whether the sidebar includes a builtin shortcut for recent files",
+ TRUE,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+ properties[PROP_SHOW_DESKTOP] =
+ g_param_spec_boolean ("show-desktop",
+ "Show “Desktop”",
+ "Whether the sidebar includes a builtin shortcut to the Desktop folder",
+ TRUE,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+ properties[PROP_SHOW_ENTER_LOCATION] =
+ g_param_spec_boolean ("show-enter-location",
+ "Show “Enter Location”",
+ "Whether the sidebar includes a builtin shortcut to manually enter a location",
+ FALSE,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+ properties[PROP_SHOW_TRASH] =
+ g_param_spec_boolean ("show-trash",
+ "Show “Trash”",
+ "Whether the sidebar includes a builtin shortcut to the Trash location",
+ TRUE,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+ properties[PROP_SHOW_OTHER_LOCATIONS] =
+ g_param_spec_boolean ("show-other-locations",
+ "Show “Other locations”",
+ "Whether the sidebar includes an item to show external locations",
+ TRUE,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+ properties[PROP_SHOW_STARRED_LOCATION] =
+ g_param_spec_boolean ("show-starred-location",
+ "Show “Starred Location”",
+ "Whether the sidebar includes an item to show starred files",
+ FALSE,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+
+ g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
+
+ gtk_widget_class_set_css_name (widget_class, "placessidebar");
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LIST);
+}
+
+/*
+ * nautilus_gtk_places_sidebar_new:
+ *
+ * Creates a new NautilusGtkPlacesSidebar widget.
+ *
+ * The application should connect to at least the
+ * NautilusGtkPlacesSidebar::open-location signal to be notified
+ * when the user makes a selection in the sidebar.
+ *
+ * Returns: a newly created NautilusGtkPlacesSidebar
+ */
+GtkWidget *
+nautilus_gtk_places_sidebar_new (void)
+{
+ return GTK_WIDGET (g_object_new (nautilus_gtk_places_sidebar_get_type (), NULL));
+}
+
+/*
+ * nautilus_gtk_places_sidebar_set_open_flags:
+ * @sidebar: a places sidebar
+ * @flags: Bitmask of modes in which the calling application can open locations
+ *
+ * Sets the way in which the calling application can open new locations from
+ * the places sidebar. For example, some applications only open locations
+ * “directly” into their main view, while others may support opening locations
+ * in a new notebook tab or a new window.
+ *
+ * This function is used to tell the places @sidebar about the ways in which the
+ * application can open new locations, so that the sidebar can display (or not)
+ * the “Open in new tab” and “Open in new window” menu items as appropriate.
+ *
+ * When the NautilusGtkPlacesSidebar::open-location signal is emitted, its flags
+ * argument will be set to one of the @flags that was passed in
+ * nautilus_gtk_places_sidebar_set_open_flags().
+ *
+ * Passing 0 for @flags will cause NAUTILUS_GTK_PLACES_OPEN_NORMAL to always be sent
+ * to callbacks for the “open-location” signal.
+ */
+void
+nautilus_gtk_places_sidebar_set_open_flags (NautilusGtkPlacesSidebar *sidebar,
+ NautilusGtkPlacesOpenFlags flags)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+
+ if (sidebar->open_flags != flags)
+ {
+ sidebar->open_flags = flags;
+ g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_OPEN_FLAGS]);
+ }
+}
+
+/*
+ * nautilus_gtk_places_sidebar_get_open_flags:
+ * @sidebar: a NautilusGtkPlacesSidebar
+ *
+ * Gets the open flags.
+ *
+ * Returns: the NautilusGtkPlacesOpenFlags of @sidebar
+ */
+NautilusGtkPlacesOpenFlags
+nautilus_gtk_places_sidebar_get_open_flags (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), 0);
+
+ return sidebar->open_flags;
+}
+
+/*
+ * nautilus_gtk_places_sidebar_set_location:
+ * @sidebar: a places sidebar
+ * @location: (nullable): location to select, or %NULL for no current path
+ *
+ * Sets the location that is being shown in the widgets surrounding the
+ * @sidebar, for example, in a folder view in a file manager. In turn, the
+ * @sidebar will highlight that location if it is being shown in the list of
+ * places, or it will unhighlight everything if the @location is not among the
+ * places in the list.
+ */
+void
+nautilus_gtk_places_sidebar_set_location (NautilusGtkPlacesSidebar *sidebar,
+ GFile *location)
+{
+ GtkWidget *row;
+ char *row_uri;
+ char *uri;
+ gboolean found = FALSE;
+
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+
+ gtk_list_box_unselect_all (GTK_LIST_BOX (sidebar->list_box));
+
+ if (sidebar->current_location != NULL)
+ g_object_unref (sidebar->current_location);
+ sidebar->current_location = location;
+ if (sidebar->current_location != NULL)
+ g_object_ref (sidebar->current_location);
+
+ if (location == NULL)
+ goto out;
+
+ uri = g_file_get_uri (location);
+
+ for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box));
+ row != NULL && !found;
+ row = gtk_widget_get_next_sibling (row))
+ {
+ if (!GTK_IS_LIST_BOX_ROW (row))
+ continue;
+
+ g_object_get (row, "uri", &row_uri, NULL);
+ if (row_uri != NULL && g_strcmp0 (row_uri, uri) == 0)
+ {
+ gtk_list_box_select_row (GTK_LIST_BOX (sidebar->list_box),
+ GTK_LIST_BOX_ROW (row));
+ found = TRUE;
+ }
+
+ g_free (row_uri);
+ }
+
+ g_free (uri);
+
+ out:
+ g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_LOCATION]);
+}
+
+/*
+ * nautilus_gtk_places_sidebar_get_location:
+ * @sidebar: a places sidebar
+ *
+ * Gets the currently selected location in the @sidebar. This can be %NULL when
+ * nothing is selected, for example, when nautilus_gtk_places_sidebar_set_location() has
+ * been called with a location that is not among the sidebar’s list of places to
+ * show.
+ *
+ * You can use this function to get the selection in the @sidebar.
+ *
+ * Returns: (nullable) (transfer full): a GFile with the selected location, or
+ * %NULL if nothing is visually selected.
+ */
+GFile *
+nautilus_gtk_places_sidebar_get_location (NautilusGtkPlacesSidebar *sidebar)
+{
+ GtkListBoxRow *selected;
+ GFile *file;
+
+ g_return_val_if_fail (sidebar != NULL, NULL);
+
+ file = NULL;
+ selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box));
+
+ if (selected)
+ {
+ char *uri;
+
+ g_object_get (selected, "uri", &uri, NULL);
+ file = g_file_new_for_uri (uri);
+ g_free (uri);
+ }
+
+ return file;
+}
+
+char *
+nautilus_gtk_places_sidebar_get_location_title (NautilusGtkPlacesSidebar *sidebar)
+{
+ GtkListBoxRow *selected;
+ char *title;
+
+ g_return_val_if_fail (sidebar != NULL, NULL);
+
+ title = NULL;
+ selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box));
+
+ if (selected)
+ g_object_get (selected, "label", &title, NULL);
+
+ return title;
+}
+
+/*
+ * nautilus_gtk_places_sidebar_set_show_recent:
+ * @sidebar: a places sidebar
+ * @show_recent: whether to show an item for recent files
+ *
+ * Sets whether the @sidebar should show an item for recent files.
+ * The default value for this option is determined by the desktop
+ * environment, but this function can be used to override it on a
+ * per-application basis.
+ */
+void
+nautilus_gtk_places_sidebar_set_show_recent (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_recent)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+
+ sidebar->show_recent_set = TRUE;
+
+ show_recent = !!show_recent;
+ if (sidebar->show_recent != show_recent)
+ {
+ sidebar->show_recent = show_recent;
+ update_places (sidebar);
+ g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_RECENT]);
+ }
+}
+
+/*
+ * nautilus_gtk_places_sidebar_get_show_recent:
+ * @sidebar: a places sidebar
+ *
+ * Returns the value previously set with nautilus_gtk_places_sidebar_set_show_recent()
+ *
+ * Returns: %TRUE if the sidebar will display a builtin shortcut for recent files
+ */
+gboolean
+nautilus_gtk_places_sidebar_get_show_recent (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), FALSE);
+
+ return sidebar->show_recent;
+}
+
+/*
+ * nautilus_gtk_places_sidebar_set_show_desktop:
+ * @sidebar: a places sidebar
+ * @show_desktop: whether to show an item for the Desktop folder
+ *
+ * Sets whether the @sidebar should show an item for the Desktop folder.
+ * The default value for this option is determined by the desktop
+ * environment and the user’s configuration, but this function can be
+ * used to override it on a per-application basis.
+ */
+void
+nautilus_gtk_places_sidebar_set_show_desktop (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_desktop)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+
+ /* Don't bother disconnecting from the GtkSettings -- it will just
+ * complicate things. Besides, it's highly unlikely that this will
+ * change while we're running, but we can ignore it if it does.
+ */
+ sidebar->show_desktop_set = TRUE;
+
+ show_desktop = !!show_desktop;
+ if (sidebar->show_desktop != show_desktop)
+ {
+ sidebar->show_desktop = show_desktop;
+ update_places (sidebar);
+ g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_DESKTOP]);
+ }
+}
+
+/*
+ * nautilus_gtk_places_sidebar_get_show_desktop:
+ * @sidebar: a places sidebar
+ *
+ * Returns the value previously set with nautilus_gtk_places_sidebar_set_show_desktop()
+ *
+ * Returns: %TRUE if the sidebar will display a builtin shortcut to the desktop folder.
+ */
+gboolean
+nautilus_gtk_places_sidebar_get_show_desktop (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), FALSE);
+
+ return sidebar->show_desktop;
+}
+
+/*
+ * nautilus_gtk_places_sidebar_set_show_enter_location:
+ * @sidebar: a places sidebar
+ * @show_enter_location: whether to show an item to enter a location
+ *
+ * Sets whether the @sidebar should show an item for entering a location;
+ * this is off by default. An application may want to turn this on if manually
+ * entering URLs is an expected user action.
+ *
+ * If you enable this, you should connect to the
+ * NautilusGtkPlacesSidebar::show-enter-location signal.
+ */
+void
+nautilus_gtk_places_sidebar_set_show_enter_location (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_enter_location)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+
+ show_enter_location = !!show_enter_location;
+ if (sidebar->show_enter_location != show_enter_location)
+ {
+ sidebar->show_enter_location = show_enter_location;
+ update_places (sidebar);
+ g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_ENTER_LOCATION]);
+ }
+}
+
+/*
+ * nautilus_gtk_places_sidebar_get_show_enter_location:
+ * @sidebar: a places sidebar
+ *
+ * Returns the value previously set with nautilus_gtk_places_sidebar_set_show_enter_location()
+ *
+ * Returns: %TRUE if the sidebar will display an “Enter Location” item.
+ */
+gboolean
+nautilus_gtk_places_sidebar_get_show_enter_location (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), FALSE);
+
+ return sidebar->show_enter_location;
+}
+
+/*
+ * nautilus_gtk_places_sidebar_set_show_other_locations:
+ * @sidebar: a places sidebar
+ * @show_other_locations: whether to show an item for the Other Locations view
+ *
+ * Sets whether the @sidebar should show an item for the application to show
+ * an Other Locations view; this is off by default. When set to %TRUE, persistent
+ * devices such as hard drives are hidden, otherwise they are shown in the sidebar.
+ * An application may want to turn this on if it implements a way for the user to
+ * see and interact with drives and network servers directly.
+ *
+ * If you enable this, you should connect to the
+ * NautilusGtkPlacesSidebar::show-other-locations-with-flags signal.
+ */
+void
+nautilus_gtk_places_sidebar_set_show_other_locations (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_other_locations)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+
+ show_other_locations = !!show_other_locations;
+ if (sidebar->show_other_locations != show_other_locations)
+ {
+ sidebar->show_other_locations = show_other_locations;
+ update_places (sidebar);
+ g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_OTHER_LOCATIONS]);
+ }
+ }
+
+/*
+ * nautilus_gtk_places_sidebar_get_show_other_locations:
+ * @sidebar: a places sidebar
+ *
+ * Returns the value previously set with nautilus_gtk_places_sidebar_set_show_other_locations()
+ *
+ * Returns: %TRUE if the sidebar will display an “Other Locations” item.
+ */
+gboolean
+nautilus_gtk_places_sidebar_get_show_other_locations (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), FALSE);
+
+ return sidebar->show_other_locations;
+}
+
+/*
+ * nautilus_gtk_places_sidebar_set_show_trash:
+ * @sidebar: a places sidebar
+ * @show_trash: whether to show an item for the Trash location
+ *
+ * Sets whether the @sidebar should show an item for the Trash location.
+ */
+void
+nautilus_gtk_places_sidebar_set_show_trash (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_trash)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+
+ show_trash = !!show_trash;
+ if (sidebar->show_trash != show_trash)
+ {
+ sidebar->show_trash = show_trash;
+ update_places (sidebar);
+ g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_TRASH]);
+ }
+}
+
+/*
+ * nautilus_gtk_places_sidebar_get_show_trash:
+ * @sidebar: a places sidebar
+ *
+ * Returns the value previously set with nautilus_gtk_places_sidebar_set_show_trash()
+ *
+ * Returns: %TRUE if the sidebar will display a “Trash” item.
+ */
+gboolean
+nautilus_gtk_places_sidebar_get_show_trash (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), TRUE);
+
+ return sidebar->show_trash;
+}
+
+/*
+ * nautilus_gtk_places_sidebar_add_shortcut:
+ * @sidebar: a places sidebar
+ * @location: location to add as an application-specific shortcut
+ *
+ * Applications may want to present some folders in the places sidebar if
+ * they could be immediately useful to users. For example, a drawing
+ * program could add a “/usr/share/clipart” location when the sidebar is
+ * being used in an “Insert Clipart” dialog box.
+ *
+ * This function adds the specified @location to a special place for immutable
+ * shortcuts. The shortcuts are application-specific; they are not shared
+ * across applications, and they are not persistent. If this function
+ * is called multiple times with different locations, then they are added
+ * to the sidebar’s list in the same order as the function is called.
+ */
+void
+nautilus_gtk_places_sidebar_add_shortcut (NautilusGtkPlacesSidebar *sidebar,
+ GFile *location)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+ g_return_if_fail (G_IS_FILE (location));
+
+ g_list_store_append (sidebar->shortcuts, location);
+
+ update_places (sidebar);
+}
+
+/*
+ * nautilus_gtk_places_sidebar_remove_shortcut:
+ * @sidebar: a places sidebar
+ * @location: location to remove
+ *
+ * Removes an application-specific shortcut that has been previously been
+ * inserted with nautilus_gtk_places_sidebar_add_shortcut(). If the @location is not a
+ * shortcut in the sidebar, then nothing is done.
+ */
+void
+nautilus_gtk_places_sidebar_remove_shortcut (NautilusGtkPlacesSidebar *sidebar,
+ GFile *location)
+{
+ guint i, n;
+
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+ g_return_if_fail (G_IS_FILE (location));
+
+ n = g_list_model_get_n_items (G_LIST_MODEL (sidebar->shortcuts));
+ for (i = 0; i < n; i++)
+ {
+ GFile *shortcut = g_list_model_get_item (G_LIST_MODEL (sidebar->shortcuts), i);
+
+ if (shortcut == location)
+ {
+ g_list_store_remove (sidebar->shortcuts, i);
+ g_object_unref (shortcut);
+ update_places (sidebar);
+ return;
+ }
+
+ g_object_unref (shortcut);
+ }
+}
+
+/*
+ * nautilus_gtk_places_sidebar_list_shortcuts:
+ * @sidebar: a places sidebar
+ *
+ * Gets the list of shortcuts, as a list model containing GFile objects.
+ *
+ * You should not modify the returned list model. Future changes to
+ * @sidebar may or may not affect the returned model.
+ *
+ * Returns: (transfer full): a list model of GFiles that have been added as
+ * application-specific shortcuts with nautilus_gtk_places_sidebar_add_shortcut()
+ */
+GListModel *
+nautilus_gtk_places_sidebar_get_shortcuts (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), NULL);
+
+ return G_LIST_MODEL (g_object_ref (sidebar->shortcuts));
+}
+
+/*
+ * nautilus_gtk_places_sidebar_get_nth_bookmark:
+ * @sidebar: a places sidebar
+ * @n: index of the bookmark to query
+ *
+ * This function queries the bookmarks added by the user to the places sidebar,
+ * and returns one of them. This function is used by GtkFileChooser to implement
+ * the “Alt-1”, “Alt-2”, etc. shortcuts, which activate the corresponding bookmark.
+ *
+ * Returns: (nullable) (transfer full): The bookmark specified by the index @n, or
+ * %NULL if no such index exist. Note that the indices start at 0, even though
+ * the file chooser starts them with the keyboard shortcut "Alt-1".
+ */
+GFile *
+nautilus_gtk_places_sidebar_get_nth_bookmark (NautilusGtkPlacesSidebar *sidebar,
+ int n)
+{
+ GtkWidget *row;
+ int k;
+ GFile *file;
+
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), NULL);
+
+ file = NULL;
+ k = 0;
+ for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box));
+ row != NULL;
+ row = gtk_widget_get_next_sibling (row))
+ {
+ NautilusGtkPlacesPlaceType place_type;
+ char *uri;
+
+ if (!GTK_IS_LIST_BOX_ROW (row))
+ continue;
+
+ g_object_get (row,
+ "place-type", &place_type,
+ "uri", &uri,
+ NULL);
+ if (place_type == NAUTILUS_GTK_PLACES_BOOKMARK)
+ {
+ if (k == n)
+ {
+ file = g_file_new_for_uri (uri);
+ g_free (uri);
+ break;
+ }
+ k++;
+ }
+ g_free (uri);
+ }
+
+ return file;
+}
+
+/*
+ * nautilus_gtk_places_sidebar_set_drop_targets_visible:
+ * @sidebar: a places sidebar.
+ * @visible: whether to show the valid targets or not.
+ *
+ * Make the NautilusGtkPlacesSidebar show drop targets, so it can show the available
+ * drop targets and a "new bookmark" row. This improves the Drag-and-Drop
+ * experience of the user and allows applications to show all available
+ * drop targets at once.
+ *
+ * This needs to be called when the application is aware of an ongoing drag
+ * that might target the sidebar. The drop-targets-visible state will be unset
+ * automatically if the drag finishes in the NautilusGtkPlacesSidebar. You only need
+ * to unset the state when the drag ends on some other widget on your application.
+ */
+void
+nautilus_gtk_places_sidebar_set_drop_targets_visible (NautilusGtkPlacesSidebar *sidebar,
+ gboolean visible)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+
+ if (visible)
+ {
+ sidebar->drop_state = DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT;
+ start_drop_feedback (sidebar, NULL);
+ }
+ else
+ {
+ if (sidebar->drop_state == DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT ||
+ sidebar->drop_state == DROP_STATE_NEW_BOOKMARK_ARMED)
+ {
+ if (!sidebar->dragging_over)
+ {
+ sidebar->drop_state = DROP_STATE_NORMAL;
+ stop_drop_feedback (sidebar);
+ }
+ else
+ {
+ /* In case this is called while we are dragging we need to mark the
+ * drop state as no permanent so the leave timeout can do its job.
+ * This will only happen in applications that call this in a wrong
+ * time */
+ sidebar->drop_state = DROP_STATE_NEW_BOOKMARK_ARMED;
+ }
+ }
+ }
+}
+
+/*
+ * nautilus_gtk_places_sidebar_set_show_starred_location:
+ * @sidebar: a places sidebar
+ * @show_starred_location: whether to show an item for Starred files
+ *
+ * If you enable this, you should connect to the
+ * NautilusGtkPlacesSidebar::show-starred-location signal.
+ */
+void
+nautilus_gtk_places_sidebar_set_show_starred_location (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_starred_location)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+
+ show_starred_location = !!show_starred_location;
+ if (sidebar->show_starred_location != show_starred_location)
+ {
+ sidebar->show_starred_location = show_starred_location;
+ update_places (sidebar);
+ g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_STARRED_LOCATION]);
+ }
+}
+
+/*
+ * nautilus_gtk_places_sidebar_get_show_starred_location:
+ * @sidebar: a places sidebar
+ *
+ * Returns the value previously set with nautilus_gtk_places_sidebar_set_show_starred_location()
+ *
+ * Returns: %TRUE if the sidebar will display a Starred item.
+ */
+gboolean
+nautilus_gtk_places_sidebar_get_show_starred_location (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), FALSE);
+
+ return sidebar->show_starred_location;
+}
diff --git a/src/gtk/nautilusgtkplacessidebarprivate.h b/src/gtk/nautilusgtkplacessidebarprivate.h
new file mode 100644
index 0000000..c1503ad
--- /dev/null
+++ b/src/gtk/nautilusgtkplacessidebarprivate.h
@@ -0,0 +1,148 @@
+/* nautilusgtkplacessidebarprivate.h
+ *
+ * Copyright (C) 2015 Red Hat
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Carlos Soriano <csoriano@gnome.org>
+ */
+
+#ifndef __NAUTILUS_GTK_PLACES_SIDEBAR_PRIVATE_H__
+#define __NAUTILUS_GTK_PLACES_SIDEBAR_PRIVATE_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_GTK_PLACES_SIDEBAR (nautilus_gtk_places_sidebar_get_type ())
+#define NAUTILUS_GTK_PLACES_SIDEBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_GTK_PLACES_SIDEBAR, NautilusGtkPlacesSidebar))
+#define NAUTILUS_GTK_PLACES_SIDEBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_GTK_PLACES_SIDEBAR, NautilusGtkPlacesSidebarClass))
+#define NAUTILUS_IS_GTK_PLACES_SIDEBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_GTK_PLACES_SIDEBAR))
+#define NAUTILUS_IS_GTK_PLACES_SIDEBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_GTK_PLACES_SIDEBAR))
+#define NAUTILUS_GTK_PLACES_SIDEBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_GTK_PLACES_SIDEBAR, NautilusGtkPlacesSidebarClass))
+
+typedef struct _NautilusGtkPlacesSidebar NautilusGtkPlacesSidebar;
+typedef struct _NautilusGtkPlacesSidebarClass NautilusGtkPlacesSidebarClass;
+
+/*
+ * NautilusGtkPlacesOpenFlags:
+ * @NAUTILUS_GTK_PLACES_OPEN_NORMAL: This is the default mode that NautilusGtkPlacesSidebar uses if no other flags
+ * are specified. It indicates that the calling application should open the selected location
+ * in the normal way, for example, in the folder view beside the sidebar.
+ * @NAUTILUS_GTK_PLACES_OPEN_NEW_TAB: When passed to nautilus_gtk_places_sidebar_set_open_flags(), this indicates
+ * that the application can open folders selected from the sidebar in new tabs. This value
+ * will be passed to the NautilusGtkPlacesSidebar::open-location signal when the user selects
+ * that a location be opened in a new tab instead of in the standard fashion.
+ * @NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW: Similar to @NAUTILUS_GTK_PLACES_OPEN_NEW_TAB, but indicates that the application
+ * can open folders in new windows.
+ *
+ * These flags serve two purposes. First, the application can call nautilus_gtk_places_sidebar_set_open_flags()
+ * using these flags as a bitmask. This tells the sidebar that the application is able to open
+ * folders selected from the sidebar in various ways, for example, in new tabs or in new windows in
+ * addition to the normal mode.
+ *
+ * Second, when one of these values gets passed back to the application in the
+ * NautilusGtkPlacesSidebar::open-location signal, it means that the application should
+ * open the selected location in the normal way, in a new tab, or in a new
+ * window. The sidebar takes care of determining the desired way to open the location,
+ * based on the modifier keys that the user is pressing at the time the selection is made.
+ *
+ * If the application never calls nautilus_gtk_places_sidebar_set_open_flags(), then the sidebar will only
+ * use NAUTILUS_GTK_PLACES_OPEN_NORMAL in the NautilusGtkPlacesSidebar::open-location signal. This is the
+ * default mode of operation.
+ */
+typedef enum {
+ NAUTILUS_GTK_PLACES_OPEN_NORMAL = 1 << 0,
+ NAUTILUS_GTK_PLACES_OPEN_NEW_TAB = 1 << 1,
+ NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW = 1 << 2
+} NautilusGtkPlacesOpenFlags;
+
+GType nautilus_gtk_places_sidebar_get_type (void) G_GNUC_CONST;
+GtkWidget * nautilus_gtk_places_sidebar_new (void);
+
+NautilusGtkPlacesOpenFlags nautilus_gtk_places_sidebar_get_open_flags (NautilusGtkPlacesSidebar *sidebar);
+void nautilus_gtk_places_sidebar_set_open_flags (NautilusGtkPlacesSidebar *sidebar,
+ NautilusGtkPlacesOpenFlags flags);
+
+GFile * nautilus_gtk_places_sidebar_get_location (NautilusGtkPlacesSidebar *sidebar);
+void nautilus_gtk_places_sidebar_set_location (NautilusGtkPlacesSidebar *sidebar,
+ GFile *location);
+
+gboolean nautilus_gtk_places_sidebar_get_show_recent (NautilusGtkPlacesSidebar *sidebar);
+void nautilus_gtk_places_sidebar_set_show_recent (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_recent);
+
+gboolean nautilus_gtk_places_sidebar_get_show_desktop (NautilusGtkPlacesSidebar *sidebar);
+void nautilus_gtk_places_sidebar_set_show_desktop (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_desktop);
+
+gboolean nautilus_gtk_places_sidebar_get_show_enter_location (NautilusGtkPlacesSidebar *sidebar);
+void nautilus_gtk_places_sidebar_set_show_enter_location (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_enter_location);
+
+void nautilus_gtk_places_sidebar_add_shortcut (NautilusGtkPlacesSidebar *sidebar,
+ GFile *location);
+void nautilus_gtk_places_sidebar_remove_shortcut (NautilusGtkPlacesSidebar *sidebar,
+ GFile *location);
+GListModel * nautilus_gtk_places_sidebar_get_shortcuts (NautilusGtkPlacesSidebar *sidebar);
+
+GFile * nautilus_gtk_places_sidebar_get_nth_bookmark (NautilusGtkPlacesSidebar *sidebar,
+ int n);
+void nautilus_gtk_places_sidebar_set_drop_targets_visible (NautilusGtkPlacesSidebar *sidebar,
+ gboolean visible);
+gboolean nautilus_gtk_places_sidebar_get_show_trash (NautilusGtkPlacesSidebar *sidebar);
+void nautilus_gtk_places_sidebar_set_show_trash (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_trash);
+
+void nautilus_gtk_places_sidebar_set_show_other_locations (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_other_locations);
+gboolean nautilus_gtk_places_sidebar_get_show_other_locations (NautilusGtkPlacesSidebar *sidebar);
+
+void nautilus_gtk_places_sidebar_set_show_starred_location (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_starred_location);
+gboolean nautilus_gtk_places_sidebar_get_show_starred_location (NautilusGtkPlacesSidebar *sidebar);
+
+/* Keep order, since it's used for the sort functions */
+typedef enum {
+ NAUTILUS_GTK_PLACES_SECTION_INVALID,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ NAUTILUS_GTK_PLACES_SECTION_MOUNTS,
+ NAUTILUS_GTK_PLACES_SECTION_CLOUD,
+ NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS,
+ NAUTILUS_GTK_PLACES_SECTION_OTHER_LOCATIONS,
+ NAUTILUS_GTK_PLACES_N_SECTIONS
+} NautilusGtkPlacesSectionType;
+
+typedef enum {
+ NAUTILUS_GTK_PLACES_INVALID,
+ NAUTILUS_GTK_PLACES_BUILT_IN,
+ NAUTILUS_GTK_PLACES_XDG_DIR,
+ NAUTILUS_GTK_PLACES_MOUNTED_VOLUME,
+ NAUTILUS_GTK_PLACES_BOOKMARK,
+ NAUTILUS_GTK_PLACES_HEADING,
+ NAUTILUS_GTK_PLACES_CONNECT_TO_SERVER,
+ NAUTILUS_GTK_PLACES_ENTER_LOCATION,
+ NAUTILUS_GTK_PLACES_DROP_FEEDBACK,
+ NAUTILUS_GTK_PLACES_BOOKMARK_PLACEHOLDER,
+ NAUTILUS_GTK_PLACES_OTHER_LOCATIONS,
+ NAUTILUS_GTK_PLACES_STARRED_LOCATION,
+ NAUTILUS_GTK_PLACES_N_PLACES
+} NautilusGtkPlacesPlaceType;
+
+char *nautilus_gtk_places_sidebar_get_location_title (NautilusGtkPlacesSidebar *sidebar);
+
+G_END_DECLS
+
+#endif /* __NAUTILUS_GTK_PLACES_SIDEBAR_PRIVATE_H__ */
diff --git a/src/gtk/nautilusgtkplacesview.c b/src/gtk/nautilusgtkplacesview.c
new file mode 100644
index 0000000..0d062c9
--- /dev/null
+++ b/src/gtk/nautilusgtkplacesview.c
@@ -0,0 +1,2635 @@
+/* nautilusgtkplacesview.c
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "nautilus-enum-types.h"
+
+#include <gio/gio.h>
+#include <gio/gvfs.h>
+#include <gtk/gtk.h>
+
+#include "nautilusgtkplacesviewprivate.h"
+#include "nautilusgtkplacesviewrowprivate.h"
+#include "nautilus-file.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-properties-window.h"
+
+/*< private >
+ * NautilusGtkPlacesView:
+ *
+ * NautilusGtkPlacesView is a widget that displays a list of persistent drives
+ * such as harddisk partitions and networks. NautilusGtkPlacesView does not monitor
+ * removable devices.
+ *
+ * The places view displays drives and networks, and will automatically mount
+ * them when the user activates. Network addresses are stored even if they fail
+ * to connect. When the connection is successful, the connected network is
+ * shown at the network list.
+ *
+ * To make use of the places view, an application at least needs to connect
+ * to the NautilusGtkPlacesView::open-location signal. This is emitted when the user
+ * selects a location to open in the view.
+ */
+
+struct _NautilusGtkPlacesViewClass
+{
+ GtkBoxClass parent_class;
+
+ void (* open_location) (NautilusGtkPlacesView *view,
+ GFile *location,
+ NautilusGtkPlacesOpenFlags open_flags);
+
+ void (* show_error_message) (NautilusGtkPlacesSidebar *sidebar,
+ const char *primary,
+ const char *secondary);
+};
+
+struct _NautilusGtkPlacesView
+{
+ GtkBox parent_instance;
+
+ GVolumeMonitor *volume_monitor;
+ NautilusGtkPlacesOpenFlags open_flags;
+ NautilusGtkPlacesOpenFlags current_open_flags;
+
+ GFile *server_list_file;
+ GFileMonitor *server_list_monitor;
+ GFileMonitor *network_monitor;
+
+ GCancellable *cancellable;
+
+ char *search_query;
+
+ GtkWidget *actionbar;
+ GtkWidget *address_entry;
+ GtkWidget *connect_button;
+ GtkWidget *listbox;
+ GtkWidget *popup_menu;
+ GtkWidget *recent_servers_listbox;
+ GtkWidget *recent_servers_popover;
+ GtkWidget *recent_servers_stack;
+ GtkWidget *stack;
+ GtkWidget *server_adresses_popover;
+ GtkWidget *available_protocols_grid;
+ GtkWidget *network_placeholder;
+ GtkWidget *network_placeholder_label;
+
+ GtkSizeGroup *path_size_group;
+ GtkSizeGroup *space_size_group;
+
+ GtkEntryCompletion *address_entry_completion;
+ GtkListStore *completion_store;
+
+ GCancellable *networks_fetching_cancellable;
+
+ NautilusGtkPlacesViewRow *row_for_action;
+
+ guint should_open_location : 1;
+ guint should_pulse_entry : 1;
+ guint entry_pulse_timeout_id;
+ guint connecting_to_server : 1;
+ guint mounting_volume : 1;
+ guint unmounting_mount : 1;
+ guint fetching_networks : 1;
+ guint loading : 1;
+ guint destroyed : 1;
+};
+
+static void mount_volume (NautilusGtkPlacesView *view,
+ GVolume *volume);
+
+static void on_eject_button_clicked (GtkWidget *widget,
+ NautilusGtkPlacesViewRow *row);
+
+static gboolean on_row_popup_menu (GtkWidget *widget,
+ GVariant *args,
+ gpointer user_data);
+
+static void click_cb (GtkGesture *gesture,
+ int n_press,
+ double x,
+ double y,
+ gpointer user_data);
+
+static void populate_servers (NautilusGtkPlacesView *view);
+
+static gboolean nautilus_gtk_places_view_get_fetching_networks (NautilusGtkPlacesView *view);
+
+static void nautilus_gtk_places_view_set_fetching_networks (NautilusGtkPlacesView *view,
+ gboolean fetching_networks);
+
+static void nautilus_gtk_places_view_set_loading (NautilusGtkPlacesView *view,
+ gboolean loading);
+
+static void update_loading (NautilusGtkPlacesView *view);
+
+G_DEFINE_TYPE (NautilusGtkPlacesView, nautilus_gtk_places_view, GTK_TYPE_BOX)
+
+/* NautilusGtkPlacesView properties & signals */
+enum {
+ PROP_0,
+ PROP_OPEN_FLAGS,
+ PROP_FETCHING_NETWORKS,
+ PROP_LOADING,
+ LAST_PROP
+};
+
+enum {
+ OPEN_LOCATION,
+ SHOW_ERROR_MESSAGE,
+ LAST_SIGNAL
+};
+
+const char *unsupported_protocols [] =
+{
+ "file", "afc", "obex", "http",
+ "trash", "burn", "computer",
+ "archive", "recent", "localtest",
+ NULL
+};
+
+static guint places_view_signals [LAST_SIGNAL] = { 0 };
+static GParamSpec *properties [LAST_PROP];
+
+static void
+emit_open_location (NautilusGtkPlacesView *view,
+ GFile *location,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ if ((open_flags & view->open_flags) == 0)
+ open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL;
+
+ g_signal_emit (view, places_view_signals[OPEN_LOCATION], 0, location, open_flags);
+}
+
+static void
+emit_show_error_message (NautilusGtkPlacesView *view,
+ char *primary_message,
+ char *secondary_message)
+{
+ g_signal_emit (view, places_view_signals[SHOW_ERROR_MESSAGE],
+ 0, primary_message, secondary_message);
+}
+
+static void
+server_file_changed_cb (NautilusGtkPlacesView *view)
+{
+ populate_servers (view);
+}
+
+static GBookmarkFile *
+server_list_load (NautilusGtkPlacesView *view)
+{
+ GBookmarkFile *bookmarks;
+ GError *error = NULL;
+ char *datadir;
+ char *filename;
+
+ bookmarks = g_bookmark_file_new ();
+ datadir = g_build_filename (g_get_user_config_dir (), "gtk-4.0", NULL);
+ filename = g_build_filename (datadir, "servers", NULL);
+
+ g_mkdir_with_parents (datadir, 0700);
+ g_bookmark_file_load_from_file (bookmarks, filename, &error);
+
+ if (error)
+ {
+ if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+ {
+ /* only warn if the file exists */
+ g_warning ("Unable to open server bookmarks: %s", error->message);
+ g_clear_pointer (&bookmarks, g_bookmark_file_free);
+ }
+
+ g_clear_error (&error);
+ }
+
+ /* Monitor the file in case it's modified outside this code */
+ if (!view->server_list_monitor)
+ {
+ view->server_list_file = g_file_new_for_path (filename);
+
+ if (view->server_list_file)
+ {
+ view->server_list_monitor = g_file_monitor_file (view->server_list_file,
+ G_FILE_MONITOR_NONE,
+ NULL,
+ &error);
+
+ if (error)
+ {
+ g_warning ("Cannot monitor server file: %s", error->message);
+ g_clear_error (&error);
+ }
+ else
+ {
+ g_signal_connect_swapped (view->server_list_monitor,
+ "changed",
+ G_CALLBACK (server_file_changed_cb),
+ view);
+ }
+ }
+
+ g_clear_object (&view->server_list_file);
+ }
+
+ g_free (datadir);
+ g_free (filename);
+
+ return bookmarks;
+}
+
+static void
+server_list_save (GBookmarkFile *bookmarks)
+{
+ char *filename;
+
+ filename = g_build_filename (g_get_user_config_dir (), "gtk-4.0", "servers", NULL);
+ g_bookmark_file_to_file (bookmarks, filename, NULL);
+ g_free (filename);
+}
+
+static void
+server_list_add_server (NautilusGtkPlacesView *view,
+ GFile *file)
+{
+ GBookmarkFile *bookmarks;
+ GFileInfo *info;
+ GError *error;
+ char *title;
+ char *uri;
+ GDateTime *now;
+
+ error = NULL;
+ bookmarks = server_list_load (view);
+
+ if (!bookmarks)
+ return;
+
+ uri = g_file_get_uri (file);
+
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+ title = g_file_info_get_attribute_as_string (info, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
+
+ g_bookmark_file_set_title (bookmarks, uri, title);
+ now = g_date_time_new_now_utc ();
+ g_bookmark_file_set_visited_date_time (bookmarks, uri, now);
+ g_date_time_unref (now);
+ g_bookmark_file_add_application (bookmarks, uri, NULL, NULL);
+
+ server_list_save (bookmarks);
+
+ g_bookmark_file_free (bookmarks);
+ g_clear_object (&info);
+ g_free (title);
+ g_free (uri);
+}
+
+static void
+server_list_remove_server (NautilusGtkPlacesView *view,
+ const char *uri)
+{
+ GBookmarkFile *bookmarks;
+
+ bookmarks = server_list_load (view);
+
+ if (!bookmarks)
+ return;
+
+ g_bookmark_file_remove_item (bookmarks, uri, NULL);
+ server_list_save (bookmarks);
+
+ g_bookmark_file_free (bookmarks);
+}
+
+/* Returns a toplevel GtkWindow, or NULL if none */
+static GtkWindow *
+get_toplevel (GtkWidget *widget)
+{
+ GtkWidget *toplevel;
+
+ toplevel = GTK_WIDGET (gtk_widget_get_root (widget));
+ if (GTK_IS_WINDOW (toplevel))
+ return GTK_WINDOW (toplevel);
+ else
+ return NULL;
+}
+
+static void
+set_busy_cursor (NautilusGtkPlacesView *view,
+ gboolean busy)
+{
+ GtkWidget *widget;
+ GtkWindow *toplevel;
+
+ toplevel = get_toplevel (GTK_WIDGET (view));
+ widget = GTK_WIDGET (toplevel);
+ if (!toplevel || !gtk_widget_get_realized (widget))
+ return;
+
+ if (busy)
+ gtk_widget_set_cursor_from_name (widget, "progress");
+ else
+ gtk_widget_set_cursor (widget, NULL);
+}
+
+/* Activates the given row, with the given flags as parameter */
+static void
+activate_row (NautilusGtkPlacesView *view,
+ NautilusGtkPlacesViewRow *row,
+ NautilusGtkPlacesOpenFlags flags)
+{
+ GVolume *volume;
+ GMount *mount;
+ GFile *file;
+
+ mount = nautilus_gtk_places_view_row_get_mount (row);
+ volume = nautilus_gtk_places_view_row_get_volume (row);
+ file = nautilus_gtk_places_view_row_get_file (row);
+
+ if (file)
+ {
+ emit_open_location (view, file, flags);
+ }
+ else if (mount)
+ {
+ GFile *location = g_mount_get_default_location (mount);
+
+ emit_open_location (view, location, flags);
+
+ g_object_unref (location);
+ }
+ else if (volume && g_volume_can_mount (volume))
+ {
+ /*
+ * When the row is activated, the unmounted volume shall
+ * be mounted and opened right after.
+ */
+ view->should_open_location = TRUE;
+
+ nautilus_gtk_places_view_row_set_busy (row, TRUE);
+ mount_volume (view, volume);
+ }
+}
+
+static void update_places (NautilusGtkPlacesView *view);
+
+static void
+nautilus_gtk_places_view_finalize (GObject *object)
+{
+ NautilusGtkPlacesView *view = (NautilusGtkPlacesView *)object;
+
+ if (view->entry_pulse_timeout_id > 0)
+ g_source_remove (view->entry_pulse_timeout_id);
+
+ g_clear_pointer (&view->search_query, g_free);
+ g_clear_object (&view->server_list_file);
+ g_clear_object (&view->server_list_monitor);
+ g_clear_object (&view->volume_monitor);
+ g_clear_object (&view->network_monitor);
+ g_clear_object (&view->cancellable);
+ g_clear_object (&view->networks_fetching_cancellable);
+ g_clear_object (&view->path_size_group);
+ g_clear_object (&view->space_size_group);
+
+ G_OBJECT_CLASS (nautilus_gtk_places_view_parent_class)->finalize (object);
+}
+
+static void
+nautilus_gtk_places_view_dispose (GObject *object)
+{
+ NautilusGtkPlacesView *view = (NautilusGtkPlacesView *)object;
+
+ view->destroyed = 1;
+
+ g_signal_handlers_disconnect_by_func (view->volume_monitor, update_places, object);
+
+ if (view->network_monitor)
+ g_signal_handlers_disconnect_by_func (view->network_monitor, update_places, object);
+
+ if (view->server_list_monitor)
+ g_signal_handlers_disconnect_by_func (view->server_list_monitor, server_file_changed_cb, object);
+
+ g_cancellable_cancel (view->cancellable);
+ g_cancellable_cancel (view->networks_fetching_cancellable);
+ g_clear_pointer (&view->popup_menu, gtk_widget_unparent);
+
+ G_OBJECT_CLASS (nautilus_gtk_places_view_parent_class)->dispose (object);
+}
+
+static void
+nautilus_gtk_places_view_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusGtkPlacesView *self = NAUTILUS_GTK_PLACES_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_LOADING:
+ g_value_set_boolean (value, nautilus_gtk_places_view_get_loading (self));
+ break;
+
+ case PROP_OPEN_FLAGS:
+ g_value_set_flags (value, nautilus_gtk_places_view_get_open_flags (self));
+ break;
+
+ case PROP_FETCHING_NETWORKS:
+ g_value_set_boolean (value, nautilus_gtk_places_view_get_fetching_networks (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+nautilus_gtk_places_view_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusGtkPlacesView *self = NAUTILUS_GTK_PLACES_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_OPEN_FLAGS:
+ nautilus_gtk_places_view_set_open_flags (self, g_value_get_flags (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static gboolean
+is_external_volume (GVolume *volume)
+{
+ gboolean is_external;
+ GDrive *drive;
+ char *id;
+
+ drive = g_volume_get_drive (volume);
+ id = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);
+
+ is_external = g_volume_can_eject (volume);
+
+ /* NULL volume identifier only happens on removable devices */
+ is_external |= !id;
+
+ if (drive)
+ is_external |= g_drive_is_removable (drive);
+
+ g_clear_object (&drive);
+ g_free (id);
+
+ return is_external;
+}
+
+typedef struct
+{
+ char *uri;
+ NautilusGtkPlacesView *view;
+} RemoveServerData;
+
+static void
+on_remove_server_button_clicked (RemoveServerData *data)
+{
+ server_list_remove_server (data->view, data->uri);
+
+ populate_servers (data->view);
+}
+
+static void
+populate_servers (NautilusGtkPlacesView *view)
+{
+ GBookmarkFile *server_list;
+ GtkWidget *child;
+ char **uris;
+ gsize num_uris;
+ int i;
+
+ server_list = server_list_load (view);
+
+ if (!server_list)
+ return;
+
+ uris = g_bookmark_file_get_uris (server_list, &num_uris);
+
+ gtk_stack_set_visible_child_name (GTK_STACK (view->recent_servers_stack),
+ num_uris > 0 ? "list" : "empty");
+
+ if (!uris)
+ {
+ g_bookmark_file_free (server_list);
+ return;
+ }
+
+ /* clear previous items */
+ while ((child = gtk_widget_get_first_child (GTK_WIDGET (view->recent_servers_listbox))))
+ gtk_list_box_remove (GTK_LIST_BOX (view->recent_servers_listbox), child);
+
+ gtk_list_store_clear (view->completion_store);
+
+ for (i = 0; i < num_uris; i++)
+ {
+ RemoveServerData *data;
+ GtkTreeIter iter;
+ GtkWidget *row;
+ GtkWidget *grid;
+ GtkWidget *button;
+ GtkWidget *label;
+ char *name;
+ char *dup_uri;
+
+ name = g_bookmark_file_get_title (server_list, uris[i], NULL);
+ dup_uri = g_strdup (uris[i]);
+
+ /* add to the completion list */
+ gtk_list_store_append (view->completion_store, &iter);
+ gtk_list_store_set (view->completion_store,
+ &iter,
+ 0, name,
+ 1, uris[i],
+ -1);
+
+ /* add to the recent servers listbox */
+ row = gtk_list_box_row_new ();
+
+ grid = g_object_new (GTK_TYPE_GRID,
+ "orientation", GTK_ORIENTATION_VERTICAL,
+ NULL);
+
+ /* name of the connected uri, if any */
+ label = gtk_label_new (name);
+ gtk_widget_set_hexpand (label, TRUE);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
+ gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1);
+
+ /* the uri itself */
+ label = gtk_label_new (uris[i]);
+ gtk_widget_set_hexpand (label, TRUE);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
+ gtk_widget_add_css_class (label, "dim-label");
+ gtk_grid_attach (GTK_GRID (grid), label, 0, 1, 1, 1);
+
+ /* remove button */
+ button = gtk_button_new_from_icon_name ("window-close-symbolic");
+ gtk_widget_set_halign (button, GTK_ALIGN_END);
+ gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
+ gtk_button_set_has_frame (GTK_BUTTON (button), FALSE);
+ gtk_widget_add_css_class (button, "sidebar-button");
+ gtk_grid_attach (GTK_GRID (grid), button, 1, 0, 1, 2);
+
+ gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), grid);
+ gtk_list_box_insert (GTK_LIST_BOX (view->recent_servers_listbox), row, -1);
+
+ /* custom data */
+ data = g_new0 (RemoveServerData, 1);
+ data->view = view;
+ data->uri = dup_uri;
+
+ g_object_set_data_full (G_OBJECT (row), "uri", dup_uri, g_free);
+ g_object_set_data_full (G_OBJECT (row), "remove-server-data", data, g_free);
+
+ g_signal_connect_swapped (button,
+ "clicked",
+ G_CALLBACK (on_remove_server_button_clicked),
+ data);
+
+ g_free (name);
+ }
+
+ g_strfreev (uris);
+ g_bookmark_file_free (server_list);
+}
+
+static void
+update_view_mode (NautilusGtkPlacesView *view)
+{
+ GtkWidget *child;
+ gboolean show_listbox;
+
+ show_listbox = FALSE;
+
+ /* drives */
+ for (child = gtk_widget_get_first_child (GTK_WIDGET (view->listbox));
+ child != NULL;
+ child = gtk_widget_get_next_sibling (child))
+ {
+ /* GtkListBox filter rows by changing their GtkWidget::child-visible property */
+ if (gtk_widget_get_child_visible (child))
+ {
+ show_listbox = TRUE;
+ break;
+ }
+ }
+
+ if (!show_listbox &&
+ view->search_query &&
+ view->search_query[0] != '\0')
+ {
+ gtk_stack_set_visible_child_name (GTK_STACK (view->stack), "empty-search");
+ }
+ else
+ {
+ gtk_stack_set_visible_child_name (GTK_STACK (view->stack), "browse");
+ }
+}
+
+static void
+insert_row (NautilusGtkPlacesView *view,
+ GtkWidget *row,
+ gboolean is_network)
+{
+ GtkEventController *controller;
+ GtkShortcutTrigger *trigger;
+ GtkShortcutAction *action;
+ GtkShortcut *shortcut;
+ GtkGesture *gesture;
+
+ g_object_set_data (G_OBJECT (row), "is-network", GINT_TO_POINTER (is_network));
+
+ controller = gtk_shortcut_controller_new ();
+ trigger = gtk_alternative_trigger_new (gtk_keyval_trigger_new (GDK_KEY_F10, GDK_SHIFT_MASK),
+ gtk_keyval_trigger_new (GDK_KEY_Menu, 0));
+ action = gtk_callback_action_new (on_row_popup_menu, row, NULL);
+ shortcut = gtk_shortcut_new (trigger, action);
+ gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller), shortcut);
+ gtk_widget_add_controller (GTK_WIDGET (row), controller);
+
+ gesture = gtk_gesture_click_new ();
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), GDK_BUTTON_SECONDARY);
+ g_signal_connect (gesture, "pressed", G_CALLBACK (click_cb), row);
+ gtk_widget_add_controller (row, GTK_EVENT_CONTROLLER (gesture));
+
+ g_signal_connect (nautilus_gtk_places_view_row_get_eject_button (NAUTILUS_GTK_PLACES_VIEW_ROW (row)),
+ "clicked",
+ G_CALLBACK (on_eject_button_clicked),
+ row);
+
+ nautilus_gtk_places_view_row_set_path_size_group (NAUTILUS_GTK_PLACES_VIEW_ROW (row), view->path_size_group);
+ nautilus_gtk_places_view_row_set_space_size_group (NAUTILUS_GTK_PLACES_VIEW_ROW (row), view->space_size_group);
+
+ gtk_list_box_insert (GTK_LIST_BOX (view->listbox), row, -1);
+}
+
+static void
+add_volume (NautilusGtkPlacesView *view,
+ GVolume *volume)
+{
+ gboolean is_network;
+ GMount *mount;
+ GFile *root;
+ GIcon *icon;
+ char *identifier;
+ char *name;
+ char *path;
+
+ if (is_external_volume (volume))
+ return;
+
+ identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);
+ is_network = g_strcmp0 (identifier, "network") == 0;
+
+ mount = g_volume_get_mount (volume);
+ root = mount ? g_mount_get_default_location (mount) : NULL;
+ icon = g_volume_get_icon (volume);
+ name = g_volume_get_name (volume);
+ path = !is_network ? g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE) : NULL;
+
+ if (!mount || !g_mount_is_shadowed (mount))
+ {
+ GtkWidget *row;
+
+ row = g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW,
+ "icon", icon,
+ "name", name,
+ "path", path ? path : "",
+ "volume", volume,
+ "mount", mount,
+ "file", NULL,
+ "is-network", is_network,
+ NULL);
+
+ insert_row (view, row, is_network);
+ }
+
+ g_clear_object (&root);
+ g_clear_object (&icon);
+ g_clear_object (&mount);
+ g_free (identifier);
+ g_free (name);
+ g_free (path);
+}
+
+static void
+add_mount (NautilusGtkPlacesView *view,
+ GMount *mount)
+{
+ gboolean is_network;
+ GFile *root;
+ GIcon *icon;
+ char *name;
+ char *path;
+ char *uri;
+ char *schema;
+
+ icon = g_mount_get_icon (mount);
+ name = g_mount_get_name (mount);
+ root = g_mount_get_default_location (mount);
+ path = root ? g_file_get_parse_name (root) : NULL;
+ uri = g_file_get_uri (root);
+ schema = g_uri_parse_scheme (uri);
+ is_network = g_strcmp0 (schema, "file") != 0;
+
+ if (is_network)
+ g_clear_pointer (&path, g_free);
+
+ if (!g_mount_is_shadowed (mount))
+ {
+ GtkWidget *row;
+
+ row = g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW,
+ "icon", icon,
+ "name", name,
+ "path", path ? path : "",
+ "volume", NULL,
+ "mount", mount,
+ "file", NULL,
+ "is-network", is_network,
+ NULL);
+
+ insert_row (view, row, is_network);
+ }
+
+ g_clear_object (&root);
+ g_clear_object (&icon);
+ g_free (name);
+ g_free (path);
+ g_free (uri);
+ g_free (schema);
+}
+
+static void
+add_drive (NautilusGtkPlacesView *view,
+ GDrive *drive)
+{
+ GList *volumes;
+ GList *l;
+
+ volumes = g_drive_get_volumes (drive);
+
+ for (l = volumes; l != NULL; l = l->next)
+ add_volume (view, l->data);
+
+ g_list_free_full (volumes, g_object_unref);
+}
+
+static void
+add_file (NautilusGtkPlacesView *view,
+ GFile *file,
+ GIcon *icon,
+ const char *display_name,
+ const char *path,
+ gboolean is_network)
+{
+ GtkWidget *row;
+ row = g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW,
+ "icon", icon,
+ "name", display_name,
+ "path", path,
+ "volume", NULL,
+ "mount", NULL,
+ "file", file,
+ "is_network", is_network,
+ NULL);
+
+ insert_row (view, row, is_network);
+}
+
+static gboolean
+has_networks (NautilusGtkPlacesView *view)
+{
+ GtkWidget *child;
+ gboolean has_network = FALSE;
+
+ for (child = gtk_widget_get_first_child (GTK_WIDGET (view->listbox));
+ child != NULL;
+ child = gtk_widget_get_next_sibling (child))
+ {
+ if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (child), "is-network")) &&
+ g_object_get_data (G_OBJECT (child), "is-placeholder") == NULL)
+ {
+ has_network = TRUE;
+ break;
+ }
+ }
+
+ return has_network;
+}
+
+static void
+update_network_state (NautilusGtkPlacesView *view)
+{
+ if (view->network_placeholder == NULL)
+ {
+ view->network_placeholder = gtk_list_box_row_new ();
+ view->network_placeholder_label = gtk_label_new ("");
+ gtk_label_set_xalign (GTK_LABEL (view->network_placeholder_label), 0.0);
+ gtk_widget_set_margin_start (view->network_placeholder_label, 12);
+ gtk_widget_set_margin_end (view->network_placeholder_label, 12);
+ gtk_widget_set_margin_top (view->network_placeholder_label, 6);
+ gtk_widget_set_margin_bottom (view->network_placeholder_label, 6);
+ gtk_widget_set_hexpand (view->network_placeholder_label, TRUE);
+ gtk_widget_set_sensitive (view->network_placeholder, FALSE);
+ gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (view->network_placeholder),
+ view->network_placeholder_label);
+ g_object_set_data (G_OBJECT (view->network_placeholder),
+ "is-network", GINT_TO_POINTER (TRUE));
+ /* mark the row as placeholder, so it always goes first */
+ g_object_set_data (G_OBJECT (view->network_placeholder),
+ "is-placeholder", GINT_TO_POINTER (TRUE));
+ gtk_list_box_insert (GTK_LIST_BOX (view->listbox), view->network_placeholder, -1);
+ }
+
+ if (nautilus_gtk_places_view_get_fetching_networks (view))
+ {
+ /* only show a placeholder with a message if the list is empty.
+ * otherwise just show the spinner in the header */
+ if (!has_networks (view))
+ {
+ gtk_widget_show (view->network_placeholder);
+ gtk_label_set_text (GTK_LABEL (view->network_placeholder_label),
+ _("Searching for network locations"));
+ }
+ }
+ else if (!has_networks (view))
+ {
+ gtk_widget_show (view->network_placeholder);
+ gtk_label_set_text (GTK_LABEL (view->network_placeholder_label),
+ _("No network locations found"));
+ }
+ else
+ {
+ gtk_widget_hide (view->network_placeholder);
+ }
+}
+
+static void
+monitor_network (NautilusGtkPlacesView *view)
+{
+ GFile *network_file;
+ GError *error;
+
+ if (view->network_monitor)
+ return;
+
+ error = NULL;
+ network_file = g_file_new_for_uri ("network:///");
+ view->network_monitor = g_file_monitor (network_file,
+ G_FILE_MONITOR_NONE,
+ NULL,
+ &error);
+
+ g_clear_object (&network_file);
+
+ if (error)
+ {
+ g_warning ("Error monitoring network: %s", error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ g_signal_connect_swapped (view->network_monitor,
+ "changed",
+ G_CALLBACK (update_places),
+ view);
+}
+
+static void
+populate_networks (NautilusGtkPlacesView *view,
+ GFileEnumerator *enumerator,
+ GList *detected_networks)
+{
+ GList *l;
+ GFile *file;
+ GFile *activatable_file;
+ char *uri;
+ GFileType type;
+ GIcon *icon;
+ char *display_name;
+
+ for (l = detected_networks; l != NULL; l = l->next)
+ {
+ file = g_file_enumerator_get_child (enumerator, l->data);
+ type = g_file_info_get_file_type (l->data);
+ if (type == G_FILE_TYPE_SHORTCUT || type == G_FILE_TYPE_MOUNTABLE)
+ uri = g_file_info_get_attribute_as_string (l->data, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
+ else
+ uri = g_file_get_uri (file);
+ activatable_file = g_file_new_for_uri (uri);
+ display_name = g_file_info_get_attribute_as_string (l->data, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
+ icon = g_file_info_get_icon (l->data);
+
+ add_file (view, activatable_file, icon, display_name, NULL, TRUE);
+
+ g_free (uri);
+ g_free (display_name);
+ g_clear_object (&file);
+ g_clear_object (&activatable_file);
+ }
+}
+
+static void
+network_enumeration_next_files_finished (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesView *view;
+ GList *detected_networks;
+ GError *error;
+
+ view = NAUTILUS_GTK_PLACES_VIEW (user_data);
+ error = NULL;
+
+ detected_networks = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (source_object),
+ res, &error);
+
+ if (error)
+ {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_clear_error (&error);
+ g_object_unref (view);
+ return;
+ }
+
+ g_warning ("Failed to fetch network locations: %s", error->message);
+ g_clear_error (&error);
+ }
+ else
+ {
+ nautilus_gtk_places_view_set_fetching_networks (view, FALSE);
+ populate_networks (view, G_FILE_ENUMERATOR (source_object), detected_networks);
+
+ g_list_free_full (detected_networks, g_object_unref);
+ }
+
+ update_network_state (view);
+ monitor_network (view);
+ update_loading (view);
+
+ g_object_unref (view);
+}
+
+static void
+network_enumeration_finished (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data);
+ GFileEnumerator *enumerator;
+ GError *error;
+
+ error = NULL;
+ enumerator = g_file_enumerate_children_finish (G_FILE (source_object), res, &error);
+
+ if (error)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
+ !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
+ g_warning ("Failed to fetch network locations: %s", error->message);
+
+ g_clear_error (&error);
+ g_object_unref (view);
+ }
+ else
+ {
+ g_file_enumerator_next_files_async (enumerator,
+ G_MAXINT32,
+ G_PRIORITY_DEFAULT,
+ view->networks_fetching_cancellable,
+ network_enumeration_next_files_finished,
+ user_data);
+ g_object_unref (enumerator);
+ }
+}
+
+static void
+fetch_networks (NautilusGtkPlacesView *view)
+{
+ GFile *network_file;
+ const char * const *supported_uris;
+ gboolean found;
+
+ supported_uris = g_vfs_get_supported_uri_schemes (g_vfs_get_default ());
+
+ for (found = FALSE; !found && supported_uris && supported_uris[0]; supported_uris++)
+ if (g_strcmp0 (supported_uris[0], "network") == 0)
+ found = TRUE;
+
+ if (!found)
+ return;
+
+ network_file = g_file_new_for_uri ("network:///");
+
+ g_cancellable_cancel (view->networks_fetching_cancellable);
+ g_clear_object (&view->networks_fetching_cancellable);
+ view->networks_fetching_cancellable = g_cancellable_new ();
+ nautilus_gtk_places_view_set_fetching_networks (view, TRUE);
+ update_network_state (view);
+
+ g_object_ref (view);
+ g_file_enumerate_children_async (network_file,
+ "standard::type,standard::target-uri,standard::name,standard::display-name,standard::icon",
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ view->networks_fetching_cancellable,
+ network_enumeration_finished,
+ view);
+
+ g_clear_object (&network_file);
+}
+
+static void
+update_places (NautilusGtkPlacesView *view)
+{
+ GList *mounts;
+ GList *volumes;
+ GList *drives;
+ GList *l;
+ GIcon *icon;
+ GFile *file;
+ GtkWidget *child;
+ gchar *osname;
+
+ /* Clear all previously added items */
+ while ((child = gtk_widget_get_first_child (GTK_WIDGET (view->listbox))))
+ gtk_list_box_remove (GTK_LIST_BOX (view->listbox), child);
+
+ view->network_placeholder = NULL;
+ /* Inform clients that we started loading */
+ nautilus_gtk_places_view_set_loading (view, TRUE);
+
+ /* Add "Computer" row */
+ file = g_file_new_for_path ("/");
+ icon = g_themed_icon_new_with_default_fallbacks ("drive-harddisk");
+ osname = g_get_os_info (G_OS_INFO_KEY_NAME);
+
+ add_file (view, file, icon, ((osname != NULL) ? osname : _("Operating System")), "/", FALSE);
+
+ g_clear_object (&file);
+ g_clear_object (&icon);
+ g_free (osname);
+
+ /* Add currently connected drives */
+ drives = g_volume_monitor_get_connected_drives (view->volume_monitor);
+
+ for (l = drives; l != NULL; l = l->next)
+ add_drive (view, l->data);
+
+ g_list_free_full (drives, g_object_unref);
+
+ /*
+ * Since all volumes with an associated GDrive were already added with
+ * add_drive before, add all volumes that aren't associated with a
+ * drive.
+ */
+ volumes = g_volume_monitor_get_volumes (view->volume_monitor);
+
+ for (l = volumes; l != NULL; l = l->next)
+ {
+ GVolume *volume;
+ GDrive *drive;
+
+ volume = l->data;
+ drive = g_volume_get_drive (volume);
+
+ if (drive)
+ {
+ g_object_unref (drive);
+ continue;
+ }
+
+ add_volume (view, volume);
+ }
+
+ g_list_free_full (volumes, g_object_unref);
+
+ /*
+ * Now that all necessary drives and volumes were already added, add mounts
+ * that have no volume, such as /etc/mtab mounts, ftp, sftp, etc.
+ */
+ mounts = g_volume_monitor_get_mounts (view->volume_monitor);
+
+ for (l = mounts; l != NULL; l = l->next)
+ {
+ GMount *mount;
+ GVolume *volume;
+
+ mount = l->data;
+ volume = g_mount_get_volume (mount);
+
+ if (volume)
+ {
+ g_object_unref (volume);
+ continue;
+ }
+
+ add_mount (view, mount);
+ }
+
+ g_list_free_full (mounts, g_object_unref);
+
+ /* load saved servers */
+ populate_servers (view);
+
+ /* fetch networks and add them asynchronously */
+ fetch_networks (view);
+
+ update_view_mode (view);
+ /* Check whether we still are in a loading state */
+ update_loading (view);
+}
+
+static void
+server_mount_ready_cb (GObject *source_file,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data);
+ gboolean should_show;
+ GError *error;
+ GFile *location;
+
+ location = G_FILE (source_file);
+ should_show = TRUE;
+ error = NULL;
+
+ g_file_mount_enclosing_volume_finish (location, res, &error);
+ if (error)
+ {
+ should_show = FALSE;
+
+ if (error->code == G_IO_ERROR_ALREADY_MOUNTED)
+ {
+ /*
+ * Already mounted volume is not a critical error
+ * and we can still continue with the operation.
+ */
+ should_show = TRUE;
+ }
+ else if (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_CANCELLED &&
+ error->code != G_IO_ERROR_FAILED_HANDLED))
+ {
+ /* if it wasn't cancelled show a dialog */
+ emit_show_error_message (view, _("Unable to access location"), error->message);
+ }
+
+ /* The operation got cancelled by the user and or the error
+ has been handled already. */
+ g_clear_error (&error);
+ }
+
+ if (view->destroyed)
+ {
+ g_object_unref (view);
+ return;
+ }
+
+ view->should_pulse_entry = FALSE;
+ gtk_entry_set_progress_fraction (GTK_ENTRY (view->address_entry), 0);
+
+ /* Restore from Cancel to Connect */
+ gtk_button_set_label (GTK_BUTTON (view->connect_button), _("Con_nect"));
+ gtk_widget_set_sensitive (view->address_entry, TRUE);
+ view->connecting_to_server = FALSE;
+
+ if (should_show)
+ {
+ server_list_add_server (view, location);
+
+ /*
+ * Only clear the entry if it successfully connects to the server.
+ * Otherwise, the user would lost the typed address even if it fails
+ * to connect.
+ */
+ gtk_editable_set_text (GTK_EDITABLE (view->address_entry), "");
+
+ if (view->should_open_location)
+ {
+ GMount *mount;
+ GFile *root;
+
+ /*
+ * If the mount is not found at this point, it is probably user-
+ * invisible, which happens e.g for smb-browse, but the location
+ * should be opened anyway...
+ */
+ mount = g_file_find_enclosing_mount (location, view->cancellable, NULL);
+ if (mount)
+ {
+ root = g_mount_get_default_location (mount);
+
+ emit_open_location (view, root, view->open_flags);
+
+ g_object_unref (root);
+ g_object_unref (mount);
+ }
+ else
+ {
+ emit_open_location (view, location, view->open_flags);
+ }
+ }
+ }
+
+ update_places (view);
+ g_object_unref (view);
+}
+
+static void
+volume_mount_ready_cb (GObject *source_volume,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data);
+ gboolean should_show;
+ GVolume *volume;
+ GError *error;
+
+ volume = G_VOLUME (source_volume);
+ should_show = TRUE;
+ error = NULL;
+
+ g_volume_mount_finish (volume, res, &error);
+
+ if (error)
+ {
+ should_show = FALSE;
+
+ if (error->code == G_IO_ERROR_ALREADY_MOUNTED)
+ {
+ /*
+ * If the volume was already mounted, it's not a hard error
+ * and we can still continue with the operation.
+ */
+ should_show = TRUE;
+ }
+ else if (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_CANCELLED &&
+ error->code != G_IO_ERROR_FAILED_HANDLED))
+ {
+ /* if it wasn't cancelled show a dialog */
+ emit_show_error_message (NAUTILUS_GTK_PLACES_VIEW (user_data), _("Unable to access location"), error->message);
+ should_show = FALSE;
+ }
+
+ /* The operation got cancelled by the user and or the error
+ has been handled already. */
+ g_clear_error (&error);
+ }
+
+ if (view->destroyed)
+ {
+ g_object_unref(view);
+ return;
+ }
+
+ view->mounting_volume = FALSE;
+ update_loading (view);
+
+ if (should_show)
+ {
+ GMount *mount;
+ GFile *root;
+
+ mount = g_volume_get_mount (volume);
+ root = g_mount_get_default_location (mount);
+
+ if (view->should_open_location)
+ emit_open_location (NAUTILUS_GTK_PLACES_VIEW (user_data), root, view->open_flags);
+
+ g_object_unref (mount);
+ g_object_unref (root);
+ }
+
+ update_places (view);
+ g_object_unref (view);
+}
+
+static void
+unmount_ready_cb (GObject *source_mount,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesView *view;
+ GMount *mount;
+ GError *error;
+
+ view = NAUTILUS_GTK_PLACES_VIEW (user_data);
+ mount = G_MOUNT (source_mount);
+ error = NULL;
+
+ g_mount_unmount_with_operation_finish (mount, res, &error);
+
+ if (error)
+ {
+ if (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_CANCELLED &&
+ error->code != G_IO_ERROR_FAILED_HANDLED))
+ {
+ /* if it wasn't cancelled show a dialog */
+ emit_show_error_message (view, _("Unable to unmount volume"), error->message);
+ }
+
+ g_clear_error (&error);
+ }
+
+ if (view->destroyed) {
+ g_object_unref (view);
+ return;
+ }
+
+ view->row_for_action = NULL;
+ view->unmounting_mount = FALSE;
+ update_loading (view);
+
+ g_object_unref (view);
+}
+
+static gboolean
+pulse_entry_cb (gpointer user_data)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data);
+
+ if (view->destroyed)
+ {
+ view->entry_pulse_timeout_id = 0;
+
+ return G_SOURCE_REMOVE;
+ }
+ else if (view->should_pulse_entry)
+ {
+ gtk_entry_progress_pulse (GTK_ENTRY (view->address_entry));
+
+ return G_SOURCE_CONTINUE;
+ }
+ else
+ {
+ gtk_entry_set_progress_fraction (GTK_ENTRY (view->address_entry), 0);
+ view->entry_pulse_timeout_id = 0;
+
+ return G_SOURCE_REMOVE;
+ }
+}
+
+static void
+unmount_mount (NautilusGtkPlacesView *view,
+ GMount *mount)
+{
+ GMountOperation *operation;
+ GtkWidget *toplevel;
+
+ toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (view)));
+
+ g_cancellable_cancel (view->cancellable);
+ g_clear_object (&view->cancellable);
+ view->cancellable = g_cancellable_new ();
+
+ view->unmounting_mount = TRUE;
+ update_loading (view);
+
+ g_object_ref (view);
+
+ operation = gtk_mount_operation_new (GTK_WINDOW (toplevel));
+ g_mount_unmount_with_operation (mount,
+ 0,
+ operation,
+ view->cancellable,
+ unmount_ready_cb,
+ view);
+ g_object_unref (operation);
+}
+
+static void
+mount_server (NautilusGtkPlacesView *view,
+ GFile *location)
+{
+ GMountOperation *operation;
+ GtkWidget *toplevel;
+
+ g_cancellable_cancel (view->cancellable);
+ g_clear_object (&view->cancellable);
+ /* User cliked when the operation was ongoing, so wanted to cancel it */
+ if (view->connecting_to_server)
+ return;
+
+ view->cancellable = g_cancellable_new ();
+ toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (view)));
+ operation = gtk_mount_operation_new (GTK_WINDOW (toplevel));
+
+ view->should_pulse_entry = TRUE;
+ gtk_entry_set_progress_pulse_step (GTK_ENTRY (view->address_entry), 0.1);
+ gtk_entry_set_progress_fraction (GTK_ENTRY (view->address_entry), 0.1);
+ /* Allow to cancel the operation */
+ gtk_button_set_label (GTK_BUTTON (view->connect_button), _("Cance_l"));
+ gtk_widget_set_sensitive (view->address_entry, FALSE);
+ view->connecting_to_server = TRUE;
+ update_loading (view);
+
+ if (view->entry_pulse_timeout_id == 0)
+ view->entry_pulse_timeout_id = g_timeout_add (100, (GSourceFunc) pulse_entry_cb, view);
+
+ g_mount_operation_set_password_save (operation, G_PASSWORD_SAVE_FOR_SESSION);
+
+ /* make sure we keep the view around for as long as we are running */
+ g_object_ref (view);
+
+ g_file_mount_enclosing_volume (location,
+ 0,
+ operation,
+ view->cancellable,
+ server_mount_ready_cb,
+ view);
+
+ /* unref operation here - g_file_mount_enclosing_volume() does ref for itself */
+ g_object_unref (operation);
+}
+
+static void
+mount_volume (NautilusGtkPlacesView *view,
+ GVolume *volume)
+{
+ GMountOperation *operation;
+ GtkWidget *toplevel;
+
+ toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (view)));
+ operation = gtk_mount_operation_new (GTK_WINDOW (toplevel));
+
+ g_cancellable_cancel (view->cancellable);
+ g_clear_object (&view->cancellable);
+ view->cancellable = g_cancellable_new ();
+
+ view->mounting_volume = TRUE;
+ update_loading (view);
+
+ g_mount_operation_set_password_save (operation, G_PASSWORD_SAVE_FOR_SESSION);
+
+ /* make sure we keep the view around for as long as we are running */
+ g_object_ref (view);
+
+ g_volume_mount (volume,
+ 0,
+ operation,
+ view->cancellable,
+ volume_mount_ready_cb,
+ view);
+
+ /* unref operation here - g_file_mount_enclosing_volume() does ref for itself */
+ g_object_unref (operation);
+}
+
+static void
+open_cb (GtkWidget *widget,
+ const char *action_name,
+ GVariant *parameter)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget);
+ NautilusGtkPlacesOpenFlags flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL;
+
+ if (view->row_for_action == NULL)
+ return;
+
+ if (strcmp (action_name, "location.open") == 0)
+ flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL;
+ else if (strcmp (action_name, "location.open-tab") == 0)
+ flags = NAUTILUS_GTK_PLACES_OPEN_NEW_TAB;
+ else if (strcmp (action_name, "location.open-window") == 0)
+ flags = NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW;
+
+ activate_row (view, view->row_for_action, flags);
+}
+
+static void
+properties_cb (GtkWidget *widget,
+ const char *action_name,
+ GVariant *parameter)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget);
+ GList *list;
+ GMount *mount;
+ g_autoptr (GFile) location = NULL;
+ NautilusFile *file;
+
+ if (view->row_for_action == NULL)
+ return;
+
+ file = NULL;
+ mount = nautilus_gtk_places_view_row_get_mount (view->row_for_action);
+
+ if (mount)
+ {
+ location = g_mount_get_root (mount);
+ file = nautilus_file_get (location);
+ }
+ else
+ file = nautilus_file_get (nautilus_gtk_places_view_row_get_file (view->row_for_action));
+
+ if (file)
+ {
+ list = g_list_append (NULL, file);
+ nautilus_properties_window_present (list, widget, NULL, NULL, NULL);
+
+ nautilus_file_list_unref (list);
+ }
+}
+
+static void
+mount_cb (GtkWidget *widget,
+ const char *action_name,
+ GVariant *parameter)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget);
+ GVolume *volume;
+
+ if (view->row_for_action == NULL)
+ return;
+
+ volume = nautilus_gtk_places_view_row_get_volume (view->row_for_action);
+
+ /*
+ * When the mount item is activated, it's expected that
+ * the volume only gets mounted, without opening it after
+ * the operation is complete.
+ */
+ view->should_open_location = FALSE;
+
+ nautilus_gtk_places_view_row_set_busy (view->row_for_action, TRUE);
+ mount_volume (view, volume);
+}
+
+static void
+unmount_cb (GtkWidget *widget,
+ const char *action_name,
+ GVariant *parameter)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget);
+ GMount *mount;
+
+ if (view->row_for_action == NULL)
+ return;
+
+ mount = nautilus_gtk_places_view_row_get_mount (view->row_for_action);
+
+ nautilus_gtk_places_view_row_set_busy (view->row_for_action, TRUE);
+
+ unmount_mount (view, mount);
+}
+
+static void
+attach_protocol_row_to_grid (GtkGrid *grid,
+ const char *protocol_name,
+ const char *protocol_prefix)
+{
+ GtkWidget *name_label;
+ GtkWidget *prefix_label;
+
+ name_label = gtk_label_new (protocol_name);
+ gtk_widget_set_halign (name_label, GTK_ALIGN_START);
+ gtk_grid_attach_next_to (grid, name_label, NULL, GTK_POS_BOTTOM, 1, 1);
+
+ prefix_label = gtk_label_new (protocol_prefix);
+ gtk_widget_set_halign (prefix_label, GTK_ALIGN_START);
+ gtk_grid_attach_next_to (grid, prefix_label, name_label, GTK_POS_RIGHT, 1, 1);
+}
+
+static void
+populate_available_protocols_grid (GtkGrid *grid)
+{
+ const char * const *supported_protocols;
+ gboolean has_any = FALSE;
+
+ supported_protocols = g_vfs_get_supported_uri_schemes (g_vfs_get_default ());
+
+ if (g_strv_contains (supported_protocols, "afp"))
+ {
+ attach_protocol_row_to_grid (grid, _("AppleTalk"), "afp://");
+ has_any = TRUE;
+ }
+
+ if (g_strv_contains (supported_protocols, "ftp"))
+ {
+ attach_protocol_row_to_grid (grid, _("File Transfer Protocol"),
+ /* Translators: do not translate ftp:// and ftps:// */
+ _("ftp:// or ftps://"));
+ has_any = TRUE;
+ }
+
+ if (g_strv_contains (supported_protocols, "nfs"))
+ {
+ attach_protocol_row_to_grid (grid, _("Network File System"), "nfs://");
+ has_any = TRUE;
+ }
+
+ if (g_strv_contains (supported_protocols, "smb"))
+ {
+ attach_protocol_row_to_grid (grid, _("Samba"), "smb://");
+ has_any = TRUE;
+ }
+
+ if (g_strv_contains (supported_protocols, "ssh"))
+ {
+ attach_protocol_row_to_grid (grid, _("SSH File Transfer Protocol"),
+ /* Translators: do not translate sftp:// and ssh:// */
+ _("sftp:// or ssh://"));
+ has_any = TRUE;
+ }
+
+ if (g_strv_contains (supported_protocols, "dav"))
+ {
+ attach_protocol_row_to_grid (grid, _("WebDAV"),
+ /* Translators: do not translate dav:// and davs:// */
+ _("dav:// or davs://"));
+ has_any = TRUE;
+ }
+
+ if (!has_any)
+ gtk_widget_hide (GTK_WIDGET (grid));
+}
+
+static GMenuModel *
+get_menu_model (void)
+{
+ GMenu *menu;
+ GMenu *section;
+ GMenuItem *item;
+
+ menu = g_menu_new ();
+ section = g_menu_new ();
+ item = g_menu_item_new (_("_Open"), "location.open");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ item = g_menu_item_new (_("Open in New _Tab"), "location.open-tab");
+ g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ item = g_menu_item_new (_("Open in New _Window"), "location.open-window");
+ g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
+ g_object_unref (section);
+
+ section = g_menu_new ();
+ item = g_menu_item_new (_("_Disconnect"), "location.disconnect");
+ g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ item = g_menu_item_new (_("_Unmount"), "location.unmount");
+ g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+
+ item = g_menu_item_new (_("_Connect"), "location.connect");
+ g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ item = g_menu_item_new (_("_Mount"), "location.mount");
+ g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ item = g_menu_item_new (_("_Properties"), "location.properties");
+ g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
+ g_object_unref (section);
+
+ return G_MENU_MODEL (menu);
+}
+
+static void
+_popover_set_pointing_to_widget (GtkPopover *popover,
+ GtkWidget *target)
+{
+ GtkWidget *parent;
+ double x, y, w, h;
+
+ parent = gtk_widget_get_parent (GTK_WIDGET (popover));
+
+ if (!gtk_widget_translate_coordinates (target, parent, 0, 0, &x, &y))
+ return;
+
+ w = gtk_widget_get_allocated_width (GTK_WIDGET (target));
+ h = gtk_widget_get_allocated_height (GTK_WIDGET (target));
+
+ gtk_popover_set_pointing_to (popover, &(GdkRectangle){x, y, w, h});
+}
+
+static gboolean
+real_popup_menu (GtkWidget *widget,
+ double x,
+ double y)
+{
+ NautilusGtkPlacesViewRow *row = NAUTILUS_GTK_PLACES_VIEW_ROW (widget);
+ NautilusGtkPlacesView *view;
+ GMount *mount;
+ GFile *file;
+ gboolean is_root, is_network;
+ double x_in_view, y_in_view;
+
+ view = NAUTILUS_GTK_PLACES_VIEW (gtk_widget_get_ancestor (GTK_WIDGET (row), NAUTILUS_TYPE_GTK_PLACES_VIEW));
+
+ mount = nautilus_gtk_places_view_row_get_mount (row);
+ file = nautilus_gtk_places_view_row_get_file (row);
+ is_root = file && nautilus_is_root_directory (file);
+ is_network = nautilus_gtk_places_view_row_get_is_network (row);
+
+ gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.disconnect",
+ !file && mount && is_network);
+ gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.unmount",
+ !file && mount && !is_network);
+ gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.connect",
+ !file && !mount && is_network);
+ gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.mount",
+ !file && !mount && !is_network);
+ gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.properties",
+ (is_root ||
+ (mount && !(file && is_network))));
+
+ if (!view->popup_menu)
+ {
+ GMenuModel *model = get_menu_model ();
+
+ view->popup_menu = gtk_popover_menu_new_from_model (model);
+
+ gtk_popover_set_has_arrow (GTK_POPOVER (view->popup_menu), FALSE);
+ gtk_widget_set_parent (view->popup_menu, GTK_WIDGET (view));
+ gtk_widget_set_halign (view->popup_menu, GTK_ALIGN_START);
+
+ g_object_unref (model);
+ }
+
+ if (view->row_for_action)
+ g_object_set_data (G_OBJECT (view->row_for_action), "menu", NULL);
+
+ if (x == -1 && y == -1)
+ _popover_set_pointing_to_widget (GTK_POPOVER (view->popup_menu), widget);
+ else
+ {
+ gtk_widget_translate_coordinates (widget, GTK_WIDGET (view),
+ x, y, &x_in_view, &y_in_view);
+ gtk_popover_set_pointing_to (GTK_POPOVER (view->popup_menu),
+ &(GdkRectangle){x_in_view, y_in_view, 0, 0});
+ }
+
+ view->row_for_action = row;
+ if (view->row_for_action)
+ g_object_set_data (G_OBJECT (view->row_for_action), "menu", view->popup_menu);
+
+ gtk_popover_popup (GTK_POPOVER (view->popup_menu));
+
+ return TRUE;
+}
+
+static gboolean
+on_row_popup_menu (GtkWidget *widget,
+ GVariant *args,
+ gpointer user_data)
+{
+ return real_popup_menu (widget, -1, -1);
+}
+
+static void
+click_cb (GtkGesture *gesture,
+ int n_press,
+ double x,
+ double y,
+ gpointer user_data)
+{
+ real_popup_menu (GTK_WIDGET (user_data), x, y);
+}
+
+static gboolean
+on_key_press_event (GtkEventController *controller,
+ guint keyval,
+ guint keycode,
+ GdkModifierType state,
+ NautilusGtkPlacesView *view)
+{
+ GdkModifierType modifiers;
+
+ modifiers = gtk_accelerator_get_default_mod_mask ();
+
+ if (keyval == GDK_KEY_Return ||
+ keyval == GDK_KEY_KP_Enter ||
+ keyval == GDK_KEY_ISO_Enter ||
+ keyval == GDK_KEY_space)
+ {
+ GtkWidget *focus_widget;
+ GtkWindow *toplevel;
+
+ view->current_open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL;
+ toplevel = get_toplevel (GTK_WIDGET (view));
+
+ if (!toplevel)
+ return FALSE;
+
+ focus_widget = gtk_root_get_focus (GTK_ROOT (toplevel));
+
+ if (!NAUTILUS_IS_GTK_PLACES_VIEW_ROW (focus_widget))
+ return FALSE;
+
+ if ((state & modifiers) == GDK_SHIFT_MASK)
+ view->current_open_flags = NAUTILUS_GTK_PLACES_OPEN_NEW_TAB;
+ else if ((state & modifiers) == GDK_CONTROL_MASK)
+ view->current_open_flags = NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW;
+
+ activate_row (view, NAUTILUS_GTK_PLACES_VIEW_ROW (focus_widget), view->current_open_flags);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+on_middle_click_row_event (GtkGestureClick *gesture,
+ guint n_press,
+ double x,
+ double y,
+ NautilusGtkPlacesView *view)
+{
+ GtkListBoxRow *row;
+
+ if (n_press != 1)
+ return;
+
+ row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (view->listbox), y);
+ if (row != NULL && gtk_widget_is_sensitive (GTK_WIDGET (row)))
+ activate_row (view, NAUTILUS_GTK_PLACES_VIEW_ROW (row), NAUTILUS_GTK_PLACES_OPEN_NEW_TAB);
+}
+
+
+static void
+on_eject_button_clicked (GtkWidget *widget,
+ NautilusGtkPlacesViewRow *row)
+{
+ if (row)
+ {
+ GtkWidget *view = gtk_widget_get_ancestor (GTK_WIDGET (row), NAUTILUS_TYPE_GTK_PLACES_VIEW);
+
+ unmount_mount (NAUTILUS_GTK_PLACES_VIEW (view), nautilus_gtk_places_view_row_get_mount (row));
+ }
+}
+
+static void
+on_connect_button_clicked (NautilusGtkPlacesView *view)
+{
+ const char *uri;
+ GFile *file;
+
+ file = NULL;
+
+ /*
+ * Since the 'Connect' button is updated whenever the typed
+ * address changes, it is sufficient to check if it's sensitive
+ * or not, in order to determine if the given address is valid.
+ */
+ if (!gtk_widget_get_sensitive (view->connect_button))
+ return;
+
+ uri = gtk_editable_get_text (GTK_EDITABLE (view->address_entry));
+
+ if (uri != NULL && uri[0] != '\0')
+ file = g_file_new_for_commandline_arg (uri);
+
+ if (file)
+ {
+ view->should_open_location = TRUE;
+
+ mount_server (view, file);
+ }
+ else
+ {
+ emit_show_error_message (view, _("Unable to get remote server location"), NULL);
+ }
+}
+
+static void
+on_address_entry_text_changed (NautilusGtkPlacesView *view)
+{
+ const char * const *supported_protocols;
+ char *address, *scheme;
+ gboolean supported;
+
+ supported = FALSE;
+ supported_protocols = g_vfs_get_supported_uri_schemes (g_vfs_get_default ());
+ address = g_strdup (gtk_editable_get_text (GTK_EDITABLE (view->address_entry)));
+ scheme = g_uri_parse_scheme (address);
+
+ if (!supported_protocols)
+ goto out;
+
+ if (!scheme)
+ goto out;
+
+ supported = g_strv_contains (supported_protocols, scheme) &&
+ !g_strv_contains (unsupported_protocols, scheme);
+
+out:
+ gtk_widget_set_sensitive (view->connect_button, supported);
+ if (scheme && !supported)
+ gtk_widget_add_css_class (view->address_entry, "error");
+ else
+ gtk_widget_remove_css_class (view->address_entry, "error");
+
+ g_free (address);
+ g_free (scheme);
+}
+
+static void
+on_address_entry_show_help_pressed (NautilusGtkPlacesView *view,
+ GtkEntryIconPosition icon_pos,
+ GtkEntry *entry)
+{
+ GdkRectangle rect;
+ double x, y;
+
+ /* Setup the auxiliary popover's rectangle */
+ gtk_entry_get_icon_area (GTK_ENTRY (view->address_entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ &rect);
+ gtk_widget_translate_coordinates (view->address_entry, GTK_WIDGET (view),
+ rect.x, rect.y, &x, &y);
+
+ rect.x = x;
+ rect.y = y;
+ gtk_popover_set_pointing_to (GTK_POPOVER (view->server_adresses_popover), &rect);
+ gtk_widget_set_visible (view->server_adresses_popover, TRUE);
+}
+
+static void
+on_recent_servers_listbox_row_activated (NautilusGtkPlacesView *view,
+ NautilusGtkPlacesViewRow *row,
+ GtkWidget *listbox)
+{
+ char *uri;
+
+ uri = g_object_get_data (G_OBJECT (row), "uri");
+
+ gtk_editable_set_text (GTK_EDITABLE (view->address_entry), uri);
+
+ gtk_widget_hide (view->recent_servers_popover);
+}
+
+static void
+on_listbox_row_activated (NautilusGtkPlacesView *view,
+ NautilusGtkPlacesViewRow *row,
+ GtkWidget *listbox)
+{
+ activate_row (view, row, view->current_open_flags);
+}
+
+static gboolean
+listbox_filter_func (GtkListBoxRow *row,
+ gpointer user_data)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data);
+ gboolean is_placeholder;
+ gboolean retval;
+ gboolean searching;
+ char *name;
+ char *path;
+
+ retval = FALSE;
+ searching = view->search_query && view->search_query[0] != '\0';
+
+ is_placeholder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "is-placeholder"));
+
+ if (is_placeholder && searching)
+ return FALSE;
+
+ if (!searching)
+ return TRUE;
+
+ g_object_get (row,
+ "name", &name,
+ "path", &path,
+ NULL);
+
+ if (name)
+ {
+ char *lowercase_name = g_utf8_strdown (name, -1);
+
+ retval |= strstr (lowercase_name, view->search_query) != NULL;
+
+ g_free (lowercase_name);
+ }
+
+ if (path)
+ {
+ char *lowercase_path = g_utf8_strdown (path, -1);
+
+ retval |= strstr (lowercase_path, view->search_query) != NULL;
+
+ g_free (lowercase_path);
+ }
+
+ g_free (name);
+ g_free (path);
+
+ return retval;
+}
+
+static void
+listbox_header_func (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ gpointer user_data)
+{
+ gboolean row_is_network;
+ char *text;
+
+ text = NULL;
+ row_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "is-network"));
+
+ if (!before)
+ {
+ text = g_strdup_printf ("<b>%s</b>", row_is_network ? _("Networks") : _("On This Computer"));
+ }
+ else
+ {
+ gboolean before_is_network;
+
+ before_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (before), "is-network"));
+
+ if (before_is_network != row_is_network)
+ text = g_strdup_printf ("<b>%s</b>", row_is_network ? _("Networks") : _("On This Computer"));
+ }
+
+ if (text)
+ {
+ GtkWidget *header;
+ GtkWidget *label;
+ GtkWidget *separator;
+
+ header = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_widget_set_margin_top (header, 6);
+
+ separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+
+ label = g_object_new (GTK_TYPE_LABEL,
+ "use_markup", TRUE,
+ "margin-start", 12,
+ "label", text,
+ "xalign", 0.0f,
+ NULL);
+ if (row_is_network)
+ {
+ GtkWidget *header_name;
+ GtkWidget *network_header_spinner;
+
+ gtk_widget_set_margin_end (label, 6);
+
+ header_name = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ network_header_spinner = gtk_spinner_new ();
+ gtk_widget_set_margin_end (network_header_spinner, 12);
+ g_object_bind_property (NAUTILUS_GTK_PLACES_VIEW (user_data),
+ "fetching-networks",
+ network_header_spinner,
+ "spinning",
+ G_BINDING_SYNC_CREATE);
+
+ gtk_box_append (GTK_BOX (header_name), label);
+ gtk_box_append (GTK_BOX (header_name), network_header_spinner);
+ gtk_box_append (GTK_BOX (header), header_name);
+ }
+ else
+ {
+ gtk_widget_set_hexpand (label, TRUE);
+ gtk_widget_set_margin_end (label, 12);
+ gtk_box_append (GTK_BOX (header), label);
+ }
+
+ gtk_box_append (GTK_BOX (header), separator);
+
+ gtk_list_box_row_set_header (row, header);
+
+ g_free (text);
+ }
+ else
+ {
+ gtk_list_box_row_set_header (row, NULL);
+ }
+}
+
+static int
+listbox_sort_func (GtkListBoxRow *row1,
+ GtkListBoxRow *row2,
+ gpointer user_data)
+{
+ gboolean row1_is_network;
+ gboolean row2_is_network;
+ char *path1;
+ char *path2;
+ gboolean *is_placeholder1;
+ gboolean *is_placeholder2;
+ int retval;
+
+ row1_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row1), "is-network"));
+ row2_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row2), "is-network"));
+
+ retval = row1_is_network - row2_is_network;
+
+ if (retval != 0)
+ return retval;
+
+ is_placeholder1 = g_object_get_data (G_OBJECT (row1), "is-placeholder");
+ is_placeholder2 = g_object_get_data (G_OBJECT (row2), "is-placeholder");
+
+ /* we can't have two placeholders for the same section */
+ g_assert (!(is_placeholder1 != NULL && is_placeholder2 != NULL));
+
+ if (is_placeholder1)
+ return -1;
+ if (is_placeholder2)
+ return 1;
+
+ g_object_get (row1, "path", &path1, NULL);
+ g_object_get (row2, "path", &path2, NULL);
+
+ retval = g_utf8_collate (path1, path2);
+
+ g_free (path1);
+ g_free (path2);
+
+ return retval;
+}
+
+static void
+nautilus_gtk_places_view_constructed (GObject *object)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (object);
+
+ G_OBJECT_CLASS (nautilus_gtk_places_view_parent_class)->constructed (object);
+
+ gtk_list_box_set_sort_func (GTK_LIST_BOX (view->listbox),
+ listbox_sort_func,
+ object,
+ NULL);
+ gtk_list_box_set_filter_func (GTK_LIST_BOX (view->listbox),
+ listbox_filter_func,
+ object,
+ NULL);
+ gtk_list_box_set_header_func (GTK_LIST_BOX (view->listbox),
+ listbox_header_func,
+ object,
+ NULL);
+
+ /* load drives */
+ update_places (view);
+
+ g_signal_connect_swapped (view->volume_monitor,
+ "mount-added",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (view->volume_monitor,
+ "mount-changed",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (view->volume_monitor,
+ "mount-removed",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (view->volume_monitor,
+ "volume-added",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (view->volume_monitor,
+ "volume-changed",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (view->volume_monitor,
+ "volume-removed",
+ G_CALLBACK (update_places),
+ object);
+}
+
+static void
+nautilus_gtk_places_view_map (GtkWidget *widget)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget);
+
+ gtk_editable_set_text (GTK_EDITABLE (view->address_entry), "");
+
+ GTK_WIDGET_CLASS (nautilus_gtk_places_view_parent_class)->map (widget);
+}
+
+static void
+nautilus_gtk_places_view_class_init (NautilusGtkPlacesViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = nautilus_gtk_places_view_finalize;
+ object_class->dispose = nautilus_gtk_places_view_dispose;
+ object_class->constructed = nautilus_gtk_places_view_constructed;
+ object_class->get_property = nautilus_gtk_places_view_get_property;
+ object_class->set_property = nautilus_gtk_places_view_set_property;
+
+ widget_class->map = nautilus_gtk_places_view_map;
+
+ /*
+ * NautilusGtkPlacesView::open-location:
+ * @view: the object which received the signal.
+ * @location: (type Gio.File): GFile to which the caller should switch.
+ * @open_flags: a single value from NautilusGtkPlacesOpenFlags specifying how the @location
+ * should be opened.
+ *
+ * The places view emits this signal when the user selects a location
+ * in it. The calling application should display the contents of that
+ * location; for example, a file manager should show a list of files in
+ * the specified location.
+ */
+ places_view_signals [OPEN_LOCATION] =
+ g_signal_new ("open-location",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesViewClass, open_location),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 2,
+ G_TYPE_OBJECT,
+ NAUTILUS_TYPE_OPEN_FLAGS);
+
+ /*
+ * NautilusGtkPlacesView::show-error-message:
+ * @view: the object which received the signal.
+ * @primary: primary message with a summary of the error to show.
+ * @secondary: secondary message with details of the error to show.
+ *
+ * The places view emits this signal when it needs the calling
+ * application to present an error message. Most of these messages
+ * refer to mounting or unmounting media, for example, when a drive
+ * cannot be started for some reason.
+ */
+ places_view_signals [SHOW_ERROR_MESSAGE] =
+ g_signal_new ("show-error-message",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesViewClass, show_error_message),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ properties[PROP_LOADING] =
+ g_param_spec_boolean ("loading",
+ "Loading",
+ "Whether the view is loading locations",
+ FALSE,
+ G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+
+ properties[PROP_FETCHING_NETWORKS] =
+ g_param_spec_boolean ("fetching-networks",
+ "Fetching networks",
+ "Whether the view is fetching networks",
+ FALSE,
+ G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+
+ properties[PROP_OPEN_FLAGS] =
+ g_param_spec_flags ("open-flags",
+ "Open Flags",
+ "Modes in which the calling application can open locations selected in the sidebar",
+ NAUTILUS_TYPE_OPEN_FLAGS,
+ NAUTILUS_GTK_PLACES_OPEN_NORMAL,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ /* Bind class to template */
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/gtk/ui/nautilusgtkplacesview.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, actionbar);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, address_entry);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, address_entry_completion);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, completion_store);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, connect_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, listbox);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, recent_servers_listbox);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, recent_servers_popover);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, recent_servers_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, server_adresses_popover);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, available_protocols_grid);
+
+ gtk_widget_class_bind_template_callback (widget_class, on_address_entry_text_changed);
+ gtk_widget_class_bind_template_callback (widget_class, on_address_entry_show_help_pressed);
+ gtk_widget_class_bind_template_callback (widget_class, on_connect_button_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, on_listbox_row_activated);
+ gtk_widget_class_bind_template_callback (widget_class, on_recent_servers_listbox_row_activated);
+
+ /**
+ * NautilusGtkPlacesView|location.open:
+ *
+ * Opens the location in the current window.
+ */
+ gtk_widget_class_install_action (widget_class, "location.open", NULL, open_cb);
+
+ /**
+ * NautilusGtkPlacesView|location.open-tab:
+ *
+ * Opens the location in a new tab.
+ */
+ gtk_widget_class_install_action (widget_class, "location.open-tab", NULL, open_cb);
+
+ /**
+ * NautilusGtkPlacesView|location.open-window:
+ *
+ * Opens the location in a new window.
+ */
+ gtk_widget_class_install_action (widget_class, "location.open-window", NULL, open_cb);
+
+ /**
+ * NautilusGtkPlacesView|location.mount:
+ *
+ * Mount the location.
+ */
+ gtk_widget_class_install_action (widget_class, "location.mount", NULL, mount_cb);
+
+ /**
+ * NautilusGtkPlacesView|location.connect:
+ *
+ * Connect the location.
+ */
+ gtk_widget_class_install_action (widget_class, "location.connect", NULL, mount_cb);
+
+ /**
+ * NautilusGtkPlacesView|location.unmount:
+ *
+ * Unmount the location.
+ */
+ gtk_widget_class_install_action (widget_class, "location.unmount", NULL, unmount_cb);
+
+ /**
+ * NautilusGtkPlacesView|location.disconnect:
+ *
+ * Disconnect the location.
+ */
+ gtk_widget_class_install_action (widget_class, "location.disconnect", NULL, unmount_cb);
+
+ /**
+ * NautilusGtkPlacesView|location.properties:
+ *
+ * Show location properties.
+ */
+ gtk_widget_class_install_action (widget_class, "location.properties", NULL, properties_cb);
+
+ gtk_widget_class_set_css_name (widget_class, "placesview");
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LIST);
+}
+
+static void
+nautilus_gtk_places_view_init (NautilusGtkPlacesView *self)
+{
+ GtkEventController *controller;
+
+ self->volume_monitor = g_volume_monitor_get ();
+ self->open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL;
+ self->path_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+ self->space_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ gtk_widget_action_set_enabled (GTK_WIDGET (self), "location.open-tab", FALSE);
+ gtk_widget_action_set_enabled (GTK_WIDGET (self), "location.open-window", FALSE);
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_widget_set_parent (self->server_adresses_popover, GTK_WIDGET (self));
+ controller = gtk_event_controller_key_new ();
+ g_signal_connect (controller, "key-pressed", G_CALLBACK (on_key_press_event), self);
+ gtk_widget_add_controller (GTK_WIDGET (self), controller);
+
+ /* We need an additional controller because GtkListBox only
+ * activates rows for GDK_BUTTON_PRIMARY clicks
+ */
+ controller = (GtkEventController *) gtk_gesture_click_new ();
+ gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), GDK_BUTTON_MIDDLE);
+ g_signal_connect (controller, "released",
+ G_CALLBACK (on_middle_click_row_event), self);
+ gtk_widget_add_controller (self->listbox, controller);
+
+ populate_available_protocols_grid (GTK_GRID (self->available_protocols_grid));
+}
+
+/*
+ * nautilus_gtk_places_view_new:
+ *
+ * Creates a new NautilusGtkPlacesView widget.
+ *
+ * The application should connect to at least the
+ * NautilusGtkPlacesView::open-location signal to be notified
+ * when the user makes a selection in the view.
+ *
+ * Returns: a newly created NautilusGtkPlacesView
+ */
+GtkWidget *
+nautilus_gtk_places_view_new (void)
+{
+ return g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW, NULL);
+}
+
+/*
+ * nautilus_gtk_places_view_set_open_flags:
+ * @view: a NautilusGtkPlacesView
+ * @flags: Bitmask of modes in which the calling application can open locations
+ *
+ * Sets the way in which the calling application can open new locations from
+ * the places view. For example, some applications only open locations
+ * “directly” into their main view, while others may support opening locations
+ * in a new notebook tab or a new window.
+ *
+ * This function is used to tell the places @view about the ways in which the
+ * application can open new locations, so that the view can display (or not)
+ * the “Open in new tab” and “Open in new window” menu items as appropriate.
+ *
+ * When the NautilusGtkPlacesView::open-location signal is emitted, its flags
+ * argument will be set to one of the @flags that was passed in
+ * nautilus_gtk_places_view_set_open_flags().
+ *
+ * Passing 0 for @flags will cause NAUTILUS_GTK_PLACES_OPEN_NORMAL to always be sent
+ * to callbacks for the “open-location” signal.
+ */
+void
+nautilus_gtk_places_view_set_open_flags (NautilusGtkPlacesView *view,
+ NautilusGtkPlacesOpenFlags flags)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view));
+
+ if (view->open_flags == flags)
+ return;
+
+ view->open_flags = flags;
+
+ gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.open-tab",
+ (flags & NAUTILUS_GTK_PLACES_OPEN_NEW_TAB) != 0);
+ gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.open-window",
+ (flags & NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW) != 0);
+
+ g_object_notify_by_pspec (G_OBJECT (view), properties[PROP_OPEN_FLAGS]);
+}
+
+/*
+ * nautilus_gtk_places_view_get_open_flags:
+ * @view: a NautilusGtkPlacesSidebar
+ *
+ * Gets the open flags.
+ *
+ * Returns: the NautilusGtkPlacesOpenFlags of @view
+ */
+NautilusGtkPlacesOpenFlags
+nautilus_gtk_places_view_get_open_flags (NautilusGtkPlacesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), 0);
+
+ return view->open_flags;
+}
+
+/*
+ * nautilus_gtk_places_view_get_search_query:
+ * @view: a NautilusGtkPlacesView
+ *
+ * Retrieves the current search query from @view.
+ *
+ * Returns: (transfer none): the current search query.
+ */
+const char *
+nautilus_gtk_places_view_get_search_query (NautilusGtkPlacesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), NULL);
+
+ return view->search_query;
+}
+
+/*
+ * nautilus_gtk_places_view_set_search_query:
+ * @view: a NautilusGtkPlacesView
+ * @query_text: the query, or NULL.
+ *
+ * Sets the search query of @view. The search is immediately performed
+ * once the query is set.
+ */
+void
+nautilus_gtk_places_view_set_search_query (NautilusGtkPlacesView *view,
+ const char *query_text)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view));
+
+ if (g_strcmp0 (view->search_query, query_text) != 0)
+ {
+ g_clear_pointer (&view->search_query, g_free);
+ view->search_query = g_utf8_strdown (query_text, -1);
+
+ gtk_list_box_invalidate_filter (GTK_LIST_BOX (view->listbox));
+ gtk_list_box_invalidate_headers (GTK_LIST_BOX (view->listbox));
+
+ update_view_mode (view);
+ }
+}
+
+/*
+ * nautilus_gtk_places_view_get_loading:
+ * @view: a NautilusGtkPlacesView
+ *
+ * Returns %TRUE if the view is loading locations.
+ */
+gboolean
+nautilus_gtk_places_view_get_loading (NautilusGtkPlacesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), FALSE);
+
+ return view->loading;
+}
+
+static void
+update_loading (NautilusGtkPlacesView *view)
+{
+ gboolean loading;
+
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view));
+
+ loading = view->fetching_networks || view->connecting_to_server ||
+ view->mounting_volume || view->unmounting_mount;
+
+ set_busy_cursor (view, loading);
+ nautilus_gtk_places_view_set_loading (view, loading);
+}
+
+static void
+nautilus_gtk_places_view_set_loading (NautilusGtkPlacesView *view,
+ gboolean loading)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view));
+
+ if (view->loading != loading)
+ {
+ view->loading = loading;
+ g_object_notify_by_pspec (G_OBJECT (view), properties [PROP_LOADING]);
+ }
+}
+
+static gboolean
+nautilus_gtk_places_view_get_fetching_networks (NautilusGtkPlacesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), FALSE);
+
+ return view->fetching_networks;
+}
+
+static void
+nautilus_gtk_places_view_set_fetching_networks (NautilusGtkPlacesView *view,
+ gboolean fetching_networks)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view));
+
+ if (view->fetching_networks != fetching_networks)
+ {
+ view->fetching_networks = fetching_networks;
+ g_object_notify_by_pspec (G_OBJECT (view), properties [PROP_FETCHING_NETWORKS]);
+ }
+}
diff --git a/src/gtk/nautilusgtkplacesview.ui b/src/gtk/nautilusgtkplacesview.ui
new file mode 100644
index 0000000..fbedf70
--- /dev/null
+++ b/src/gtk/nautilusgtkplacesview.ui
@@ -0,0 +1,261 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk40">
+ <object class="GtkListStore" id="completion_store">
+ <columns>
+ <column type="gchararray"/>
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkEntryCompletion" id="address_entry_completion">
+ <property name="model">completion_store</property>
+ <property name="text-column">1</property>
+ <property name="inline-completion">1</property>
+ <property name="popup-completion">0</property>
+ </object>
+ <object class="GtkPopover" id="server_adresses_popover">
+ <property name="position">2</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">1</property>
+ <property name="spacing">6</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <property name="margin-top">18</property>
+ <property name="margin-bottom">18</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="hexpand">1</property>
+ <property name="label" translatable="yes">Server Addresses</property>
+ <attributes>
+ <attribute name="weight" value="bold"></attribute>
+ </attributes>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="hexpand">1</property>
+ <property name="label" translatable="yes">Server addresses are made up of a protocol prefix and an address. Examples:</property>
+ <property name="wrap">1</property>
+ <property name="width-chars">40</property>
+ <property name="max-width-chars">40</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="hexpand">1</property>
+ <property name="label">smb://gnome.org, ssh://192.168.0.1, ftp://[2001:db8::1]</property>
+ <property name="wrap">1</property>
+ <property name="width-chars">40</property>
+ <property name="max-width-chars">40</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkGrid" id="available_protocols_grid">
+ <property name="margin-top">12</property>
+ <property name="hexpand">1</property>
+ <property name="row-spacing">6</property>
+ <property name="column-spacing">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="hexpand">1</property>
+ <property name="label" translatable="yes">Available Protocols</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"></attribute>
+ </attributes>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Prefix</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"></attribute>
+ </attributes>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkPopover" id="recent_servers_popover">
+ <child>
+ <object class="GtkStack" id="recent_servers_stack">
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">empty</property>
+ <property name="child">
+ <object class="AdwStatusPage">
+ <property name="icon-name">network-server-symbolic</property>
+ <property name="title" translatable="yes" comments="Translators: Server as any successfully connected network address">No Recent Servers</property>
+ <style>
+ <class name="compact"/>
+ </style>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">list</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">1</property>
+ <property name="spacing">12</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Recent Servers</property>
+ <attributes>
+ <attribute name="weight" value="bold"></attribute>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="vexpand">1</property>
+ <property name="has-frame">1</property>
+ <property name="min-content-width">250</property>
+ <property name="min-content-height">200</property>
+ <child>
+ <object class="GtkViewport">
+ <child>
+ <object class="GtkListBox" id="recent_servers_listbox">
+ <property name="selection-mode">0</property>
+ <signal name="row-activated" handler="on_recent_servers_listbox_row_activated" object="NautilusGtkPlacesView" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <template class="NautilusGtkPlacesView" parent="GtkBox">
+ <accessibility>
+ <property name="label" translatable="yes">Other Locations</property>
+ <property name="description" translatable="yes">List of common local and remote mountpoints.</property>
+ </accessibility>
+ <property name="orientation">1</property>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="vhomogeneous">0</property>
+ <property name="transition-type">1</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">browse</property>
+ <property name="child">
+ <object class="GtkScrolledWindow">
+ <property name="hexpand">1</property>
+ <property name="vexpand">1</property>
+ <child>
+ <object class="GtkViewport">
+ <child>
+ <object class="GtkListBox" id="listbox">
+ <property name="selection-mode">0</property>
+ <signal name="row-activated" handler="on_listbox_row_activated" object="NautilusGtkPlacesView" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">empty-search</property>
+ <property name="child">
+ <object class="AdwStatusPage">
+ <property name="icon-name">edit-find-symbolic</property>
+ <property name="title" translatable="yes">No Results Found</property>
+ <property name="description" translatable="yes">Try a different search.</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkActionBar" id="actionbar">
+ <property name="hexpand">1</property>
+ <style>
+ <class name="background"/>
+ </style>
+ <child>
+ <object class="GtkLabel">
+ <property name="hexpand">1</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Connect to _Server</property>
+ <property name="mnemonic-widget">address_entry</property>
+ <property name="use-underline">1</property>
+ <attributes>
+ <attribute name="weight" value="bold"></attribute>
+ </attributes>
+ </object>
+ </child>
+ <child type="end">
+ <object class="GtkButton" id="connect_button">
+ <property name="label" translatable="yes">Con_nect</property>
+ <property name="use-underline">1</property>
+ <property name="sensitive">0</property>
+ <property name="valign">3</property>
+ <signal name="clicked" handler="on_connect_button_clicked" object="NautilusGtkPlacesView" swapped="yes"/>
+ </object>
+ </child>
+ <child type="end">
+ <object class="GtkBox">
+ <property name="hexpand">1</property>
+ <child>
+ <object class="GtkEntry" id="address_entry">
+ <property name="hexpand">1</property>
+ <property name="width-chars">20</property>
+ <property name="placeholder-text" translatable="yes">Enter server address…</property>
+ <property name="secondary-icon-name">dialog-question-symbolic</property>
+ <property name="completion">address_entry_completion</property>
+ <signal name="notify::text" handler="on_address_entry_text_changed" object="NautilusGtkPlacesView" swapped="yes"/>
+ <signal name="activate" handler="on_connect_button_clicked" object="NautilusGtkPlacesView" swapped="yes"/>
+ <signal name="icon-press" handler="on_address_entry_show_help_pressed" object="NautilusGtkPlacesView" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuButton" id="server_list_button">
+ <property name="direction">0</property>
+ <property name="popover">recent_servers_popover</property>
+ <property name="icon-name">pan-down-symbolic</property>
+ <style>
+ <class name="server-list-button"/>
+ </style>
+ </object>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/gtk/nautilusgtkplacesviewprivate.h b/src/gtk/nautilusgtkplacesviewprivate.h
new file mode 100644
index 0000000..4cf6e3e
--- /dev/null
+++ b/src/gtk/nautilusgtkplacesviewprivate.h
@@ -0,0 +1,55 @@
+/* nautilusgtkplacesview.h
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NAUTILUS_GTK_PLACES_VIEW_H
+#define NAUTILUS_GTK_PLACES_VIEW_H
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#endif
+
+#include "nautilusgtkplacessidebarprivate.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_GTK_PLACES_VIEW (nautilus_gtk_places_view_get_type ())
+#define NAUTILUS_GTK_PLACES_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_GTK_PLACES_VIEW, NautilusGtkPlacesView))
+#define NAUTILUS_GTK_PLACES_VIEW_CLASS(klass)(G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_GTK_PLACES_VIEW, NautilusGtkPlacesViewClass))
+#define NAUTILUS_IS_GTK_PLACES_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_GTK_PLACES_VIEW))
+#define NAUTILUS_IS_GTK_PLACES_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_GTK_PLACES_VIEW))
+#define NAUTILUS_GTK_PLACES_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_GTK_PLACES_VIEW, NautilusGtkPlacesViewClass))
+
+typedef struct _NautilusGtkPlacesView NautilusGtkPlacesView;
+typedef struct _NautilusGtkPlacesViewClass NautilusGtkPlacesViewClass;
+
+GType nautilus_gtk_places_view_get_type (void) G_GNUC_CONST;
+
+NautilusGtkPlacesOpenFlags nautilus_gtk_places_view_get_open_flags (NautilusGtkPlacesView *view);
+void nautilus_gtk_places_view_set_open_flags (NautilusGtkPlacesView *view,
+ NautilusGtkPlacesOpenFlags flags);
+
+const char * nautilus_gtk_places_view_get_search_query (NautilusGtkPlacesView *view);
+void nautilus_gtk_places_view_set_search_query (NautilusGtkPlacesView *view,
+ const char *query_text);
+
+gboolean nautilus_gtk_places_view_get_loading (NautilusGtkPlacesView *view);
+
+GtkWidget * nautilus_gtk_places_view_new (void);
+
+G_END_DECLS
+
+#endif /* NAUTILUS_GTK_PLACES_VIEW_H */
diff --git a/src/gtk/nautilusgtkplacesviewrow.c b/src/gtk/nautilusgtkplacesviewrow.c
new file mode 100644
index 0000000..64d8896
--- /dev/null
+++ b/src/gtk/nautilusgtkplacesviewrow.c
@@ -0,0 +1,508 @@
+/* nautilusgtkplacesviewrow.c
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "nautilus-application.h"
+#include "nautilus-enum-types.h"
+
+#include <gio/gio.h>
+
+#include "nautilusgtkplacesviewrowprivate.h"
+
+/* As this widget is shared with Nautilus, we use this guard to
+ * ensure that internally we only include the files that we need
+ * instead of including gtk.h
+ */
+#ifdef GTK_COMPILATION
+#else
+#include <gtk/gtk.h>
+#endif
+
+struct _NautilusGtkPlacesViewRow
+{
+ GtkListBoxRow parent_instance;
+
+ GtkLabel *available_space_label;
+ GtkStack *mount_stack;
+ GtkSpinner *busy_spinner;
+ GtkButton *eject_button;
+ GtkImage *eject_icon;
+ GtkImage *icon_image;
+ GtkLabel *name_label;
+ GtkLabel *path_label;
+
+ GVolume *volume;
+ GMount *mount;
+ GFile *file;
+
+ GCancellable *cancellable;
+
+ int is_network : 1;
+};
+
+G_DEFINE_TYPE (NautilusGtkPlacesViewRow, nautilus_gtk_places_view_row, GTK_TYPE_LIST_BOX_ROW)
+
+enum {
+ PROP_0,
+ PROP_ICON,
+ PROP_NAME,
+ PROP_PATH,
+ PROP_VOLUME,
+ PROP_MOUNT,
+ PROP_FILE,
+ PROP_IS_NETWORK,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+measure_available_space_finished (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesViewRow *row = user_data;
+ GFileInfo *info;
+ GError *error;
+ guint64 free_space;
+ guint64 total_space;
+ char *formatted_free_size;
+ char *formatted_total_size;
+ char *label;
+ guint plural_form;
+
+ error = NULL;
+
+ info = g_file_query_filesystem_info_finish (G_FILE (object),
+ res,
+ &error);
+
+ if (error)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
+ !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED))
+ {
+ g_warning ("Failed to measure available space: %s", error->message);
+ }
+
+ g_clear_error (&error);
+ goto out;
+ }
+
+ if (!g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE) ||
+ !g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE))
+ {
+ g_object_unref (info);
+ goto out;
+ }
+
+ free_space = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
+ total_space = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
+
+ formatted_free_size = g_format_size (free_space);
+ formatted_total_size = g_format_size (total_space);
+
+ /* read g_format_size code in glib for further understanding */
+ plural_form = free_space < 1000 ? free_space : free_space % 1000 + 1000;
+
+ /* Translators: respectively, free and total space of the drive. The plural form
+ * should be based on the free space available.
+ * i.e. 1 GB / 24 GB available.
+ */
+ label = g_strdup_printf (dngettext (GETTEXT_PACKAGE, "%s / %s available", "%s / %s available", plural_form),
+ formatted_free_size, formatted_total_size);
+
+ gtk_label_set_label (row->available_space_label, label);
+
+ g_object_unref (info);
+ g_free (formatted_total_size);
+ g_free (formatted_free_size);
+ g_free (label);
+out:
+ g_object_unref (object);
+}
+
+static void
+measure_available_space (NautilusGtkPlacesViewRow *row)
+{
+ gboolean skip_measure;
+ gboolean should_measure;
+ g_autoptr (GFile) root = NULL;
+
+ skip_measure = FALSE;
+ if (nautilus_application_is_sandboxed ())
+ {
+ root = g_file_new_for_uri ("file:///");
+ if (row->file != NULL)
+ skip_measure = g_file_equal (root, row->file);
+ }
+
+ should_measure = ((row->volume || row->mount || row->file) &&
+ !row->is_network && !skip_measure);
+
+ gtk_label_set_label (row->available_space_label, "");
+ gtk_widget_set_visible (GTK_WIDGET (row->available_space_label), should_measure);
+
+ if (should_measure)
+ {
+ GFile *file = NULL;
+
+ if (row->file)
+ {
+ file = g_object_ref (row->file);
+ }
+ else if (row->mount)
+ {
+ file = g_mount_get_root (row->mount);
+ }
+ else if (row->volume)
+ {
+ GMount *mount;
+
+ mount = g_volume_get_mount (row->volume);
+
+ if (mount)
+ file = g_mount_get_root (row->mount);
+
+ g_clear_object (&mount);
+ }
+
+ if (file)
+ {
+ g_cancellable_cancel (row->cancellable);
+ g_clear_object (&row->cancellable);
+ row->cancellable = g_cancellable_new ();
+
+ g_file_query_filesystem_info_async (file,
+ G_FILE_ATTRIBUTE_FILESYSTEM_FREE "," G_FILE_ATTRIBUTE_FILESYSTEM_SIZE,
+ G_PRIORITY_DEFAULT,
+ row->cancellable,
+ measure_available_space_finished,
+ row);
+ }
+ }
+}
+
+static void
+nautilus_gtk_places_view_row_finalize (GObject *object)
+{
+ NautilusGtkPlacesViewRow *self = NAUTILUS_GTK_PLACES_VIEW_ROW (object);
+
+ g_cancellable_cancel (self->cancellable);
+
+ g_clear_object (&self->volume);
+ g_clear_object (&self->mount);
+ g_clear_object (&self->file);
+ g_clear_object (&self->cancellable);
+
+ G_OBJECT_CLASS (nautilus_gtk_places_view_row_parent_class)->finalize (object);
+}
+
+static void
+nautilus_gtk_places_view_row_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusGtkPlacesViewRow *self;
+
+ self = NAUTILUS_GTK_PLACES_VIEW_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_ICON:
+ g_value_set_object (value, gtk_image_get_gicon (self->icon_image));
+ break;
+
+ case PROP_NAME:
+ g_value_set_string (value, gtk_label_get_label (self->name_label));
+ break;
+
+ case PROP_PATH:
+ g_value_set_string (value, gtk_label_get_label (self->path_label));
+ break;
+
+ case PROP_VOLUME:
+ g_value_set_object (value, self->volume);
+ break;
+
+ case PROP_MOUNT:
+ g_value_set_object (value, self->mount);
+ break;
+
+ case PROP_FILE:
+ g_value_set_object (value, self->file);
+ break;
+
+ case PROP_IS_NETWORK:
+ g_value_set_boolean (value, self->is_network);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+nautilus_gtk_places_view_row_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusGtkPlacesViewRow *self = NAUTILUS_GTK_PLACES_VIEW_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_ICON:
+ gtk_image_set_from_gicon (self->icon_image, g_value_get_object (value));
+ break;
+
+ case PROP_NAME:
+ gtk_label_set_label (self->name_label, g_value_get_string (value));
+ break;
+
+ case PROP_PATH:
+ gtk_label_set_label (self->path_label, g_value_get_string (value));
+ break;
+
+ case PROP_VOLUME:
+ g_set_object (&self->volume, g_value_get_object (value));
+ break;
+
+ case PROP_MOUNT:
+ g_set_object (&self->mount, g_value_get_object (value));
+ if (self->mount != NULL)
+ {
+ gtk_stack_set_visible_child (self->mount_stack, GTK_WIDGET (self->eject_button));
+ gtk_widget_set_child_visible (GTK_WIDGET (self->mount_stack), TRUE);
+ }
+ else
+ {
+ gtk_widget_set_child_visible (GTK_WIDGET (self->mount_stack), FALSE);
+ }
+ measure_available_space (self);
+ break;
+
+ case PROP_FILE:
+ g_set_object (&self->file, g_value_get_object (value));
+ measure_available_space (self);
+ break;
+
+ case PROP_IS_NETWORK:
+ nautilus_gtk_places_view_row_set_is_network (self, g_value_get_boolean (value));
+ measure_available_space (self);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+nautilus_gtk_places_view_row_size_allocate (GtkWidget *widget,
+ int width,
+ int height,
+ int baseline)
+{
+ GtkWidget *menu = GTK_WIDGET (g_object_get_data (G_OBJECT (widget), "menu"));
+
+ GTK_WIDGET_CLASS (nautilus_gtk_places_view_row_parent_class)->size_allocate (widget, width, height, baseline);
+ if (menu)
+ gtk_popover_present (GTK_POPOVER (menu));
+}
+
+static void
+nautilus_gtk_places_view_row_class_init (NautilusGtkPlacesViewRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = nautilus_gtk_places_view_row_finalize;
+ object_class->get_property = nautilus_gtk_places_view_row_get_property;
+ object_class->set_property = nautilus_gtk_places_view_row_set_property;
+
+ widget_class->size_allocate = nautilus_gtk_places_view_row_size_allocate;
+
+ properties[PROP_ICON] =
+ g_param_spec_object ("icon",
+ "Icon of the row",
+ "The icon representing the volume",
+ G_TYPE_ICON,
+ G_PARAM_READWRITE);
+
+ properties[PROP_NAME] =
+ g_param_spec_string ("name",
+ "Name of the volume",
+ "The name of the volume",
+ "",
+ G_PARAM_READWRITE);
+
+ properties[PROP_PATH] =
+ g_param_spec_string ("path",
+ "Path of the volume",
+ "The path of the volume",
+ "",
+ G_PARAM_READWRITE);
+
+ properties[PROP_VOLUME] =
+ g_param_spec_object ("volume",
+ "Volume represented by the row",
+ "The volume represented by the row",
+ G_TYPE_VOLUME,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+ properties[PROP_MOUNT] =
+ g_param_spec_object ("mount",
+ "Mount represented by the row",
+ "The mount point represented by the row, if any",
+ G_TYPE_MOUNT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+ properties[PROP_FILE] =
+ g_param_spec_object ("file",
+ "File represented by the row",
+ "The file represented by the row, if any",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+ properties[PROP_IS_NETWORK] =
+ g_param_spec_boolean ("is-network",
+ "Whether the row represents a network location",
+ "Whether the row represents a network location",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/gtk/ui/nautilusgtkplacesviewrow.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, available_space_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, mount_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, busy_spinner);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, eject_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, eject_icon);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, icon_image);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, name_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, path_label);
+}
+
+static void
+nautilus_gtk_places_view_row_init (NautilusGtkPlacesViewRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+GtkWidget*
+nautilus_gtk_places_view_row_new (GVolume *volume,
+ GMount *mount)
+{
+ return g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW,
+ "volume", volume,
+ "mount", mount,
+ NULL);
+}
+
+GMount*
+nautilus_gtk_places_view_row_get_mount (NautilusGtkPlacesViewRow *row)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row), NULL);
+
+ return row->mount;
+}
+
+GVolume*
+nautilus_gtk_places_view_row_get_volume (NautilusGtkPlacesViewRow *row)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row), NULL);
+
+ return row->volume;
+}
+
+GFile*
+nautilus_gtk_places_view_row_get_file (NautilusGtkPlacesViewRow *row)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row), NULL);
+
+ return row->file;
+}
+
+GtkWidget*
+nautilus_gtk_places_view_row_get_eject_button (NautilusGtkPlacesViewRow *row)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row), NULL);
+
+ return GTK_WIDGET (row->eject_button);
+}
+
+void
+nautilus_gtk_places_view_row_set_busy (NautilusGtkPlacesViewRow *row,
+ gboolean is_busy)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row));
+
+ if (is_busy)
+ {
+ gtk_stack_set_visible_child (row->mount_stack, GTK_WIDGET (row->busy_spinner));
+ gtk_widget_set_child_visible (GTK_WIDGET (row->mount_stack), TRUE);
+ gtk_spinner_start (row->busy_spinner);
+ }
+ else
+ {
+ gtk_widget_set_child_visible (GTK_WIDGET (row->mount_stack), FALSE);
+ gtk_spinner_stop (row->busy_spinner);
+ }
+}
+
+gboolean
+nautilus_gtk_places_view_row_get_is_network (NautilusGtkPlacesViewRow *row)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row), FALSE);
+
+ return row->is_network;
+}
+
+void
+nautilus_gtk_places_view_row_set_is_network (NautilusGtkPlacesViewRow *row,
+ gboolean is_network)
+{
+ if (row->is_network != is_network)
+ {
+ row->is_network = is_network;
+
+ gtk_image_set_from_icon_name (row->eject_icon, "media-eject-symbolic");
+ gtk_widget_set_tooltip_text (GTK_WIDGET (row->eject_button), is_network ? _("Disconnect") : _("Unmount"));
+ }
+}
+
+void
+nautilus_gtk_places_view_row_set_path_size_group (NautilusGtkPlacesViewRow *row,
+ GtkSizeGroup *group)
+{
+ if (group)
+ gtk_size_group_add_widget (group, GTK_WIDGET (row->path_label));
+}
+
+void
+nautilus_gtk_places_view_row_set_space_size_group (NautilusGtkPlacesViewRow *row,
+ GtkSizeGroup *group)
+{
+ if (group)
+ gtk_size_group_add_widget (group, GTK_WIDGET (row->available_space_label));
+}
diff --git a/src/gtk/nautilusgtkplacesviewrow.ui b/src/gtk/nautilusgtkplacesviewrow.ui
new file mode 100644
index 0000000..06c8041
--- /dev/null
+++ b/src/gtk/nautilusgtkplacesviewrow.ui
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk40">
+ <template class="NautilusGtkPlacesViewRow" parent="GtkListBoxRow">
+ <property name="width-request">100</property>
+ <property name="child">
+ <object class="GtkBox" id="box">
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">6</property>
+ <property name="margin-bottom">6</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkImage" id="icon_image">
+ <property name="pixel-size">32</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="name_label">
+ <property name="hexpand">1</property>
+ <property name="xalign">0</property>
+ <property name="ellipsize">3</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="available_space_label">
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="path_label">
+ <property name="justify">1</property>
+ <property name="ellipsize">2</property>
+ <property name="xalign">0</property>
+ <property name="max-width-chars">15</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="mount_stack">
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">button</property>
+ <property name="child">
+ <object class="GtkButton" id="eject_button">
+ <property name="visible">0</property>
+ <property name="halign">3</property>
+ <property name="valign">3</property>
+ <property name="tooltip-text" translatable="yes">Unmount</property>
+ <child>
+ <object class="GtkImage" id="eject_icon">
+ <property name="icon-name">media-eject-symbolic</property>
+ </object>
+ </child>
+ <style>
+ <class name="image-button"/>
+ <class name="sidebar-button"/>
+ </style>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">spinner</property>
+ <property name="child">
+ <object class="GtkSpinner" id="busy_spinner">
+ <property name="halign">3</property>
+ <property name="valign">3</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </template>
+</interface>
diff --git a/src/gtk/nautilusgtkplacesviewrowprivate.h b/src/gtk/nautilusgtkplacesviewrowprivate.h
new file mode 100644
index 0000000..d54b918
--- /dev/null
+++ b/src/gtk/nautilusgtkplacesviewrowprivate.h
@@ -0,0 +1,59 @@
+/* nautilusgtkplacesviewrow.h
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NAUTILUS_GTK_PLACES_VIEW_ROW_H
+#define NAUTILUS_GTK_PLACES_VIEW_ROW_H
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#endif
+
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW (nautilus_gtk_places_view_row_get_type())
+
+ G_DECLARE_FINAL_TYPE (NautilusGtkPlacesViewRow, nautilus_gtk_places_view_row, NAUTILUS, GTK_PLACES_VIEW_ROW, GtkListBoxRow)
+
+GtkWidget* nautilus_gtk_places_view_row_new (GVolume *volume,
+ GMount *mount);
+
+GtkWidget* nautilus_gtk_places_view_row_get_eject_button (NautilusGtkPlacesViewRow *row);
+
+GMount* nautilus_gtk_places_view_row_get_mount (NautilusGtkPlacesViewRow *row);
+
+GVolume* nautilus_gtk_places_view_row_get_volume (NautilusGtkPlacesViewRow *row);
+
+GFile* nautilus_gtk_places_view_row_get_file (NautilusGtkPlacesViewRow *row);
+
+void nautilus_gtk_places_view_row_set_busy (NautilusGtkPlacesViewRow *row,
+ gboolean is_busy);
+
+gboolean nautilus_gtk_places_view_row_get_is_network (NautilusGtkPlacesViewRow *row);
+
+void nautilus_gtk_places_view_row_set_is_network (NautilusGtkPlacesViewRow *row,
+ gboolean is_network);
+
+void nautilus_gtk_places_view_row_set_path_size_group (NautilusGtkPlacesViewRow *row,
+ GtkSizeGroup *group);
+
+void nautilus_gtk_places_view_row_set_space_size_group (NautilusGtkPlacesViewRow *row,
+ GtkSizeGroup *group);
+
+G_END_DECLS
+
+#endif /* NAUTILUS_GTK_PLACES_VIEW_ROW_H */
diff --git a/src/gtk/nautilusgtksidebarrow.c b/src/gtk/nautilusgtksidebarrow.c
new file mode 100644
index 0000000..9b6ebaf
--- /dev/null
+++ b/src/gtk/nautilusgtksidebarrow.c
@@ -0,0 +1,692 @@
+/* nautilusgtksidebarrow.c
+ *
+ * Copyright (C) 2015 Carlos Soriano <csoriano@gnome.org>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "nautilus-enum-types.h"
+#include "nautilus-file.h"
+
+#include "nautilusgtksidebarrowprivate.h"
+/* For section and place type enums */
+#include "nautilusgtkplacessidebarprivate.h"
+
+#include <cloudproviders.h>
+
+struct _NautilusGtkSidebarRow
+{
+ GtkListBoxRow parent_instance;
+ GIcon *start_icon;
+ GIcon *end_icon;
+ GtkWidget *start_icon_widget;
+ GtkWidget *end_icon_widget;
+ char *label;
+ char *tooltip;
+ char *eject_tooltip;
+ GtkWidget *label_widget;
+ gboolean ejectable;
+ GtkWidget *eject_button;
+ int order_index;
+ NautilusGtkPlacesSectionType section_type;
+ NautilusGtkPlacesPlaceType place_type;
+ char *uri;
+ NautilusFile *file;
+ GDrive *drive;
+ GVolume *volume;
+ GMount *mount;
+ GObject *cloud_provider_account;
+ gboolean placeholder;
+ NautilusGtkPlacesSidebar *sidebar;
+ GtkWidget *revealer;
+ GtkWidget *busy_spinner;
+};
+
+G_DEFINE_TYPE (NautilusGtkSidebarRow, nautilus_gtk_sidebar_row, GTK_TYPE_LIST_BOX_ROW)
+
+enum
+{
+ PROP_0,
+ PROP_START_ICON,
+ PROP_END_ICON,
+ PROP_LABEL,
+ PROP_TOOLTIP,
+ PROP_EJECT_TOOLTIP,
+ PROP_EJECTABLE,
+ PROP_SIDEBAR,
+ PROP_ORDER_INDEX,
+ PROP_SECTION_TYPE,
+ PROP_PLACE_TYPE,
+ PROP_URI,
+ PROP_NAUTILUS_FILE,
+ PROP_DRIVE,
+ PROP_VOLUME,
+ PROP_MOUNT,
+ PROP_CLOUD_PROVIDER_ACCOUNT,
+ PROP_PLACEHOLDER,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+cloud_row_update (NautilusGtkSidebarRow *self)
+{
+ CloudProvidersAccount *account;
+ GIcon *end_icon;
+ int provider_status;
+
+ account = CLOUD_PROVIDERS_ACCOUNT (self->cloud_provider_account);
+ provider_status = cloud_providers_account_get_status (account);
+ switch (provider_status)
+ {
+ case CLOUD_PROVIDERS_ACCOUNT_STATUS_IDLE:
+ end_icon = NULL;
+ break;
+
+ case CLOUD_PROVIDERS_ACCOUNT_STATUS_SYNCING:
+ end_icon = g_themed_icon_new ("emblem-synchronizing-symbolic");
+ break;
+
+ case CLOUD_PROVIDERS_ACCOUNT_STATUS_ERROR:
+ end_icon = g_themed_icon_new ("dialog-warning-symbolic");
+ break;
+
+ default:
+ return;
+ }
+
+ g_object_set (self,
+ "label", cloud_providers_account_get_name (account),
+ NULL);
+ g_object_set (self,
+ "tooltip", cloud_providers_account_get_status_details (account),
+ NULL);
+ g_object_set (self,
+ "end-icon", end_icon,
+ NULL);
+
+ if (end_icon != NULL)
+ g_object_unref (end_icon);
+}
+
+static void
+nautilus_gtk_sidebar_row_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusGtkSidebarRow *self = NAUTILUS_GTK_SIDEBAR_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_SIDEBAR:
+ g_value_set_object (value, self->sidebar);
+ break;
+
+ case PROP_START_ICON:
+ g_value_set_object (value, self->start_icon);
+ break;
+
+ case PROP_END_ICON:
+ g_value_set_object (value, self->end_icon);
+ break;
+
+ case PROP_LABEL:
+ g_value_set_string (value, self->label);
+ break;
+
+ case PROP_TOOLTIP:
+ g_value_set_string (value, self->tooltip);
+ break;
+
+ case PROP_EJECT_TOOLTIP:
+ g_value_set_string (value, self->eject_tooltip);
+ break;
+
+ case PROP_EJECTABLE:
+ g_value_set_boolean (value, self->ejectable);
+ break;
+
+ case PROP_ORDER_INDEX:
+ g_value_set_int (value, self->order_index);
+ break;
+
+ case PROP_SECTION_TYPE:
+ g_value_set_enum (value, self->section_type);
+ break;
+
+ case PROP_PLACE_TYPE:
+ g_value_set_enum (value, self->place_type);
+ break;
+
+ case PROP_URI:
+ g_value_set_string (value, self->uri);
+ break;
+
+ case PROP_NAUTILUS_FILE:
+ g_value_set_object (value, self->file);
+ break;
+
+ case PROP_DRIVE:
+ g_value_set_object (value, self->drive);
+ break;
+
+ case PROP_VOLUME:
+ g_value_set_object (value, self->volume);
+ break;
+
+ case PROP_MOUNT:
+ g_value_set_object (value, self->mount);
+ break;
+
+ case PROP_CLOUD_PROVIDER_ACCOUNT:
+ g_value_set_object (value, self->cloud_provider_account);
+ break;
+
+ case PROP_PLACEHOLDER:
+ g_value_set_boolean (value, self->placeholder);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+nautilus_gtk_sidebar_row_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusGtkSidebarRow *self = NAUTILUS_GTK_SIDEBAR_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_SIDEBAR:
+ self->sidebar = g_value_get_object (value);
+ break;
+
+ case PROP_START_ICON:
+ {
+ g_clear_object (&self->start_icon);
+ object = g_value_get_object (value);
+ if (object != NULL)
+ {
+ self->start_icon = G_ICON (g_object_ref (object));
+ gtk_image_set_from_gicon (GTK_IMAGE (self->start_icon_widget), self->start_icon);
+ }
+ else
+ {
+ gtk_image_clear (GTK_IMAGE (self->start_icon_widget));
+ }
+ break;
+ }
+
+ case PROP_END_ICON:
+ {
+ g_clear_object (&self->end_icon);
+ object = g_value_get_object (value);
+ if (object != NULL)
+ {
+ self->end_icon = G_ICON (g_object_ref (object));
+ gtk_image_set_from_gicon (GTK_IMAGE (self->end_icon_widget), self->end_icon);
+ gtk_widget_show (self->end_icon_widget);
+ }
+ else
+ {
+ gtk_image_clear (GTK_IMAGE (self->end_icon_widget));
+ gtk_widget_hide (self->end_icon_widget);
+ }
+ break;
+ }
+
+ case PROP_LABEL:
+ g_free (self->label);
+ self->label = g_strdup (g_value_get_string (value));
+ gtk_label_set_text (GTK_LABEL (self->label_widget), self->label);
+ break;
+
+ case PROP_TOOLTIP:
+ g_free (self->tooltip);
+ self->tooltip = g_strdup (g_value_get_string (value));
+ gtk_widget_set_tooltip_text (GTK_WIDGET (self), self->tooltip);
+ break;
+
+ case PROP_EJECT_TOOLTIP:
+ g_free (self->eject_tooltip);
+ self->eject_tooltip = g_strdup (g_value_get_string (value));
+ gtk_widget_set_tooltip_text (GTK_WIDGET (self->eject_button), self->eject_tooltip);
+ break;
+
+ case PROP_EJECTABLE:
+ self->ejectable = g_value_get_boolean (value);
+ if (self->ejectable)
+ gtk_widget_show (self->eject_button);
+ else
+ gtk_widget_hide (self->eject_button);
+ break;
+
+ case PROP_ORDER_INDEX:
+ self->order_index = g_value_get_int (value);
+ break;
+
+ case PROP_SECTION_TYPE:
+ self->section_type = g_value_get_enum (value);
+ if (self->section_type == NAUTILUS_GTK_PLACES_SECTION_COMPUTER ||
+ self->section_type == NAUTILUS_GTK_PLACES_SECTION_OTHER_LOCATIONS)
+ gtk_label_set_ellipsize (GTK_LABEL (self->label_widget), PANGO_ELLIPSIZE_NONE);
+ else
+ gtk_label_set_ellipsize (GTK_LABEL (self->label_widget), PANGO_ELLIPSIZE_END);
+ break;
+
+ case PROP_PLACE_TYPE:
+ self->place_type = g_value_get_enum (value);
+ break;
+
+ case PROP_URI:
+ g_free (self->uri);
+ self->uri = g_strdup (g_value_get_string (value));
+ if (self->uri != NULL)
+ {
+ self->file = nautilus_file_get_by_uri (self->uri);
+ if (self->file != NULL)
+ nautilus_file_call_when_ready (self->file, NAUTILUS_FILE_ATTRIBUTE_MOUNT, NULL, NULL);
+ }
+ break;
+
+ case PROP_DRIVE:
+ g_set_object (&self->drive, g_value_get_object (value));
+ break;
+
+ case PROP_VOLUME:
+ g_set_object (&self->volume, g_value_get_object (value));
+ break;
+
+ case PROP_MOUNT:
+ g_set_object (&self->mount, g_value_get_object (value));
+ break;
+
+ case PROP_CLOUD_PROVIDER_ACCOUNT:
+ if (self->cloud_provider_account != NULL)
+ g_signal_handlers_disconnect_by_data (self->cloud_provider_account, self);
+
+ self->cloud_provider_account = g_value_dup_object (value);
+
+ if (self->cloud_provider_account != NULL)
+ {
+ g_signal_connect_swapped (self->cloud_provider_account, "notify::name",
+ G_CALLBACK (cloud_row_update), self);
+ g_signal_connect_swapped (self->cloud_provider_account, "notify::status",
+ G_CALLBACK (cloud_row_update), self);
+ g_signal_connect_swapped (self->cloud_provider_account, "notify::status-details",
+ G_CALLBACK (cloud_row_update), self);
+ }
+ break;
+
+ case PROP_PLACEHOLDER:
+ {
+ self->placeholder = g_value_get_boolean (value);
+ if (self->placeholder)
+ {
+ g_clear_object (&self->start_icon);
+ g_clear_object (&self->end_icon);
+ g_free (self->label);
+ self->label = NULL;
+ g_free (self->tooltip);
+ self->tooltip = NULL;
+ self->eject_tooltip = NULL;
+ gtk_widget_set_tooltip_text (GTK_WIDGET (self), NULL);
+ self->ejectable = FALSE;
+ self->section_type = NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS;
+ self->place_type = NAUTILUS_GTK_PLACES_BOOKMARK_PLACEHOLDER;
+ g_free (self->uri);
+ self->uri = NULL;
+ g_clear_object (&self->drive);
+ g_clear_object (&self->volume);
+ g_clear_object (&self->mount);
+ g_clear_object (&self->cloud_provider_account);
+
+ gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (self), NULL);
+
+ gtk_widget_add_css_class (GTK_WIDGET (self), "sidebar-placeholder-row");
+ }
+
+ break;
+ }
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+on_child_revealed (GObject *self,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ /* We need to hide the actual widget because if not the GtkListBoxRow will
+ * still allocate the paddings, even if the revealer is not revealed, and
+ * therefore the row will be still somewhat visible. */
+ if (!gtk_revealer_get_reveal_child (GTK_REVEALER (self)))
+ gtk_widget_hide (GTK_WIDGET (NAUTILUS_GTK_SIDEBAR_ROW (user_data)));
+}
+
+void
+nautilus_gtk_sidebar_row_reveal (NautilusGtkSidebarRow *self)
+{
+ gtk_widget_show (GTK_WIDGET (self));
+ gtk_revealer_set_reveal_child (GTK_REVEALER (self->revealer), TRUE);
+}
+
+void
+nautilus_gtk_sidebar_row_hide (NautilusGtkSidebarRow *self,
+ gboolean immediate)
+{
+ guint transition_duration;
+
+ transition_duration = gtk_revealer_get_transition_duration (GTK_REVEALER (self->revealer));
+ if (immediate)
+ gtk_revealer_set_transition_duration (GTK_REVEALER (self->revealer), 0);
+
+ gtk_revealer_set_reveal_child (GTK_REVEALER (self->revealer), FALSE);
+
+ gtk_revealer_set_transition_duration (GTK_REVEALER (self->revealer), transition_duration);
+}
+
+void
+nautilus_gtk_sidebar_row_set_start_icon (NautilusGtkSidebarRow *self,
+ GIcon *icon)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_SIDEBAR_ROW (self));
+
+ if (self->start_icon != icon)
+ {
+ g_set_object (&self->start_icon, icon);
+ if (self->start_icon != NULL)
+ gtk_image_set_from_gicon (GTK_IMAGE (self->start_icon_widget), self->start_icon);
+ else
+ gtk_image_clear (GTK_IMAGE (self->start_icon_widget));
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_START_ICON]);
+ }
+}
+
+void
+nautilus_gtk_sidebar_row_set_end_icon (NautilusGtkSidebarRow *self,
+ GIcon *icon)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_SIDEBAR_ROW (self));
+
+ if (self->end_icon != icon)
+ {
+ g_set_object (&self->end_icon, icon);
+ if (self->end_icon != NULL)
+ gtk_image_set_from_gicon (GTK_IMAGE (self->end_icon_widget), self->end_icon);
+ else
+ if (self->end_icon_widget != NULL)
+ gtk_image_clear (GTK_IMAGE (self->end_icon_widget));
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_END_ICON]);
+ }
+}
+
+static void
+nautilus_gtk_sidebar_row_finalize (GObject *object)
+{
+ NautilusGtkSidebarRow *self = NAUTILUS_GTK_SIDEBAR_ROW (object);
+
+ g_clear_object (&self->start_icon);
+ g_clear_object (&self->end_icon);
+ g_free (self->label);
+ self->label = NULL;
+ g_free (self->tooltip);
+ self->tooltip = NULL;
+ g_free (self->eject_tooltip);
+ self->eject_tooltip = NULL;
+ g_free (self->uri);
+ self->uri = NULL;
+ nautilus_file_unref (self->file);
+ g_clear_object (&self->drive);
+ g_clear_object (&self->volume);
+ g_clear_object (&self->mount);
+ if (self->cloud_provider_account != NULL)
+ g_signal_handlers_disconnect_by_data (self->cloud_provider_account, self);
+ g_clear_object (&self->cloud_provider_account);
+
+ G_OBJECT_CLASS (nautilus_gtk_sidebar_row_parent_class)->finalize (object);
+}
+
+static void
+nautilus_gtk_sidebar_row_init (NautilusGtkSidebarRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_widget_set_focus_on_click (GTK_WIDGET (self), FALSE);
+
+ self->file = NULL;
+}
+
+static void
+nautilus_gtk_sidebar_row_class_init (NautilusGtkSidebarRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->get_property = nautilus_gtk_sidebar_row_get_property;
+ object_class->set_property = nautilus_gtk_sidebar_row_set_property;
+ object_class->finalize = nautilus_gtk_sidebar_row_finalize;
+
+ properties [PROP_SIDEBAR] =
+ g_param_spec_object ("sidebar",
+ "Sidebar",
+ "Sidebar",
+ NAUTILUS_TYPE_GTK_PLACES_SIDEBAR,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_START_ICON] =
+ g_param_spec_object ("start-icon",
+ "start-icon",
+ "The start icon.",
+ G_TYPE_ICON,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_END_ICON] =
+ g_param_spec_object ("end-icon",
+ "end-icon",
+ "The end icon.",
+ G_TYPE_ICON,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_LABEL] =
+ g_param_spec_string ("label",
+ "label",
+ "The label text.",
+ NULL,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TOOLTIP] =
+ g_param_spec_string ("tooltip",
+ "Tooltip",
+ "Tooltip",
+ NULL,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_EJECT_TOOLTIP] =
+ g_param_spec_string ("eject-tooltip",
+ "Eject Tooltip",
+ "Eject Tooltip",
+ NULL,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_EJECTABLE] =
+ g_param_spec_boolean ("ejectable",
+ "Ejectable",
+ "Ejectable",
+ FALSE,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_ORDER_INDEX] =
+ g_param_spec_int ("order-index",
+ "OrderIndex",
+ "Order Index",
+ 0, G_MAXINT, 0,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_SECTION_TYPE] =
+ g_param_spec_enum ("section-type",
+ "section type",
+ "The section type.",
+ NAUTILUS_TYPE_GTK_PLACES_SECTION_TYPE,
+ NAUTILUS_GTK_PLACES_SECTION_INVALID,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ properties [PROP_PLACE_TYPE] =
+ g_param_spec_enum ("place-type",
+ "place type",
+ "The place type.",
+ NAUTILUS_TYPE_GTK_PLACES_PLACE_TYPE,
+ NAUTILUS_GTK_PLACES_INVALID,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ properties [PROP_URI] =
+ g_param_spec_string ("uri",
+ "Uri",
+ "Uri",
+ NULL,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_NAUTILUS_FILE] =
+ g_param_spec_object ("file",
+ "File",
+ "Nautilus File",
+ NAUTILUS_TYPE_FILE,
+ (G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS ));
+
+ properties [PROP_DRIVE] =
+ g_param_spec_object ("drive",
+ "Drive",
+ "Drive",
+ G_TYPE_DRIVE,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_VOLUME] =
+ g_param_spec_object ("volume",
+ "Volume",
+ "Volume",
+ G_TYPE_VOLUME,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_MOUNT] =
+ g_param_spec_object ("mount",
+ "Mount",
+ "Mount",
+ G_TYPE_MOUNT,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_CLOUD_PROVIDER_ACCOUNT] =
+ g_param_spec_object ("cloud-provider-account",
+ "CloudProvidersAccount",
+ "CloudProvidersAccount",
+ G_TYPE_OBJECT,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_PLACEHOLDER] =
+ g_param_spec_boolean ("placeholder",
+ "Placeholder",
+ "Placeholder",
+ FALSE,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/nautilus/gtk/ui/nautilusgtksidebarrow.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkSidebarRow, start_icon_widget);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkSidebarRow, end_icon_widget);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkSidebarRow, label_widget);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkSidebarRow, eject_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkSidebarRow, revealer);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkSidebarRow, busy_spinner);
+
+ gtk_widget_class_bind_template_callback (widget_class, on_child_revealed);
+ gtk_widget_class_set_css_name (widget_class, "row");
+}
+
+NautilusGtkSidebarRow*
+nautilus_gtk_sidebar_row_clone (NautilusGtkSidebarRow *self)
+{
+ return g_object_new (NAUTILUS_TYPE_GTK_SIDEBAR_ROW,
+ "sidebar", self->sidebar,
+ "start-icon", self->start_icon,
+ "end-icon", self->end_icon,
+ "label", self->label,
+ "tooltip", self->tooltip,
+ "eject-tooltip", self->eject_tooltip,
+ "ejectable", self->ejectable,
+ "order-index", self->order_index,
+ "section-type", self->section_type,
+ "place-type", self->place_type,
+ "uri", self->uri,
+ "file", self->file,
+ "drive", self->drive,
+ "volume", self->volume,
+ "mount", self->mount,
+ "cloud-provider-account", self->cloud_provider_account,
+ NULL);
+}
+
+GtkWidget*
+nautilus_gtk_sidebar_row_get_eject_button (NautilusGtkSidebarRow *self)
+{
+ return self->eject_button;
+}
+
+void
+nautilus_gtk_sidebar_row_set_busy (NautilusGtkSidebarRow *row,
+ gboolean is_busy)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_SIDEBAR_ROW (row));
+
+ gtk_widget_set_visible (row->busy_spinner, is_busy);
+}
diff --git a/src/gtk/nautilusgtksidebarrow.ui b/src/gtk/nautilusgtksidebarrow.ui
new file mode 100644
index 0000000..95d91f9
--- /dev/null
+++ b/src/gtk/nautilusgtksidebarrow.ui
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk40">
+ <template class="NautilusGtkSidebarRow" parent="GtkListBoxRow">
+ <property name="focus-on-click">0</property>
+ <style>
+ <class name="sidebar-row"/>
+ </style>
+ <property name="child">
+ <object class="GtkRevealer" id="revealer">
+ <property name="reveal-child">1</property>
+ <signal name="notify::child-revealed" handler="on_child_revealed"/>
+ <style>
+ <class name="sidebar-revealer"/>
+ </style>
+ <child>
+ <object class="GtkBox">
+ <child>
+ <object class="GtkImage" id="start_icon_widget">
+ <style>
+ <class name="sidebar-icon"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_widget">
+ <property name="hexpand">1</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="sidebar-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="end_icon_widget">
+ <property name="visible">0</property>
+ <property name="hexpand">1</property>
+ <property name="halign">2</property>
+ <property name="valign">3</property>
+ <style>
+ <class name="sidebar-icon"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="eject_button">
+ <property name="halign">3</property>
+ <property name="valign">3</property>
+ <property name="margin-start">4px</property>
+ <property name="icon-name">media-eject-symbolic</property>
+ <style>
+ <class name="sidebar-button"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinner" id="busy_spinner">
+ <property name="spinning">1</property>
+ <property name="halign">3</property>
+ <property name="valign">3</property>
+ <property name="margin-start">4px</property>
+ <property name="visible">0</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </template>
+</interface>
diff --git a/src/gtk/nautilusgtksidebarrowprivate.h b/src/gtk/nautilusgtksidebarrowprivate.h
new file mode 100644
index 0000000..0bd9355
--- /dev/null
+++ b/src/gtk/nautilusgtksidebarrowprivate.h
@@ -0,0 +1,60 @@
+/* nautilusgtksidebarrowprivate.h
+ *
+ * Copyright (C) 2015 Carlos Soriano <csoriano@gnome.org>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef NAUTILUS_GTK_SIDEBAR_ROW_PRIVATE_H
+#define NAUTILUS_GTK_SIDEBAR_ROW_PRIVATE_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_GTK_SIDEBAR_ROW (nautilus_gtk_sidebar_row_get_type())
+#define NAUTILUS_GTK_SIDEBAR_ROW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_GTK_SIDEBAR_ROW, NautilusGtkSidebarRow))
+#define NAUTILUS_GTK_SIDEBAR_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_GTK_SIDEBAR_ROW, NautilusGtkSidebarRowClass))
+#define NAUTILUS_IS_GTK_SIDEBAR_ROW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_GTK_SIDEBAR_ROW))
+#define NAUTILUS_IS_GTK_SIDEBAR_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_GTK_SIDEBAR_ROW))
+#define NAUTILUS_GTK_SIDEBAR_ROW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_GTK_SIDEBAR_ROW, NautilusGtkSidebarRowClass))
+
+typedef struct _NautilusGtkSidebarRow NautilusGtkSidebarRow;
+typedef struct _NautilusGtkSidebarRowClass NautilusGtkSidebarRowClass;
+
+struct _NautilusGtkSidebarRowClass
+{
+ GtkListBoxRowClass parent;
+};
+
+GType nautilus_gtk_sidebar_row_get_type (void) G_GNUC_CONST;
+
+NautilusGtkSidebarRow *nautilus_gtk_sidebar_row_new (void);
+NautilusGtkSidebarRow *nautilus_gtk_sidebar_row_clone (NautilusGtkSidebarRow *self);
+
+/* Use these methods instead of gtk_widget_hide/show to use an animation */
+void nautilus_gtk_sidebar_row_hide (NautilusGtkSidebarRow *self,
+ gboolean immediate);
+void nautilus_gtk_sidebar_row_reveal (NautilusGtkSidebarRow *self);
+
+GtkWidget *nautilus_gtk_sidebar_row_get_eject_button (NautilusGtkSidebarRow *self);
+void nautilus_gtk_sidebar_row_set_start_icon (NautilusGtkSidebarRow *self,
+ GIcon *icon);
+void nautilus_gtk_sidebar_row_set_end_icon (NautilusGtkSidebarRow *self,
+ GIcon *icon);
+void nautilus_gtk_sidebar_row_set_busy (NautilusGtkSidebarRow *row,
+ gboolean is_busy);
+
+G_END_DECLS
+
+#endif /* NAUTILUS_GTK_SIDEBAR_ROW_PRIVATE_H */