summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGeorges Basile Stavracas Neto <georges.stavracas@gmail.com>2015-08-18 15:23:02 -0300
committerGeorges Basile Stavracas Neto <georges.stavracas@gmail.com>2015-08-20 17:38:35 -0300
commit404f1492dbe03b407abd4ca0c2b743f7810f4b3e (patch)
treebabcbe37fa37648d8731acd20a19e0967ebe8cc9 /src
parentabef8cac2f1df4b808fcb66b35877b75b54774e4 (diff)
downloadnautilus-404f1492dbe03b407abd4ca0c2b743f7810f4b3e.tar.gz
places-view: implement a view for Other Locations
GtkFileChooser received a Other Locations view that lists persistent devices, as well as networks and the root location for the computer's hard drive. Since Nautilus is a file management tool too, it should keep consistency between Gtk+ file chooser, something that doesn't happen since it doesn't display Other Locations. To fix that, add NautilusPlacesView, a NautilusView implementation that displays the GtkPlacesView widget. In order to implement that, update window-slot to correctly display the places-view whenever Other Locations is clicked. https://bugzilla.gnome.org/show_bug.cgi?id=753871
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am6
-rw-r--r--src/gtk/gtkplacesview.c2436
-rw-r--r--src/gtk/gtkplacesview.ui332
-rw-r--r--src/gtk/gtkplacesviewprivate.h91
-rw-r--r--src/gtk/gtkplacesviewrow.c326
-rw-r--r--src/gtk/gtkplacesviewrow.ui109
-rw-r--r--src/gtk/gtkplacesviewrowprivate.h53
-rw-r--r--src/nautilus-application.c2
-rw-r--r--src/nautilus-pathbar.c23
-rw-r--r--src/nautilus-places-view.c359
-rw-r--r--src/nautilus-places-view.h37
-rw-r--r--src/nautilus-window-slot-dnd.c8
-rw-r--r--src/nautilus-window-slot.c222
-rw-r--r--src/nautilus-window.c15
-rw-r--r--src/nautilus-window.ui2
-rw-r--r--src/nautilus.gresource.xml2
16 files changed, 3917 insertions, 106 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 2b01996db..56972248b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -139,6 +139,10 @@ nautilus_built_sources = \
$(NULL)
nautilus_SOURCES = \
+ gtk/gtkplacesview.c \
+ gtk/gtkplacesviewprivate.h \
+ gtk/gtkplacesviewrow.c \
+ gtk/gtkplacesviewrowprivate.h \
nautilus-application.c \
nautilus-application.h \
nautilus-application-actions.c \
@@ -189,6 +193,8 @@ nautilus_SOURCES = \
nautilus-notebook.h \
nautilus-pathbar.c \
nautilus-pathbar.h \
+ nautilus-places-view.c \
+ nautilus-places-view.h \
nautilus-previewer.c \
nautilus-previewer.h \
nautilus-progress-info-widget.c \
diff --git a/src/gtk/gtkplacesview.c b/src/gtk/gtkplacesview.c
new file mode 100644
index 000000000..ca58d3afc
--- /dev/null
+++ b/src/gtk/gtkplacesview.c
@@ -0,0 +1,2436 @@
+/* gtkplacesview.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 General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include "gtkplacesviewprivate.h"
+#include "gtkplacesviewrowprivate.h"
+
+/**
+ * SECTION:gtkplacesview
+ * @Short_description: Widget that displays persistent drives and manages mounted networks
+ * @Title: GtkPlacesView
+ * @See_also: #GtkFileChooser
+ *
+ * #GtkPlacesView is a stock widget that displays a list of persistent drives
+ * such as harddisk partitions and networks. #GtkPlacesView 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 #GtkPlacesView::open-location signal. This is emitted when the user
+ * selects a location to open in the view.
+ */
+
+struct _GtkPlacesViewPrivate
+{
+ GVolumeMonitor *volume_monitor;
+ GtkPlacesOpenFlags open_flags;
+ GtkPlacesOpenFlags current_open_flags;
+
+ GFile *server_list_file;
+ GFileMonitor *server_list_monitor;
+
+ GCancellable *cancellable;
+
+ gchar *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 *network_header_spinner;
+ GtkWidget *network_placeholder;
+ GtkWidget *network_placeholder_label;
+
+ GtkEntryCompletion *address_entry_completion;
+ GtkListStore *completion_store;
+
+ GList *detected_networks;
+ GCancellable *networks_fetching_cancellable;
+
+ guint local_only : 1;
+ guint should_open_location : 1;
+ guint should_pulse_entry : 1;
+ guint entry_pulse_timeout_id;
+ guint connecting_to_server : 1;
+ guint fetching_networks : 1;
+ guint loading : 1;
+};
+
+static void mount_volume (GtkPlacesView *view,
+ GVolume *volume);
+
+static gboolean on_button_press_event (GtkPlacesViewRow *row,
+ GdkEventButton *event);
+
+static void on_eject_button_clicked (GtkWidget *widget,
+ GtkPlacesViewRow *row);
+
+static gboolean on_row_popup_menu (GtkPlacesViewRow *row);
+
+static void populate_servers (GtkPlacesView *view);
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtkPlacesView, gtk_places_view, GTK_TYPE_BOX)
+
+/* GtkPlacesView properties & signals */
+enum {
+ PROP_0,
+ PROP_LOCAL_ONLY,
+ PROP_OPEN_FLAGS,
+ PROP_LOADING,
+ LAST_PROP
+};
+
+enum {
+ OPEN_LOCATION,
+ SHOW_ERROR_MESSAGE,
+ LAST_SIGNAL
+};
+
+const gchar *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 (GtkPlacesView *view,
+ GFile *location,
+ GtkPlacesOpenFlags open_flags)
+{
+ GtkPlacesViewPrivate *priv;
+
+ priv = gtk_places_view_get_instance_private (view);
+
+ if ((open_flags & priv->open_flags) == 0)
+ open_flags = GTK_PLACES_OPEN_NORMAL;
+
+ g_signal_emit (view, places_view_signals[OPEN_LOCATION], 0, location, open_flags);
+}
+
+static void
+emit_show_error_message (GtkPlacesView *view,
+ gchar *primary_message,
+ gchar *secondary_message)
+{
+ g_signal_emit (view, places_view_signals[SHOW_ERROR_MESSAGE],
+ 0, primary_message, secondary_message);
+}
+
+static void
+server_file_changed_cb (GtkPlacesView *view)
+{
+ populate_servers (view);
+}
+
+static GBookmarkFile *
+server_list_load (GtkPlacesView *view)
+{
+ GtkPlacesViewPrivate *priv;
+ GBookmarkFile *bookmarks;
+ GError *error = NULL;
+ gchar *datadir;
+ gchar *filename;
+
+ priv = gtk_places_view_get_instance_private (view);
+ bookmarks = g_bookmark_file_new ();
+ datadir = g_build_filename (g_get_user_config_dir (), "gtk-3.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 (!priv->server_list_monitor)
+ {
+ priv->server_list_file = g_file_new_for_path (filename);
+
+ if (priv->server_list_file)
+ {
+ priv->server_list_monitor = g_file_monitor_file (priv->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 (priv->server_list_monitor,
+ "changed",
+ G_CALLBACK (server_file_changed_cb),
+ view);
+ }
+ }
+
+ g_clear_object (&priv->server_list_file);
+ }
+
+ g_free (datadir);
+ g_free (filename);
+
+ return bookmarks;
+}
+
+static void
+server_list_save (GBookmarkFile *bookmarks)
+{
+ gchar *filename;
+
+ filename = g_build_filename (g_get_user_config_dir (), "gtk-3.0", "servers", NULL);
+ g_bookmark_file_to_file (bookmarks, filename, NULL);
+ g_free (filename);
+}
+
+static void
+server_list_add_server (GtkPlacesView *view,
+ GFile *file)
+{
+ GBookmarkFile *bookmarks;
+ GFileInfo *info;
+ GError *error;
+ gchar *title;
+ gchar *uri;
+
+ 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);
+ g_bookmark_file_set_visited (bookmarks, uri, -1);
+ 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 (GtkPlacesView *view,
+ const gchar *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_get_toplevel (widget);
+ if (!gtk_widget_is_toplevel (toplevel))
+ return NULL;
+ else
+ return GTK_WINDOW (toplevel);
+}
+
+static void
+set_busy_cursor (GtkPlacesView *view,
+ gboolean busy)
+{
+ GtkWidget *widget;
+ GtkWindow *toplevel;
+ GdkDisplay *display;
+ GdkCursor *cursor;
+
+ toplevel = get_toplevel (GTK_WIDGET (view));
+ widget = GTK_WIDGET (toplevel);
+ if (!toplevel || !gtk_widget_get_realized (widget))
+ return;
+
+ display = gtk_widget_get_display (widget);
+
+ if (busy)
+ {
+ cursor = gdk_cursor_new_from_name (display, "left_ptr_watch");
+ if (cursor == NULL)
+ cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
+ }
+ else
+ cursor = NULL;
+
+ gdk_window_set_cursor (gtk_widget_get_window (widget), cursor);
+ gdk_display_flush (display);
+
+ if (cursor)
+ g_object_unref (cursor);
+}
+
+/* Activates the given row, with the given flags as parameter */
+static void
+activate_row (GtkPlacesView *view,
+ GtkPlacesViewRow *row,
+ GtkPlacesOpenFlags flags)
+{
+ GtkPlacesViewPrivate *priv;
+ GVolume *volume;
+ GMount *mount;
+ GFile *file;
+
+ priv = gtk_places_view_get_instance_private (view);
+ mount = gtk_places_view_row_get_mount (row);
+ volume = gtk_places_view_row_get_volume (row);
+ file = 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.
+ */
+ priv->should_open_location = TRUE;
+
+ gtk_places_view_row_set_busy (row, TRUE);
+ mount_volume (view, volume);
+ }
+}
+
+static void update_places (GtkPlacesView *view);
+
+static void
+gtk_places_view_finalize (GObject *object)
+{
+ GtkPlacesView *self = (GtkPlacesView *)object;
+ GtkPlacesViewPrivate *priv = gtk_places_view_get_instance_private (self);
+
+ g_signal_handlers_disconnect_by_func (priv->volume_monitor, update_places, object);
+
+ if (priv->entry_pulse_timeout_id > 0)
+ g_source_remove (priv->entry_pulse_timeout_id);
+
+ g_cancellable_cancel (priv->cancellable);
+ g_cancellable_cancel (priv->networks_fetching_cancellable);
+
+ g_clear_pointer (&priv->search_query, g_free);
+ g_clear_object (&priv->server_list_file);
+ g_clear_object (&priv->server_list_monitor);
+ g_clear_object (&priv->volume_monitor);
+ g_clear_object (&priv->cancellable);
+ g_clear_object (&priv->networks_fetching_cancellable);
+
+ G_OBJECT_CLASS (gtk_places_view_parent_class)->finalize (object);
+}
+
+static void
+gtk_places_view_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkPlacesView *self = GTK_PLACES_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_LOCAL_ONLY:
+ g_value_set_boolean (value, gtk_places_view_get_local_only (self));
+ break;
+
+ case PROP_LOADING:
+ g_value_set_boolean (value, gtk_places_view_get_loading (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtk_places_view_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkPlacesView *self = GTK_PLACES_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_LOCAL_ONLY:
+ gtk_places_view_set_local_only (self, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static gboolean
+is_removable_volume (GVolume *volume)
+{
+ gboolean is_removable;
+ GDrive *drive;
+ GMount *mount;
+ gchar *id;
+
+ drive = g_volume_get_drive (volume);
+ mount = g_volume_get_mount (volume);
+ id = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);
+
+ is_removable = g_volume_can_eject (volume);
+
+ /* NULL volume identifier only happens on removable devices */
+ is_removable |= !id;
+
+ if (drive)
+ is_removable |= g_drive_can_eject (drive);
+
+ if (mount)
+ is_removable |= (g_mount_can_eject (mount) && !g_mount_can_unmount (mount));
+
+ g_clear_object (&drive);
+ g_clear_object (&mount);
+ g_free (id);
+
+ return is_removable;
+}
+
+typedef struct
+{
+ gchar *uri;
+ GtkPlacesView *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 (GtkPlacesView *view)
+{
+ GtkPlacesViewPrivate *priv;
+ GBookmarkFile *server_list;
+ GList *children;
+ gchar **uris;
+ gsize num_uris;
+ gint i;
+
+ priv = gtk_places_view_get_instance_private (view);
+ 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 (priv->recent_servers_stack),
+ num_uris > 0 ? "list" : "empty");
+
+ if (!uris)
+ {
+ g_bookmark_file_free (server_list);
+ return;
+ }
+
+ /* clear previous items */
+ children = gtk_container_get_children (GTK_CONTAINER (priv->recent_servers_listbox));
+ g_list_free_full (children, (GDestroyNotify) gtk_widget_destroy);
+
+ gtk_list_store_clear (priv->completion_store);
+
+ for (i = 0; i < num_uris; i++)
+ {
+ RemoveServerData *data;
+ GtkTreeIter iter;
+ GtkWidget *row;
+ GtkWidget *grid;
+ GtkWidget *button;
+ GtkWidget *label;
+ gchar *name;
+ gchar *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 (priv->completion_store, &iter);
+ gtk_list_store_set (priv->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,
+ "border-width", 6,
+ 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_container_add (GTK_CONTAINER (grid), label);
+
+ /* 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_style_context_add_class (gtk_widget_get_style_context (label), "dim-label");
+ gtk_container_add (GTK_CONTAINER (grid), label);
+
+ /* remove button */
+ button = gtk_button_new ();
+ gtk_widget_set_halign (button, GTK_ALIGN_END);
+ gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ gtk_style_context_add_class (gtk_widget_get_style_context (button), "image-button");
+ gtk_style_context_add_class (gtk_widget_get_style_context (button), "sidebar-button");
+ gtk_grid_attach (GTK_GRID (grid), button, 1, 0, 1, 2);
+ gtk_container_add (GTK_CONTAINER (button),
+ gtk_image_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_BUTTON));
+
+ gtk_container_add (GTK_CONTAINER (row), grid);
+ gtk_container_add (GTK_CONTAINER (priv->recent_servers_listbox), row);
+
+ /* 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);
+
+ gtk_widget_show_all (row);
+
+ g_free (name);
+ }
+
+ g_strfreev (uris);
+ g_bookmark_file_free (server_list);
+}
+
+static void
+update_view_mode (GtkPlacesView *view)
+{
+ GtkPlacesViewPrivate *priv;
+ GList *children;
+ GList *l;
+ gboolean show_listbox;
+
+ priv = gtk_places_view_get_instance_private (view);
+ show_listbox = FALSE;
+
+ /* drives */
+ children = gtk_container_get_children (GTK_CONTAINER (priv->listbox));
+
+ for (l = children; l; l = l->next)
+ {
+ /* GtkListBox filter rows by changing their GtkWidget::child-visible property */
+ if (gtk_widget_get_child_visible (l->data))
+ {
+ show_listbox = TRUE;
+ break;
+ }
+ }
+
+ g_list_free (children);
+
+ if (!show_listbox &&
+ priv->search_query &&
+ priv->search_query[0] != '\0')
+ {
+ gtk_stack_set_visible_child_name (GTK_STACK (priv->stack), "empty-search");
+ }
+ else
+ {
+ gtk_stack_set_visible_child_name (GTK_STACK (priv->stack), "browse");
+ }
+}
+
+static void
+insert_row (GtkPlacesView *view,
+ GtkWidget *row,
+ gboolean is_network)
+{
+ GtkPlacesViewPrivate *priv;
+
+ priv = gtk_places_view_get_instance_private (view);
+
+ g_object_set_data (G_OBJECT (row), "is-network", GINT_TO_POINTER (is_network));
+
+ g_signal_connect_swapped (gtk_places_view_row_get_event_box (GTK_PLACES_VIEW_ROW (row)),
+ "button-press-event",
+ G_CALLBACK (on_button_press_event),
+ row);
+
+ g_signal_connect (row,
+ "popup-menu",
+ G_CALLBACK (on_row_popup_menu),
+ row);
+
+ g_signal_connect (gtk_places_view_row_get_eject_button (GTK_PLACES_VIEW_ROW (row)),
+ "clicked",
+ G_CALLBACK (on_eject_button_clicked),
+ row);
+
+ gtk_container_add (GTK_CONTAINER (priv->listbox), row);
+}
+
+static void
+add_volume (GtkPlacesView *view,
+ GVolume *volume)
+{
+ gboolean is_network;
+ GDrive *drive;
+ GMount *mount;
+ GFile *root;
+ GIcon *icon;
+ gchar *identifier;
+ gchar *name;
+ gchar *path;
+
+ if (is_removable_volume (volume))
+ return;
+
+ drive = g_volume_get_drive (volume);
+
+ if (drive)
+ {
+ gboolean is_removable;
+
+ is_removable = g_drive_is_media_removable (drive) ||
+ g_volume_can_eject (volume);
+ g_object_unref (drive);
+
+ if (is_removable)
+ 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 ||
+ (mount && !g_mount_is_shadowed (mount)))
+ {
+ GtkWidget *row;
+
+ row = g_object_new (GTK_TYPE_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 (GtkPlacesView *view,
+ GMount *mount)
+{
+ gboolean is_network;
+ GFile *root;
+ GIcon *icon;
+ gchar *name;
+ gchar *path;
+ gchar *uri;
+ gchar *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 (GTK_TYPE_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 (GtkPlacesView *view,
+ GDrive *drive)
+{
+ GList *volumes;
+ GList *l;
+
+ /* Removable devices won't appear here */
+ if (g_drive_can_eject (drive))
+ return;
+
+ 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 (GtkPlacesView *view,
+ GFile *file,
+ GIcon *icon,
+ const gchar *display_name,
+ const gchar *path,
+ gboolean is_network)
+{
+ GtkWidget *row;
+ row = g_object_new (GTK_TYPE_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 (GtkPlacesView *view)
+{
+ GList *l;
+ GtkPlacesViewPrivate *priv;
+ GList *children;
+ gboolean has_network = FALSE;
+
+ priv = gtk_places_view_get_instance_private (view);
+
+ children = gtk_container_get_children (GTK_CONTAINER (priv->listbox));
+ for (l = children; l != NULL; l = l->next)
+ {
+ if (GPOINTER_TO_INT (g_object_get_data (l->data, "is-network")) == TRUE &&
+ g_object_get_data (l->data, "is-placeholder") == NULL)
+ {
+ has_network = TRUE;
+ break;
+ }
+ }
+
+ g_list_free (children);
+
+ return has_network;
+}
+
+static void
+update_network_state (GtkPlacesView *view)
+{
+ GtkPlacesViewPrivate *priv;
+
+ priv = gtk_places_view_get_instance_private (view);
+
+ if (priv->network_placeholder == NULL)
+ {
+ priv->network_placeholder = gtk_list_box_row_new ();
+ priv->network_placeholder_label = gtk_label_new ("");
+ gtk_label_set_xalign (GTK_LABEL (priv->network_placeholder_label), 0.0);
+ gtk_widget_set_margin_start (priv->network_placeholder_label, 12);
+ gtk_widget_set_margin_end (priv->network_placeholder_label, 12);
+ gtk_widget_set_margin_top (priv->network_placeholder_label, 6);
+ gtk_widget_set_margin_bottom (priv->network_placeholder_label, 6);
+ gtk_widget_set_hexpand (priv->network_placeholder_label, TRUE);
+ gtk_widget_set_sensitive (priv->network_placeholder, FALSE);
+ gtk_container_add (GTK_CONTAINER (priv->network_placeholder),
+ priv->network_placeholder_label);
+ g_object_set_data (G_OBJECT (priv->network_placeholder),
+ "is-network", GINT_TO_POINTER (TRUE));
+ /* mark the row as placeholder, so it always goes first */
+ g_object_set_data (G_OBJECT (priv->network_placeholder),
+ "is-placeholder", GINT_TO_POINTER (TRUE));
+ gtk_container_add (GTK_CONTAINER (priv->listbox), priv->network_placeholder);
+
+ gtk_widget_show_all (GTK_WIDGET (priv->network_placeholder));
+ gtk_list_box_invalidate_headers (GTK_LIST_BOX (priv->listbox));
+ }
+
+ if (priv->fetching_networks)
+ {
+ gtk_spinner_start (GTK_SPINNER (priv->network_header_spinner));
+ /* 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_all (priv->network_placeholder);
+ gtk_label_set_text (GTK_LABEL (priv->network_placeholder_label),
+ _("Searching for network locations"));
+ }
+ }
+ else if (!has_networks (view))
+ {
+ gtk_spinner_stop (GTK_SPINNER (priv->network_header_spinner));
+ gtk_widget_show_all (priv->network_placeholder);
+ gtk_label_set_text (GTK_LABEL (priv->network_placeholder_label),
+ _("No networks locations found"));
+ }
+ else
+ {
+ gtk_spinner_stop (GTK_SPINNER (priv->network_header_spinner));
+ gtk_widget_hide (priv->network_placeholder);
+ }
+}
+
+static void
+populate_networks (GtkPlacesView *view,
+ GFileEnumerator *enumerator)
+{
+ GList *l;
+ GtkPlacesViewPrivate *priv;
+ GFile *file;
+ GFile *activatable_file;
+ gchar *uri;
+ GFileType type;
+ GIcon *icon;
+ gchar *display_name;
+
+
+ priv = gtk_places_view_get_instance_private (view);
+
+ for (l = priv->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);
+ }
+
+ g_clear_object (&enumerator);
+}
+
+static void
+network_enumeration_next_files_finished (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GtkPlacesViewPrivate *priv;
+ GtkPlacesView *view;
+ GError *error;
+
+ view = GTK_PLACES_VIEW (user_data);
+ priv = gtk_places_view_get_instance_private (view);
+ error = NULL;
+
+ /* clean previous fetched networks */
+ g_list_free_full (priv->detected_networks, g_object_unref);
+ priv->fetching_networks = FALSE;
+
+ priv->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_warning ("Failed to fetch network locations: %s", error->message);
+
+ g_clear_error (&error);
+ g_clear_object (&source_object);
+ }
+ else
+ {
+ populate_networks (view, G_FILE_ENUMERATOR (source_object));
+ }
+
+ /* avoid to update widgets if the operation was cancelled in finalize */
+ if (priv->listbox != NULL)
+ update_network_state (view);
+
+ priv->loading = FALSE;
+ g_object_notify_by_pspec (G_OBJECT (view), properties[PROP_LOADING]);
+}
+
+static void
+network_enumeration_finished (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GtkPlacesViewPrivate *priv;
+ GFileEnumerator *enumerator;
+ GtkPlacesView *view;
+ GError *error;
+
+ view = GTK_PLACES_VIEW (user_data);
+ priv = gtk_places_view_get_instance_private (view);
+ error = NULL;
+ enumerator = g_file_enumerate_children_finish (G_FILE (source_object), res, &error);
+
+ if (error)
+ {
+ if (error->code != G_IO_ERROR_CANCELLED)
+ g_warning ("Failed to fetch network locations: %s", error->message);
+
+ g_clear_error (&error);
+ }
+ else
+ {
+ g_file_enumerator_next_files_async (enumerator,
+ G_MAXINT32,
+ G_PRIORITY_DEFAULT,
+ priv->networks_fetching_cancellable,
+ network_enumeration_next_files_finished,
+ user_data);
+ }
+}
+
+static void
+fetch_networks (GtkPlacesView *view)
+{
+ GtkPlacesViewPrivate *priv;
+ GFile *network_file;
+
+ priv = gtk_places_view_get_instance_private (view);
+ network_file = g_file_new_for_uri ("network:///");
+
+ g_cancellable_cancel (priv->networks_fetching_cancellable);
+ g_clear_object (&priv->networks_fetching_cancellable);
+ priv->networks_fetching_cancellable = g_cancellable_new ();
+ priv->fetching_networks = TRUE;
+
+ update_network_state (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,
+ priv->networks_fetching_cancellable,
+ network_enumeration_finished,
+ view);
+
+ g_clear_object (&network_file);
+}
+
+static void
+update_places (GtkPlacesView *view)
+{
+ GtkPlacesViewPrivate *priv;
+ GList *children;
+ GList *mounts;
+ GList *volumes;
+ GList *drives;
+ GList *l;
+ GIcon *icon;
+ GFile *file;
+
+ priv = gtk_places_view_get_instance_private (view);
+
+ /* Clear all previously added items */
+ children = gtk_container_get_children (GTK_CONTAINER (priv->listbox));
+ g_list_free_full (children, (GDestroyNotify) gtk_widget_destroy);
+ priv->network_placeholder = NULL;
+ priv->network_header_spinner = NULL;
+
+ priv->loading = TRUE;
+ g_object_notify_by_pspec (G_OBJECT (view), properties[PROP_LOADING]);
+
+ /* Add "Computer" row */
+ file = g_file_new_for_path ("/");
+ icon = g_themed_icon_new_with_default_fallbacks ("drive-harddisk");
+
+ add_file (view, file, icon, _("Computer"), "/", FALSE);
+
+ g_clear_object (&file);
+ g_clear_object (&icon);
+
+ /* Add currently connected drives */
+ drives = g_volume_monitor_get_connected_drives (priv->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 (priv->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 (priv->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);
+}
+
+static void
+server_mount_ready_cb (GObject *source_file,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GtkPlacesViewPrivate *priv;
+ GtkPlacesView *view;
+ gboolean should_show;
+ GError *error;
+ GFile *location;
+
+ view = GTK_PLACES_VIEW (user_data);
+ priv = gtk_places_view_get_instance_private (view);
+ location = G_FILE (source_file);
+ should_show = TRUE;
+ error = NULL;
+
+ priv->should_pulse_entry = FALSE;
+ set_busy_cursor (view, FALSE);
+
+ g_file_mount_enclosing_volume_finish (location, res, &error);
+ /* Restore from Cancel to Connect */
+ gtk_button_set_label (GTK_BUTTON (priv->connect_button), _("Con_nect"));
+ gtk_widget_set_sensitive (priv->address_entry, TRUE);
+ priv->connecting_to_server = FALSE;
+
+ 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);
+ should_show = FALSE;
+ }
+
+ g_clear_error (&error);
+ }
+
+ 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_entry_set_text (GTK_ENTRY (priv->address_entry), "");
+
+ if (priv->should_open_location)
+ {
+ GMount *mount_point;
+ GError *error;
+ GFile *enclosing_location;
+
+ error = NULL;
+ mount_point = g_file_find_enclosing_mount (location, NULL, &error);
+
+ if (error)
+ {
+ emit_show_error_message (view, _("Unable to access location"), error->message);
+ g_clear_error (&error);
+ goto out;
+ }
+
+ enclosing_location = g_mount_get_default_location (mount_point);
+
+ emit_open_location (view, enclosing_location, priv->open_flags);
+
+ g_object_unref (enclosing_location);
+ }
+ }
+
+out:
+ update_places (view);
+}
+
+static void
+volume_mount_ready_cb (GObject *source_volume,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GtkPlacesViewPrivate *priv;
+ GtkPlacesView *view;
+ gboolean should_show;
+ GVolume *volume;
+ GError *error;
+
+ view = GTK_PLACES_VIEW (user_data);
+ priv = gtk_places_view_get_instance_private (view);
+ volume = G_VOLUME (source_volume);
+ should_show = TRUE;
+ error = NULL;
+
+ set_busy_cursor (view, FALSE);
+
+ 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 (GTK_PLACES_VIEW (user_data), _("Unable to access location"), error->message);
+ should_show = FALSE;
+ }
+
+ g_clear_error (&error);
+ }
+
+ if (should_show)
+ {
+ GMount *mount;
+ GFile *root;
+
+ mount = g_volume_get_mount (volume);
+ root = g_mount_get_default_location (mount);
+
+ if (priv->should_open_location)
+ emit_open_location (GTK_PLACES_VIEW (user_data), root, priv->open_flags);
+
+ g_object_unref (mount);
+ g_object_unref (root);
+ }
+
+ update_places (view);
+}
+
+static void
+unmount_ready_cb (GObject *source_mount,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GtkPlacesView *view;
+ GMount *mount;
+ GError *error;
+
+ view = GTK_PLACES_VIEW (user_data);
+ mount = G_MOUNT (source_mount);
+ error = NULL;
+
+ set_busy_cursor (view, FALSE);
+
+ 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);
+ }
+}
+
+static gboolean
+pulse_entry_cb (gpointer user_data)
+{
+ GtkPlacesViewPrivate *priv;
+
+ priv = gtk_places_view_get_instance_private (GTK_PLACES_VIEW (user_data));
+
+ if (priv->should_pulse_entry)
+ {
+ gtk_entry_progress_pulse (GTK_ENTRY (priv->address_entry));
+
+ return G_SOURCE_CONTINUE;
+ }
+ else
+ {
+ gtk_entry_set_progress_pulse_step (GTK_ENTRY (priv->address_entry), 0.0);
+ gtk_entry_set_progress_fraction (GTK_ENTRY (priv->address_entry), 0.0);
+ priv->entry_pulse_timeout_id = 0;
+
+ return G_SOURCE_REMOVE;
+ }
+}
+
+static void
+unmount_mount (GtkPlacesView *view,
+ GMount *mount)
+{
+ GtkPlacesViewPrivate *priv;
+ GMountOperation *operation;
+ GtkWidget *toplevel;
+
+ priv = gtk_places_view_get_instance_private (view);
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ operation = gtk_mount_operation_new (GTK_WINDOW (toplevel));
+
+ set_busy_cursor (GTK_PLACES_VIEW (view), TRUE);
+
+ g_cancellable_cancel (priv->cancellable);
+ g_clear_object (&priv->cancellable);
+ priv->cancellable = g_cancellable_new ();
+
+ operation = gtk_mount_operation_new (GTK_WINDOW (toplevel));
+ g_mount_unmount_with_operation (mount,
+ 0,
+ operation,
+ priv->cancellable,
+ unmount_ready_cb,
+ view);
+ g_object_unref (operation);
+}
+
+static void
+mount_server (GtkPlacesView *view,
+ GFile *location)
+{
+ GtkPlacesViewPrivate *priv;
+ GMountOperation *operation;
+ GtkWidget *toplevel;
+
+ priv = gtk_places_view_get_instance_private (view);
+
+ g_cancellable_cancel (priv->cancellable);
+ g_clear_object (&priv->cancellable);
+ /* User cliked when the operation was ongoing, so wanted to cancel it */
+ if (priv->connecting_to_server)
+ return;
+
+ priv->cancellable = g_cancellable_new ();
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ operation = gtk_mount_operation_new (GTK_WINDOW (toplevel));
+
+ set_busy_cursor (view, TRUE);
+ priv->should_pulse_entry = TRUE;
+ gtk_entry_set_progress_pulse_step (GTK_ENTRY (priv->address_entry), 0.1);
+ /* Allow to cancel the operation */
+ gtk_button_set_label (GTK_BUTTON (priv->connect_button), _("Cance_l"));
+ gtk_widget_set_sensitive (priv->address_entry, FALSE);
+ priv->connecting_to_server = TRUE;
+
+ if (priv->entry_pulse_timeout_id == 0)
+ priv->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);
+
+ g_file_mount_enclosing_volume (location,
+ 0,
+ operation,
+ priv->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 (GtkPlacesView *view,
+ GVolume *volume)
+{
+ GtkPlacesViewPrivate *priv;
+ GMountOperation *operation;
+ GtkWidget *toplevel;
+
+ priv = gtk_places_view_get_instance_private (view);
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ operation = gtk_mount_operation_new (GTK_WINDOW (toplevel));
+
+ set_busy_cursor (view, TRUE);
+
+ g_cancellable_cancel (priv->cancellable);
+ g_clear_object (&priv->cancellable);
+ priv->cancellable = g_cancellable_new ();
+
+ g_mount_operation_set_password_save (operation, G_PASSWORD_SAVE_FOR_SESSION);
+
+ g_volume_mount (volume,
+ 0,
+ operation,
+ priv->cancellable,
+ volume_mount_ready_cb,
+ view);
+
+ /* unref operation here - g_file_mount_enclosing_volume() does ref for itself */
+ g_object_unref (operation);
+}
+
+/* Callback used when the file list's popup menu is detached */
+static void
+popup_menu_detach_cb (GtkWidget *attach_widget,
+ GtkMenu *menu)
+{
+ GtkPlacesViewPrivate *priv;
+
+ priv = gtk_places_view_get_instance_private (GTK_PLACES_VIEW (attach_widget));
+ priv->popup_menu = NULL;
+}
+
+static void
+get_view_and_file (GtkPlacesViewRow *row,
+ GtkWidget **view,
+ GFile **file)
+{
+ if (view)
+ *view = gtk_widget_get_ancestor (GTK_WIDGET (row), GTK_TYPE_PLACES_VIEW);
+
+ if (file)
+ {
+ GVolume *volume;
+ GMount *mount;
+
+ volume = gtk_places_view_row_get_volume (row);
+ mount = gtk_places_view_row_get_mount (row);
+
+ if (mount)
+ *file = g_mount_get_default_location (mount);
+ else if (volume)
+ *file = g_volume_get_activation_root (volume);
+ else
+ *file = NULL;
+ }
+}
+
+static void
+open_cb (GtkMenuItem *item,
+ GtkPlacesViewRow *row)
+{
+ GtkWidget *view;
+ GFile *file;
+
+ get_view_and_file (row, &view, &file);
+
+ if (file)
+ emit_open_location (GTK_PLACES_VIEW (view), file, GTK_PLACES_OPEN_NORMAL);
+
+ g_clear_object (&file);
+}
+
+static void
+open_in_new_tab_cb (GtkMenuItem *item,
+ GtkPlacesViewRow *row)
+{
+ GtkWidget *view;
+ GFile *file;
+
+ get_view_and_file (row, &view, &file);
+
+ if (file)
+ emit_open_location (GTK_PLACES_VIEW (view), file, GTK_PLACES_OPEN_NEW_TAB);
+
+ g_clear_object (&file);
+}
+
+static void
+open_in_new_window_cb (GtkMenuItem *item,
+ GtkPlacesViewRow *row)
+{
+ GtkWidget *view;
+ GFile *file;
+
+ get_view_and_file (row, &view, &file);
+
+ if (file)
+ emit_open_location (GTK_PLACES_VIEW (view), file, GTK_PLACES_OPEN_NEW_WINDOW);
+
+ g_clear_object (&file);
+}
+
+static void
+mount_cb (GtkMenuItem *item,
+ GtkPlacesViewRow *row)
+{
+ GtkPlacesViewPrivate *priv;
+ GtkWidget *view;
+ GVolume *volume;
+
+ view = gtk_widget_get_ancestor (GTK_WIDGET (row), GTK_TYPE_PLACES_VIEW);
+ priv = gtk_places_view_get_instance_private (GTK_PLACES_VIEW (view));
+ volume = gtk_places_view_row_get_volume (row);
+
+ /*
+ * When the mount item is activated, it's expected that
+ * the volume only gets mounted, without opening it after
+ * the operation is complete.
+ */
+ priv->should_open_location = FALSE;
+
+ gtk_places_view_row_set_busy (row, TRUE);
+ mount_volume (GTK_PLACES_VIEW (view), volume);
+}
+
+static void
+unmount_cb (GtkMenuItem *item,
+ GtkPlacesViewRow *row)
+{
+ GtkWidget *view;
+ GMount *mount;
+
+ view = gtk_widget_get_ancestor (GTK_WIDGET (row), GTK_TYPE_PLACES_VIEW);
+ mount = gtk_places_view_row_get_mount (row);
+
+ gtk_places_view_row_set_busy (row, TRUE);
+
+ unmount_mount (GTK_PLACES_VIEW (view), mount);
+}
+
+/* Constructs the popup menu if needed */
+static void
+build_popup_menu (GtkPlacesView *view,
+ GtkPlacesViewRow *row)
+{
+ GtkPlacesViewPrivate *priv;
+ GtkWidget *item;
+ GMount *mount;
+ GFile *file;
+ gboolean is_network;
+
+ priv = gtk_places_view_get_instance_private (view);
+ mount = gtk_places_view_row_get_mount (row);
+ file = gtk_places_view_row_get_file (row);
+ is_network = gtk_places_view_row_get_is_network (row);
+
+ priv->popup_menu = gtk_menu_new ();
+ gtk_style_context_add_class (gtk_widget_get_style_context (priv->popup_menu),
+ GTK_STYLE_CLASS_CONTEXT_MENU);
+
+ gtk_menu_attach_to_widget (GTK_MENU (priv->popup_menu),
+ GTK_WIDGET (view),
+ popup_menu_detach_cb);
+
+ /* Open item is always present */
+ item = gtk_menu_item_new_with_mnemonic (_("_Open"));
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (open_cb),
+ row);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+
+ if (priv->open_flags & GTK_PLACES_OPEN_NEW_TAB)
+ {
+ item = gtk_menu_item_new_with_mnemonic (_("Open in New _Tab"));
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (open_in_new_tab_cb),
+ row);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+ }
+
+ if (priv->open_flags & GTK_PLACES_OPEN_NEW_WINDOW)
+ {
+ item = gtk_menu_item_new_with_mnemonic (_("Open in New _Window"));
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (open_in_new_window_cb),
+ row);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+ }
+
+ /*
+ * The only item that contains a file up to now is the Computer
+ * item, which cannot be mounted or unmounted.
+ */
+ if (file)
+ return;
+
+ /* Separator */
+ item = gtk_separator_menu_item_new ();
+ gtk_widget_show (item);
+ gtk_menu_shell_insert (GTK_MENU_SHELL (priv->popup_menu), item, -1);
+
+ /* Mount/Unmount items */
+ if (mount)
+ {
+ item = gtk_menu_item_new_with_mnemonic (is_network ? _("_Disconnect") : _("_Unmount"));
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (unmount_cb),
+ row);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+ }
+ else
+ {
+ item = gtk_menu_item_new_with_mnemonic (is_network ? _("_Connect") : _("_Mount"));
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (mount_cb),
+ row);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+ }
+}
+
+static void
+popup_menu (GtkPlacesViewRow *row,
+ GdkEventButton *event)
+{
+ GtkPlacesViewPrivate *priv;
+ GtkWidget *view;
+ gint button;
+
+ view = gtk_widget_get_ancestor (GTK_WIDGET (row), GTK_TYPE_PLACES_VIEW);
+ priv = gtk_places_view_get_instance_private (GTK_PLACES_VIEW (view));
+
+ g_clear_pointer (&priv->popup_menu, gtk_widget_destroy);
+
+ build_popup_menu (GTK_PLACES_VIEW (view), row);
+
+ /* The event button needs to be 0 if we're popping up this menu from
+ * a button release, else a 2nd click outside the menu with any button
+ * other than the one that invoked the menu will be ignored (instead
+ * of dismissing the menu). This is a subtle fragility of the GTK menu code.
+ */
+ if (event)
+ {
+ if (event->type == GDK_BUTTON_PRESS)
+ button = 0;
+ else
+ button = event->button;
+ }
+ else
+ {
+ button = 0;
+ }
+
+ gtk_menu_popup (GTK_MENU (priv->popup_menu),
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ button,
+ event ? event->time : gtk_get_current_event_time ());
+}
+
+static gboolean
+on_row_popup_menu (GtkPlacesViewRow *row)
+{
+ popup_menu (row, NULL);
+ return TRUE;
+}
+
+static gboolean
+on_button_press_event (GtkPlacesViewRow *row,
+ GdkEventButton *event)
+{
+ if (row &&
+ gdk_event_triggers_context_menu ((GdkEvent*) event) &&
+ event->type == GDK_BUTTON_PRESS)
+ {
+ popup_menu (row, event);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+on_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ GtkPlacesView *view)
+{
+ GtkPlacesViewPrivate *priv;
+
+ priv = gtk_places_view_get_instance_private (view);
+
+ if (event)
+ {
+ guint modifiers;
+
+ modifiers = gtk_accelerator_get_default_mod_mask ();
+
+ if (event->keyval == GDK_KEY_Return ||
+ event->keyval == GDK_KEY_KP_Enter ||
+ event->keyval == GDK_KEY_ISO_Enter ||
+ event->keyval == GDK_KEY_space)
+ {
+ GtkWidget *focus_widget;
+ GtkWindow *toplevel;
+
+ priv->current_open_flags = GTK_PLACES_OPEN_NORMAL;
+ toplevel = get_toplevel (GTK_WIDGET (view));
+
+ if (!toplevel)
+ return FALSE;
+
+ focus_widget = gtk_window_get_focus (toplevel);
+
+ if (!GTK_IS_PLACES_VIEW_ROW (focus_widget))
+ return FALSE;
+
+ if ((event->state & modifiers) == GDK_SHIFT_MASK)
+ priv->current_open_flags = GTK_PLACES_OPEN_NEW_TAB;
+ else if ((event->state & modifiers) == GDK_CONTROL_MASK)
+ priv->current_open_flags = GTK_PLACES_OPEN_NEW_WINDOW;
+
+ activate_row (view, GTK_PLACES_VIEW_ROW (focus_widget), priv->current_open_flags);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+on_eject_button_clicked (GtkWidget *widget,
+ GtkPlacesViewRow *row)
+{
+ if (row)
+ {
+ GtkWidget *view = gtk_widget_get_ancestor (GTK_WIDGET (row), GTK_TYPE_PLACES_VIEW);
+
+ unmount_mount (GTK_PLACES_VIEW (view), gtk_places_view_row_get_mount (row));
+ }
+}
+
+static void
+on_connect_button_clicked (GtkPlacesView *view)
+{
+ GtkPlacesViewPrivate *priv;
+ const gchar *uri;
+ GFile *file;
+
+ priv = gtk_places_view_get_instance_private (view);
+ 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 (priv->connect_button))
+ return;
+
+ uri = gtk_entry_get_text (GTK_ENTRY (priv->address_entry));
+
+ if (uri != NULL && uri[0] != '\0')
+ file = g_file_new_for_commandline_arg (uri);
+
+ if (file)
+ {
+ priv->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 (GtkPlacesView *view)
+{
+ GtkPlacesViewPrivate *priv;
+ const gchar* const *supported_protocols;
+ gchar *address, *scheme;
+ gboolean supported;
+
+ priv = gtk_places_view_get_instance_private (view);
+ supported = FALSE;
+ supported_protocols = g_vfs_get_supported_uri_schemes (g_vfs_get_default ());
+
+ if (!supported_protocols)
+ return;
+
+ address = g_strdup (gtk_entry_get_text (GTK_ENTRY (priv->address_entry)));
+ scheme = g_uri_parse_scheme (address);
+
+ if (!scheme)
+ goto out;
+
+ supported = g_strv_contains (supported_protocols, scheme) &&
+ !g_strv_contains (unsupported_protocols, scheme);
+
+out:
+ gtk_widget_set_sensitive (priv->connect_button, supported);
+ g_free (address);
+}
+
+static void
+on_recent_servers_listbox_row_activated (GtkPlacesView *view,
+ GtkPlacesViewRow *row,
+ GtkWidget *listbox)
+{
+ GtkPlacesViewPrivate *priv;
+ gchar *uri;
+
+ priv = gtk_places_view_get_instance_private (view);
+ uri = g_object_get_data (G_OBJECT (row), "uri");
+
+ gtk_entry_set_text (GTK_ENTRY (priv->address_entry), uri);
+
+ gtk_widget_hide (priv->recent_servers_popover);
+}
+
+static void
+on_listbox_row_activated (GtkPlacesView *view,
+ GtkPlacesViewRow *row,
+ GtkWidget *listbox)
+{
+ GtkPlacesViewPrivate *priv;
+
+ priv = gtk_places_view_get_instance_private (view);
+
+ activate_row (view, row, priv->current_open_flags);
+}
+
+static gboolean
+listbox_filter_func (GtkListBoxRow *row,
+ gpointer user_data)
+{
+ GtkPlacesViewPrivate *priv;
+ gboolean is_network;
+ gboolean is_placeholder;
+ gboolean retval;
+ gchar *name;
+ gchar *path;
+
+ priv = gtk_places_view_get_instance_private (GTK_PLACES_VIEW (user_data));
+ retval = FALSE;
+
+ is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "is-network"));
+ is_placeholder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "is-placeholder"));
+
+ if (is_network && priv->local_only)
+ return FALSE;
+
+ if (is_placeholder)
+ return FALSE;
+
+ if (!priv->search_query || priv->search_query[0] == '\0')
+ return TRUE;
+
+ g_object_get (row,
+ "name", &name,
+ "path", &path,
+ NULL);
+
+ if (name)
+ retval |= strstr (name, priv->search_query) != NULL;
+
+ if (path)
+ retval |= strstr (path, priv->search_query) != NULL;
+
+ g_free (name);
+ g_free (path);
+
+ return retval;
+}
+
+static void
+listbox_header_func (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ gpointer user_data)
+{
+ GtkPlacesViewPrivate *priv;
+ gboolean row_is_network;
+ gchar *text;
+
+ priv = gtk_places_view_get_instance_private (GTK_PLACES_VIEW (user_data));
+ 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);
+
+ separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+
+ label = g_object_new (GTK_TYPE_LABEL,
+ "use_markup", TRUE,
+ "margin-top", 6,
+ "margin-start", 12,
+ "label", text,
+ "xalign", 0.0f,
+ NULL);
+ if (row_is_network)
+ {
+ GtkWidget *header_name;
+
+ g_object_set (label,
+ "margin-end", 6,
+ NULL);
+
+ header_name = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ priv->network_header_spinner = gtk_spinner_new ();
+ g_object_set (priv->network_header_spinner,
+ "margin-end", 12,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (header_name), label);
+ gtk_container_add (GTK_CONTAINER (header_name), priv->network_header_spinner);
+ gtk_container_add (GTK_CONTAINER (header), header_name);
+ }
+ else
+ {
+ g_object_set (label,
+ "hexpand", TRUE,
+ "margin-end", 12,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (header), label);
+ }
+
+ gtk_container_add (GTK_CONTAINER (header), separator);
+ gtk_widget_show_all (header);
+
+ gtk_list_box_row_set_header (row, header);
+
+ g_free (text);
+ }
+ else
+ {
+ gtk_list_box_row_set_header (row, NULL);
+ }
+}
+
+static gint
+listbox_sort_func (GtkListBoxRow *row1,
+ GtkListBoxRow *row2,
+ gpointer user_data)
+{
+ gboolean row1_is_network;
+ gboolean row2_is_network;
+ gchar *location1;
+ gchar *location2;
+ gboolean *is_placeholder1;
+ gboolean *is_placeholder2;
+ gint 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", &location1, NULL);
+ g_object_get (row2, "path", &location2, NULL);
+
+ retval = g_strcmp0 (location1, location2);
+
+ g_free (location1);
+ g_free (location2);
+
+ return retval;
+}
+
+static void
+gtk_places_view_constructed (GObject *object)
+{
+ GtkPlacesViewPrivate *priv;
+
+ priv = gtk_places_view_get_instance_private (GTK_PLACES_VIEW (object));
+
+ G_OBJECT_CLASS (gtk_places_view_parent_class)->constructed (object);
+
+ gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->listbox),
+ listbox_sort_func,
+ object,
+ NULL);
+ gtk_list_box_set_filter_func (GTK_LIST_BOX (priv->listbox),
+ listbox_filter_func,
+ object,
+ NULL);
+ gtk_list_box_set_header_func (GTK_LIST_BOX (priv->listbox),
+ listbox_header_func,
+ object,
+ NULL);
+
+ /* load drives */
+ update_places (GTK_PLACES_VIEW (object));
+
+ g_signal_connect_swapped (priv->volume_monitor,
+ "mount-added",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (priv->volume_monitor,
+ "mount-changed",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (priv->volume_monitor,
+ "mount-removed",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (priv->volume_monitor,
+ "volume-added",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (priv->volume_monitor,
+ "volume-changed",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (priv->volume_monitor,
+ "volume-removed",
+ G_CALLBACK (update_places),
+ object);
+}
+
+static void
+gtk_places_view_map (GtkWidget *widget)
+{
+ GtkPlacesViewPrivate *priv;
+
+ priv = gtk_places_view_get_instance_private (GTK_PLACES_VIEW (widget));
+
+ gtk_entry_set_text (GTK_ENTRY (priv->address_entry), "");
+
+ GTK_WIDGET_CLASS (gtk_places_view_parent_class)->map (widget);
+}
+
+static void
+gtk_places_view_class_init (GtkPlacesViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = gtk_places_view_finalize;
+ object_class->constructed = gtk_places_view_constructed;
+ object_class->get_property = gtk_places_view_get_property;
+ object_class->set_property = gtk_places_view_set_property;
+
+ widget_class->map = gtk_places_view_map;
+
+ /**
+ * GtkPlacesView::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 #GtkPlacesOpenFlags 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.
+ *
+ * Since: 3.18
+ */
+ places_view_signals [OPEN_LOCATION] =
+ g_signal_new (_("open-location"),
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GtkPlacesViewClass, open_location),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2,
+ G_TYPE_OBJECT,
+ GTK_TYPE_PLACES_OPEN_FLAGS);
+
+ /**
+ * GtkPlacesView::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.
+ *
+ * Since: 3.18
+ */
+ 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 (GtkPlacesViewClass, show_error_message),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ properties[PROP_LOCAL_ONLY] =
+ g_param_spec_boolean ("local-only",
+ _("Local Only"),
+ _("Whether the sidebar only includes local files"),
+ FALSE,
+ G_PARAM_READWRITE);
+
+ properties[PROP_LOADING] =
+ g_param_spec_boolean ("loading",
+ _("Loading"),
+ _("Whether the view is loading locations"),
+ FALSE,
+ G_PARAM_READABLE);
+
+ 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"),
+ GTK_TYPE_PLACES_OPEN_FLAGS,
+ GTK_PLACES_OPEN_NORMAL,
+ G_PARAM_READWRITE);
+
+ 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/gtkplacesview.ui");
+
+ gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, actionbar);
+ gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, address_entry);
+ gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, address_entry_completion);
+ gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, completion_store);
+ gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, connect_button);
+ gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, listbox);
+ gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, recent_servers_listbox);
+ gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, recent_servers_popover);
+ gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, recent_servers_stack);
+ gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, stack);
+
+ gtk_widget_class_bind_template_callback (widget_class, on_address_entry_text_changed);
+ gtk_widget_class_bind_template_callback (widget_class, on_connect_button_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, on_key_press_event);
+ 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);
+}
+
+static void
+gtk_places_view_init (GtkPlacesView *self)
+{
+ GtkPlacesViewPrivate *priv;
+
+ priv = gtk_places_view_get_instance_private (self);
+
+ priv->volume_monitor = g_volume_monitor_get ();
+ priv->open_flags = GTK_PLACES_OPEN_NORMAL;
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+/**
+ * gtk_places_view_new:
+ *
+ * Creates a new #GtkPlacesView widget.
+ *
+ * The application should connect to at least the
+ * #GtkPlacesView::open-location signal to be notified
+ * when the user makes a selection in the view.
+ *
+ * Returns: a newly created #GtkPlacesView
+ *
+ * Since: 3.18
+ */
+GtkWidget *
+gtk_places_view_new (void)
+{
+ return g_object_new (GTK_TYPE_PLACES_VIEW, NULL);
+}
+
+/**
+ * gtk_places_view_set_open_flags:
+ * @view: a #GtkPlacesView
+ * @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 #GtkPlacesView::open-location signal is emitted, its flags
+ * argument will be set to one of the @flags that was passed in
+ * gtk_places_view_set_open_flags().
+ *
+ * Passing 0 for @flags will cause #GTK_PLACES_OPEN_NORMAL to always be sent
+ * to callbacks for the “open-location” signal.
+ *
+ * Since: 3.18
+ */
+void
+gtk_places_view_set_open_flags (GtkPlacesView *view,
+ GtkPlacesOpenFlags flags)
+{
+ GtkPlacesViewPrivate *priv;
+
+ g_return_if_fail (GTK_IS_PLACES_VIEW (view));
+
+ priv = gtk_places_view_get_instance_private (view);
+
+ if (priv->open_flags != flags)
+ {
+ priv->open_flags = flags;
+ g_object_notify_by_pspec (G_OBJECT (view), properties[PROP_OPEN_FLAGS]);
+ }
+}
+
+/**
+ * gtk_places_view_get_open_flags:
+ * @view: a #GtkPlacesSidebar
+ *
+ * Gets the open flags.
+ *
+ * Returns: the #GtkPlacesOpenFlags of @view
+ *
+ * Since: 3.18
+ */
+GtkPlacesOpenFlags
+gtk_places_view_get_open_flags (GtkPlacesView *view)
+{
+ GtkPlacesViewPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_PLACES_VIEW (view), 0);
+
+ priv = gtk_places_view_get_instance_private (view);
+
+ return priv->open_flags;
+}
+
+/**
+ * gtk_places_view_get_search_query:
+ * @view: a #GtkPlacesView
+ *
+ * Retrieves the current search query from @view.
+ *
+ * Returns: (transfer none): the current search query.
+ */
+const gchar*
+gtk_places_view_get_search_query (GtkPlacesView *view)
+{
+ GtkPlacesViewPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_PLACES_VIEW (view), NULL);
+
+ priv = gtk_places_view_get_instance_private (view);
+
+ return priv->search_query;
+}
+
+/**
+ * gtk_places_view_set_search_query:
+ * @view: a #GtkPlacesView
+ * @query_text: the query, or NULL.
+ *
+ * Sets the search query of @view. The search is immediately performed
+ * once the query is set.
+ *
+ * Returns:
+ */
+void
+gtk_places_view_set_search_query (GtkPlacesView *view,
+ const gchar *query_text)
+{
+ GtkPlacesViewPrivate *priv;
+
+ g_return_if_fail (GTK_IS_PLACES_VIEW (view));
+
+ priv = gtk_places_view_get_instance_private (view);
+
+ if (g_strcmp0 (priv->search_query, query_text) != 0)
+ {
+ g_clear_pointer (&priv->search_query, g_free);
+ priv->search_query = g_strdup (query_text);
+
+ gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->listbox));
+ gtk_list_box_invalidate_headers (GTK_LIST_BOX (priv->listbox));
+
+ update_view_mode (view);
+ }
+}
+
+/**
+ * gtk_places_view_get_loading:
+ * @view: a #GtkPlacesView
+ *
+ * Returns %TRUE if the view is loading locations.
+ *
+ * Since: 3.18
+ */
+gboolean
+gtk_places_view_get_loading (GtkPlacesView *view)
+{
+ GtkPlacesViewPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_PLACES_VIEW (view), FALSE);
+
+ priv = gtk_places_view_get_instance_private (view);
+
+ return priv->loading;
+}
+
+/**
+ * gtk_places_view_get_local_only:
+ * @view: a #GtkPlacesView
+ *
+ * Returns %TRUE if only local volumes are shown, i.e. no networks
+ * are displayed.
+ *
+ * Returns: %TRUE if only local volumes are shown, %FALSE otherwise.
+ *
+ * Since: 3.18
+ */
+gboolean
+gtk_places_view_get_local_only (GtkPlacesView *view)
+{
+ GtkPlacesViewPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_PLACES_VIEW (view), FALSE);
+
+ priv = gtk_places_view_get_instance_private (view);
+
+ return priv->local_only;
+}
+
+/**
+ * gtk_places_view_set_local_only:
+ * @view: a #GtkPlacesView
+ * @local_only: %TRUE to hide remote locations, %FALSE to show.
+ *
+ * Sets the #GtkPlacesView::local-only property to @local_only.
+ *
+ * Returns:
+ *
+ * Since: 3.18
+ */
+void
+gtk_places_view_set_local_only (GtkPlacesView *view,
+ gboolean local_only)
+{
+ GtkPlacesViewPrivate *priv;
+
+ g_return_if_fail (GTK_IS_PLACES_VIEW (view));
+
+ priv = gtk_places_view_get_instance_private (view);
+
+ if (priv->local_only != local_only)
+ {
+ priv->local_only = local_only;
+
+ gtk_widget_set_visible (priv->actionbar, !local_only);
+ gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->listbox));
+
+ update_view_mode (view);
+
+ g_object_notify_by_pspec (G_OBJECT (view), properties [PROP_LOCAL_ONLY]);
+ }
+}
diff --git a/src/gtk/gtkplacesview.ui b/src/gtk/gtkplacesview.ui
new file mode 100644
index 000000000..5ee523e92
--- /dev/null
+++ b/src/gtk/gtkplacesview.ui
@@ -0,0 +1,332 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk30">
+ <requires lib="gtk+" version="3.16"/>
+ <object class="GtkListStore" id="completion_store">
+ <columns>
+ <!-- column-name name -->
+ <column type="gchararray"/>
+ <!-- column-name uri -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkEntryCompletion" id="address_entry_completion">
+ <property name="model">completion_store</property>
+ <property name="minimum_key_length">1</property>
+ <property name="text_column">1</property>
+ <property name="inline_completion">True</property>
+ <property name="popup_completion">False</property>
+ </object>
+ <object class="GtkPopover" id="recent_servers_popover">
+ <child>
+ <object class="GtkStack" id="recent_servers_stack">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vexpand">True</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="pixel-size">48</property>
+ <property name="icon_name">network-server-symbolic</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Translators: Server as any successfully connected network address">No recent servers found</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">empty</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Recent Servers</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="vexpand">True</property>
+ <property name="shadow_type">in</property>
+ <property name="min_content_width">250</property>
+ <property name="min_content_height">150</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkListBox" id="recent_servers_listbox">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="selection_mode">none</property>
+ <signal name="row-activated" handler="on_recent_servers_listbox_row_activated" object="GtkPlacesView" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">list</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <template class="GtkPlacesView" parent="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <signal name="key-press-event" handler="on_key_press_event" object="GtkPlacesView" swapped="no" />
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vhomogeneous">False</property>
+ <property name="transition_type">crossfade</property>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkListBox" id="listbox">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="selection_mode">none</property>
+ <signal name="row-activated" handler="on_listbox_row_activated" object="GtkPlacesView" swapped="yes" />
+ </object>
+ </child>
+ <style>
+ <class name="view"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">browse</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="pixel_size">72</property>
+ <property name="icon_name">edit-find-symbolic</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">No results found</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.44"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Try a different search</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">empty-search</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkActionBar" id="actionbar">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="can_focus">False</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">True</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="connect_button">
+ <property name="label" translatable="yes">Con_nect</property>
+ <property name="use_underline">True</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="sensitive">False</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_connect_button_clicked" object="GtkPlacesView" swapped="yes" />
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkEntry" id="address_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="width_chars">20</property>
+ <property name="placeholder_text" translatable="yes">Enter server address…</property>
+ <property name="completion">address_entry_completion</property>
+ <signal name="notify::text" handler="on_address_entry_text_changed" object="GtkPlacesView" swapped="yes" />
+ <signal name="activate" handler="on_connect_button_clicked" object="GtkPlacesView" swapped="yes" />
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkMenuButton" id="server_list_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="direction">up</property>
+ <property name="popover">recent_servers_popover</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">pan-up-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/src/gtk/gtkplacesviewprivate.h b/src/gtk/gtkplacesviewprivate.h
new file mode 100644
index 000000000..706481652
--- /dev/null
+++ b/src/gtk/gtkplacesviewprivate.h
@@ -0,0 +1,91 @@
+/* gtkplacesview.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 General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef GTK_PLACES_VIEW_H
+#define GTK_PLACES_VIEW_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_PLACES_VIEW (gtk_places_view_get_type ())
+#define GTK_PLACES_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_PLACES_VIEW, GtkPlacesView))
+#define GTK_PLACES_VIEW_CLASS(klass)(G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_PLACES_VIEW, GtkPlacesViewClass))
+#define GTK_IS_PLACES_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_PLACES_VIEW))
+#define GTK_IS_PLACES_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_PLACES_VIEW))
+#define GTK_PLACES_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_PLACES_VIEW, GtkPlacesViewClass))
+
+typedef struct _GtkPlacesView GtkPlacesView;
+typedef struct _GtkPlacesViewClass GtkPlacesViewClass;
+typedef struct _GtkPlacesViewPrivate GtkPlacesViewPrivate;
+
+struct _GtkPlacesViewClass
+{
+ GtkBoxClass parent_class;
+
+ void (* open_location) (GtkPlacesView *view,
+ GFile *location,
+ GtkPlacesOpenFlags open_flags);
+
+ void (* show_error_message) (GtkPlacesSidebar *sidebar,
+ const gchar *primary,
+ const gchar *secondary);
+
+ /*< private >*/
+
+ /* Padding for future expansion */
+ gpointer reserved[10];
+};
+
+struct _GtkPlacesView
+{
+ GtkBox parent_instance;
+};
+
+GDK_AVAILABLE_IN_3_18
+GType gtk_places_view_get_type (void) G_GNUC_CONST;
+
+GDK_AVAILABLE_IN_3_18
+GtkPlacesOpenFlags gtk_places_view_get_open_flags (GtkPlacesView *view);
+GDK_AVAILABLE_IN_3_18
+void gtk_places_view_set_open_flags (GtkPlacesView *view,
+ GtkPlacesOpenFlags flags);
+
+GDK_AVAILABLE_IN_3_18
+const gchar* gtk_places_view_get_search_query (GtkPlacesView *view);
+GDK_AVAILABLE_IN_3_18
+void gtk_places_view_set_search_query (GtkPlacesView *view,
+ const gchar *query_text);
+
+GDK_AVAILABLE_IN_3_18
+gboolean gtk_places_view_get_local_only (GtkPlacesView *view);
+
+GDK_AVAILABLE_IN_3_18
+void gtk_places_view_set_local_only (GtkPlacesView *view,
+ gboolean local_only);
+
+GDK_AVAILABLE_IN_3_18
+gboolean gtk_places_view_get_loading (GtkPlacesView *view);
+
+GDK_AVAILABLE_IN_3_18
+GtkWidget * gtk_places_view_new (void);
+
+G_END_DECLS
+
+#endif /* GTK_PLACES_VIEW_H */
diff --git a/src/gtk/gtkplacesviewrow.c b/src/gtk/gtkplacesviewrow.c
new file mode 100644
index 000000000..dea648817
--- /dev/null
+++ b/src/gtk/gtkplacesviewrow.c
@@ -0,0 +1,326 @@
+/* gtkplacesviewrow.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 General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include "gtkplacesviewrowprivate.h"
+
+struct _GtkPlacesViewRow
+{
+ GtkListBoxRow parent_instance;
+
+ GtkSpinner *busy_spinner;
+ GtkButton *eject_button;
+ GtkImage *eject_icon;
+ GtkEventBox *event_box;
+ GtkImage *icon_image;
+ GtkLabel *name_label;
+ GtkLabel *path_label;
+
+ GVolume *volume;
+ GMount *mount;
+ GFile *file;
+
+ gint is_network : 1;
+};
+
+G_DEFINE_TYPE (GtkPlacesViewRow, 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
+gtk_places_view_row_finalize (GObject *object)
+{
+ GtkPlacesViewRow *self = GTK_PLACES_VIEW_ROW (object);
+
+ g_clear_object (&self->volume);
+ g_clear_object (&self->mount);
+ g_clear_object (&self->file);
+
+ G_OBJECT_CLASS (gtk_places_view_row_parent_class)->finalize (object);
+}
+
+static void
+gtk_places_view_row_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkPlacesViewRow *self;
+ GIcon *icon;
+
+ self = GTK_PLACES_VIEW_ROW (object);
+ icon = NULL;
+
+ switch (prop_id)
+ {
+ case PROP_ICON:
+ gtk_image_get_gicon (self->icon_image, &icon, NULL);
+ g_value_set_object (value, icon);
+ 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
+gtk_places_view_row_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkPlacesViewRow *self = GTK_PLACES_VIEW_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_ICON:
+ gtk_image_set_from_gicon (self->icon_image,
+ g_value_get_object (value),
+ GTK_ICON_SIZE_LARGE_TOOLBAR);
+ 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));
+ gtk_widget_set_visible (GTK_WIDGET (self->eject_button), self->mount != NULL);
+ break;
+
+ case PROP_FILE:
+ g_set_object (&self->file, g_value_get_object (value));
+ break;
+
+ case PROP_IS_NETWORK:
+ gtk_places_view_row_set_is_network (self, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtk_places_view_row_class_init (GtkPlacesViewRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = gtk_places_view_row_finalize;
+ object_class->get_property = gtk_places_view_row_get_property;
+ object_class->set_property = gtk_places_view_row_set_property;
+
+ 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/gtkplacesviewrow.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, busy_spinner);
+ gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, eject_button);
+ gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, eject_icon);
+ gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, event_box);
+ gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, icon_image);
+ gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, name_label);
+ gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, path_label);
+}
+
+static void
+gtk_places_view_row_init (GtkPlacesViewRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+GtkWidget*
+gtk_places_view_row_new (GVolume *volume,
+ GMount *mount)
+{
+ return g_object_new (GTK_TYPE_PLACES_VIEW_ROW,
+ "volume", volume,
+ "mount", mount,
+ NULL);
+}
+
+GMount*
+gtk_places_view_row_get_mount (GtkPlacesViewRow *row)
+{
+ g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), NULL);
+
+ return row->mount;
+}
+
+GVolume*
+gtk_places_view_row_get_volume (GtkPlacesViewRow *row)
+{
+ g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), NULL);
+
+ return row->volume;
+}
+
+GFile*
+gtk_places_view_row_get_file (GtkPlacesViewRow *row)
+{
+ g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), NULL);
+
+ return row->file;
+}
+
+GtkWidget*
+gtk_places_view_row_get_eject_button (GtkPlacesViewRow *row)
+{
+ g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), NULL);
+
+ return GTK_WIDGET (row->eject_button);
+}
+
+GtkWidget*
+gtk_places_view_row_get_event_box (GtkPlacesViewRow *row)
+{
+ g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), NULL);
+
+ return GTK_WIDGET (row->event_box);
+}
+
+void
+gtk_places_view_row_set_busy (GtkPlacesViewRow *row,
+ gboolean is_busy)
+{
+ g_return_if_fail (GTK_IS_PLACES_VIEW_ROW (row));
+
+ gtk_widget_set_visible (GTK_WIDGET (row->busy_spinner), is_busy);
+}
+
+gboolean
+gtk_places_view_row_get_is_network (GtkPlacesViewRow *row)
+{
+ g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), FALSE);
+
+ return row->is_network;
+}
+
+void
+gtk_places_view_row_set_is_network (GtkPlacesViewRow *row,
+ gboolean is_network)
+{
+ if (row->is_network != is_network)
+ {
+ row->is_network = is_network;
+
+ gtk_image_set_from_icon_name (row->eject_icon,
+ is_network ? "network-offline-symbolic" : "media-eject-symbolic",
+ GTK_ICON_SIZE_BUTTON);
+ gtk_widget_set_tooltip_text (GTK_WIDGET (row->eject_button), is_network ? _("Disconnect") : _("Unmount"));
+ }
+}
diff --git a/src/gtk/gtkplacesviewrow.ui b/src/gtk/gtkplacesviewrow.ui
new file mode 100644
index 000000000..d1e923b19
--- /dev/null
+++ b/src/gtk/gtkplacesviewrow.ui
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk30">
+ <requires lib="gtk+" version="3.16"/>
+ <template class="GtkPlacesViewRow" parent="GtkListBoxRow">
+ <property name="width_request">100</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <style>
+ <class name="volume-row" />
+ </style>
+ <child>
+ <object class="GtkEventBox" id="event_box">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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="border_width">0</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkImage" id="icon_image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="pixel_size">32</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="name_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="path_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="justify">right</property>
+ <property name="ellipsize">middle</property>
+ <property name="xalign">1</property>
+ <property name="width_chars">15</property>
+ <property name="max_width_chars">15</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="eject_button">
+ <property name="visible">False</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <property name="tooltip-text" translatable="yes">Unmount</property>
+ <child>
+ <object class="GtkImage" id="eject_icon">
+ <property name="visible">True</property>
+ <property name="icon_name">media-eject-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ </child>
+ <style>
+ <class name="image-button"/>
+ <class name="sidebar-button"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinner" id="busy_spinner">
+ <property name="visible">False</property>
+ <property name="can_focus">False</property>
+ <property name="active">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/gtk/gtkplacesviewrowprivate.h b/src/gtk/gtkplacesviewrowprivate.h
new file mode 100644
index 000000000..59294dec6
--- /dev/null
+++ b/src/gtk/gtkplacesviewrowprivate.h
@@ -0,0 +1,53 @@
+/* gtkplacesviewrow.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 General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GTK_PLACES_VIEW_ROW_H
+#define GTK_PLACES_VIEW_ROW_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_PLACES_VIEW_ROW (gtk_places_view_row_get_type())
+
+G_DECLARE_FINAL_TYPE (GtkPlacesViewRow, gtk_places_view_row, GTK, PLACES_VIEW_ROW, GtkListBoxRow)
+
+GtkWidget* gtk_places_view_row_new (GVolume *volume,
+ GMount *mount);
+
+GtkWidget* gtk_places_view_row_get_eject_button (GtkPlacesViewRow *row);
+
+GtkWidget* gtk_places_view_row_get_event_box (GtkPlacesViewRow *row);
+
+GMount* gtk_places_view_row_get_mount (GtkPlacesViewRow *row);
+
+GVolume* gtk_places_view_row_get_volume (GtkPlacesViewRow *row);
+
+GFile* gtk_places_view_row_get_file (GtkPlacesViewRow *row);
+
+void gtk_places_view_row_set_busy (GtkPlacesViewRow *row,
+ gboolean is_busy);
+
+gboolean gtk_places_view_row_get_is_network (GtkPlacesViewRow *row);
+
+void gtk_places_view_row_set_is_network (GtkPlacesViewRow *row,
+ gboolean is_network);
+
+G_END_DECLS
+
+#endif /* GTK_PLACES_VIEW_ROW_H */
diff --git a/src/nautilus-application.c b/src/nautilus-application.c
index bc6da1739..dbe78bed0 100644
--- a/src/nautilus-application.c
+++ b/src/nautilus-application.c
@@ -389,7 +389,7 @@ get_window_slot_for_location (NautilusApplication *application, GFile *location)
slot = NULL;
file = nautilus_file_get (location);
- if (!nautilus_file_is_directory (file)) {
+ if (!nautilus_file_is_directory (file) && !nautilus_file_is_other_locations (file)) {
location = g_file_get_parent (location);
} else {
g_object_ref (location);
diff --git a/src/nautilus-pathbar.c b/src/nautilus-pathbar.c
index 19ff073e8..8e8319af8 100644
--- a/src/nautilus-pathbar.c
+++ b/src/nautilus-pathbar.c
@@ -43,6 +43,7 @@ enum {
typedef enum {
NORMAL_BUTTON,
+ OTHER_LOCATIONS_BUTTON,
ROOT_BUTTON,
HOME_BUTTON,
MOUNT_BUTTON
@@ -400,11 +401,14 @@ nautilus_path_bar_dispose (GObject *object)
static const char *
get_dir_name (ButtonData *button_data)
{
- if (button_data->type == HOME_BUTTON) {
- return _("Home");
- } else {
- return button_data->dir_name;
- }
+ switch (button_data->type) {
+ case HOME_BUTTON:
+ return _("Home");
+ case OTHER_LOCATIONS_BUTTON:
+ return _("Other Locations");
+ default:
+ return button_data->dir_name;
+ }
}
/* We always want to request the same size for the label, whether
@@ -1629,8 +1633,13 @@ setup_button_type (ButtonData *button_data,
GFile *location)
{
GMount *mount;
+ gchar *uri;
+
+ uri = g_file_get_uri (location);
- if (nautilus_is_root_directory (location)) {
+ if (g_strcmp0 (uri, "other-locations:///") == 0) {
+ button_data->type = OTHER_LOCATIONS_BUTTON;
+ } else if (nautilus_is_root_directory (location)) {
button_data->type = ROOT_BUTTON;
} else if (nautilus_is_home_directory (location)) {
button_data->type = HOME_BUTTON;
@@ -1644,6 +1653,8 @@ setup_button_type (ButtonData *button_data,
} else {
button_data->type = NORMAL_BUTTON;
}
+
+ g_free (uri);
}
static void
diff --git a/src/nautilus-places-view.c b/src/nautilus-places-view.c
new file mode 100644
index 000000000..7a4d15fc3
--- /dev/null
+++ b/src/nautilus-places-view.c
@@ -0,0 +1,359 @@
+/* nautilus-places-view.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 General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nautilus-mime-actions.h"
+#include "nautilus-places-view.h"
+#include "nautilus-window-slot.h"
+#include "gtk/gtkplacesviewprivate.h"
+
+typedef struct
+{
+ GFile *location;
+ GIcon *icon;
+ NautilusQuery *search_query;
+
+ GtkWidget *places_view;
+} NautilusPlacesViewPrivate;
+
+struct _NautilusPlacesView
+{
+ GtkFrameClass parent;
+};
+
+static void nautilus_places_view_iface_init (NautilusViewInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (NautilusPlacesView, nautilus_places_view, GTK_TYPE_BOX,
+ G_ADD_PRIVATE (NautilusPlacesView)
+ G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_VIEW, nautilus_places_view_iface_init));
+
+enum {
+ PROP_0,
+ PROP_ICON,
+ PROP_LOCATION,
+ PROP_SEARCH_QUERY,
+ PROP_VIEW_WIDGET,
+ PROP_IS_LOADING,
+ PROP_IS_SEARCHING,
+ LAST_PROP
+};
+
+static void
+open_location_cb (NautilusPlacesView *view,
+ GFile *location,
+ GtkPlacesOpenFlags open_flags)
+{
+ NautilusWindowOpenFlags flags;
+ GtkWidget *slot;
+
+ slot = gtk_widget_get_ancestor (GTK_WIDGET (view), NAUTILUS_TYPE_WINDOW_SLOT);
+
+ switch (open_flags) {
+ case GTK_PLACES_OPEN_NEW_TAB:
+ flags = NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB;
+ break;
+
+ case GTK_PLACES_OPEN_NEW_WINDOW:
+ flags = NAUTILUS_WINDOW_OPEN_FLAG_NEW_WINDOW;
+ break;
+
+ case GTK_PLACES_OPEN_NORMAL: /* fall-through */
+ default:
+ flags = 0;
+ break;
+ }
+
+ if (slot) {
+ NautilusFile *file;
+ GtkWidget *window;
+ char *path;
+
+ path = "other-locations:///";
+ file = nautilus_file_get (location);
+ window = gtk_widget_get_toplevel (GTK_WIDGET (view));
+
+ nautilus_mime_activate_file (GTK_WINDOW (window),
+ NAUTILUS_WINDOW_SLOT (slot),
+ file,
+ path,
+ flags);
+
+ }
+}
+
+static void
+loading_cb (NautilusView *view)
+{
+ g_object_notify (G_OBJECT (view), "is-loading");
+}
+
+static void
+nautilus_places_view_finalize (GObject *object)
+{
+ NautilusPlacesView *self = (NautilusPlacesView *)object;
+ NautilusPlacesViewPrivate *priv = nautilus_places_view_get_instance_private (self);
+
+ g_clear_object (&priv->icon);
+ g_clear_object (&priv->location);
+ g_clear_object (&priv->search_query);
+
+ G_OBJECT_CLASS (nautilus_places_view_parent_class)->finalize (object);
+}
+
+static void
+nautilus_places_view_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusView *view = NAUTILUS_VIEW (object);
+
+ switch (prop_id) {
+ case PROP_ICON:
+ g_value_set_object (value, nautilus_view_get_icon (view));
+ break;
+
+ case PROP_LOCATION:
+ g_value_set_object (value, nautilus_view_get_location (view));
+ break;
+
+ case PROP_SEARCH_QUERY:
+ g_value_set_object (value, nautilus_view_get_search_query (view));
+ break;
+
+ case PROP_VIEW_WIDGET:
+ g_value_set_object (value, nautilus_view_get_view_widget (view));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+nautilus_places_view_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusView *view = NAUTILUS_VIEW (object);
+
+ switch (prop_id) {
+ case PROP_LOCATION:
+ nautilus_view_set_location (view, g_value_get_object (value));
+ break;
+
+ case PROP_SEARCH_QUERY:
+ nautilus_view_set_search_query (view, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static GIcon*
+nautilus_places_view_get_icon (NautilusView *view)
+{
+ NautilusPlacesViewPrivate *priv;
+
+ priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view));
+
+ return priv->icon;
+}
+
+static GFile*
+nautilus_places_view_get_location (NautilusView *view)
+{
+ NautilusPlacesViewPrivate *priv;
+
+ priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view));
+
+ return priv->location;
+}
+
+static void
+nautilus_places_view_set_location (NautilusView *view,
+ GFile *location)
+{
+ if (location) {
+ NautilusPlacesViewPrivate *priv;
+ gchar *uri;
+
+ priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view));
+ uri = g_file_get_uri (location);
+
+ /*
+ * If it's not trying to open the places view itself, simply
+ * delegates the location to window-slot, which takes care of
+ * selecting the appropriate view.
+ */
+ if (!g_strcmp0 (uri, "other-locations:///") == 0) {
+ GtkWidget *slot;
+
+ slot = gtk_widget_get_ancestor (GTK_WIDGET (view), NAUTILUS_TYPE_WINDOW_SLOT);
+ g_assert (slot != NULL);
+
+ nautilus_window_slot_open_location (NAUTILUS_WINDOW_SLOT (slot), location, 0);
+ } else {
+ g_set_object (&priv->location, location);
+ }
+
+ g_free (uri);
+ }
+}
+
+static GList*
+nautilus_places_view_get_selection (NautilusView *view)
+{
+ /* STUB */
+ return NULL;
+}
+
+static void
+nautilus_places_view_set_selection (NautilusView *view,
+ GList *selection)
+{
+ /* STUB */
+}
+
+static NautilusQuery*
+nautilus_places_view_get_search_query (NautilusView *view)
+{
+ NautilusPlacesViewPrivate *priv;
+
+ priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view));
+
+ return priv->search_query;
+}
+
+static void
+nautilus_places_view_set_search_query (NautilusView *view,
+ NautilusQuery *query)
+{
+ NautilusPlacesViewPrivate *priv;
+ gchar *text;
+
+ priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view));
+
+ g_set_object (&priv->search_query, query);
+
+ text = query ? nautilus_query_get_text (query) : NULL;
+
+ gtk_places_view_set_search_query (GTK_PLACES_VIEW (priv->places_view), text);
+
+ g_free (text);
+}
+
+static GtkWidget*
+nautilus_places_view_get_view_widget (NautilusView *view)
+{
+ /* By returning NULL, the view menu button turns insensitive */
+ return NULL;
+}
+
+static gboolean
+nautilus_places_view_is_loading (NautilusView *view)
+{
+ NautilusPlacesViewPrivate *priv;
+
+ priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view));
+
+ return gtk_places_view_get_loading (GTK_PLACES_VIEW (priv->places_view));
+}
+
+static gboolean
+nautilus_places_view_is_searching (NautilusView *view)
+{
+ NautilusPlacesViewPrivate *priv;
+
+ priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view));
+
+ return priv->search_query != NULL;
+}
+
+static void
+nautilus_places_view_iface_init (NautilusViewInterface *iface)
+{
+ iface->get_icon = nautilus_places_view_get_icon;
+ iface->get_location = nautilus_places_view_get_location;
+ iface->set_location = nautilus_places_view_set_location;
+ iface->get_selection = nautilus_places_view_get_selection;
+ iface->set_selection = nautilus_places_view_set_selection;
+ iface->get_search_query = nautilus_places_view_get_search_query;
+ iface->set_search_query = nautilus_places_view_set_search_query;
+ iface->get_view_widget = nautilus_places_view_get_view_widget;
+ iface->is_loading = nautilus_places_view_is_loading;
+ iface->is_searching = nautilus_places_view_is_searching;
+}
+
+static void
+nautilus_places_view_class_init (NautilusPlacesViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = nautilus_places_view_finalize;
+ object_class->get_property = nautilus_places_view_get_property;
+ object_class->set_property = nautilus_places_view_set_property;
+
+ g_object_class_override_property (object_class, PROP_ICON, "icon");
+ g_object_class_override_property (object_class, PROP_IS_LOADING, "is-loading");
+ g_object_class_override_property (object_class, PROP_IS_SEARCHING, "is-searching");
+ g_object_class_override_property (object_class, PROP_LOCATION, "location");
+ g_object_class_override_property (object_class, PROP_SEARCH_QUERY, "search-query");
+ g_object_class_override_property (object_class, PROP_VIEW_WIDGET, "view-widget");
+}
+
+static void
+nautilus_places_view_init (NautilusPlacesView *self)
+{
+ NautilusPlacesViewPrivate *priv;
+
+ priv = nautilus_places_view_get_instance_private (self);
+
+ /* Icon */
+ priv->icon = g_themed_icon_new_with_default_fallbacks ("view-list-symbolic");
+
+ /* Location */
+ priv->location = g_file_new_for_uri ("other-locations:///");
+
+ /* Places view */
+ priv->places_view = gtk_places_view_new ();
+ gtk_places_view_set_open_flags (GTK_PLACES_VIEW (priv->places_view),
+ GTK_PLACES_OPEN_NEW_TAB | GTK_PLACES_OPEN_NEW_WINDOW | GTK_PLACES_OPEN_NORMAL);
+ gtk_widget_set_hexpand (priv->places_view, TRUE);
+ gtk_widget_set_vexpand (priv->places_view, TRUE);
+ gtk_widget_show (priv->places_view);
+ gtk_container_add (GTK_CONTAINER (self), priv->places_view);
+
+ g_signal_connect_swapped (priv->places_view,
+ "notify::loading",
+ G_CALLBACK (loading_cb),
+ self);
+
+ g_signal_connect_swapped (priv->places_view,
+ "open-location",
+ G_CALLBACK (open_location_cb),
+ self);
+
+}
+
+NautilusPlacesView *
+nautilus_places_view_new (void)
+{
+ return g_object_new (NAUTILUS_TYPE_PLACES_VIEW, NULL);
+}
diff --git a/src/nautilus-places-view.h b/src/nautilus-places-view.h
new file mode 100644
index 000000000..51939103e
--- /dev/null
+++ b/src/nautilus-places-view.h
@@ -0,0 +1,37 @@
+/* nautilus-places-view.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 General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NAUTILUS_PLACES_VIEW_H
+#define NAUTILUS_PLACES_VIEW_H
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-view.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_PLACES_VIEW (nautilus_places_view_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusPlacesView, nautilus_places_view, NAUTILUS, PLACES_VIEW, GtkBox)
+
+NautilusPlacesView* nautilus_places_view_new (void);
+
+G_END_DECLS
+
+#endif /* NAUTILUS_PLACES_VIEW_H */
diff --git a/src/nautilus-window-slot-dnd.c b/src/nautilus-window-slot-dnd.c
index 71386b140..af15c18bc 100644
--- a/src/nautilus-window-slot-dnd.c
+++ b/src/nautilus-window-slot-dnd.c
@@ -360,7 +360,13 @@ slot_proxy_handle_drop (GtkWidget *widget,
target_view = NULL;
if (target_slot != NULL) {
- target_view = nautilus_window_slot_get_current_view (target_slot);
+ NautilusView *view;
+
+ view = nautilus_window_slot_get_current_view (target_slot);
+
+ if (view && NAUTILUS_IS_FILES_VIEW (view)) {
+ target_view = NAUTILUS_FILES_VIEW (view);
+ }
}
if (target_slot != NULL && target_view != NULL) {
diff --git a/src/nautilus-window-slot.c b/src/nautilus-window-slot.c
index 70ab1bfd5..128730865 100644
--- a/src/nautilus-window-slot.c
+++ b/src/nautilus-window-slot.c
@@ -29,6 +29,7 @@
#include "nautilus-desktop-window.h"
#include "nautilus-list-view.h"
#include "nautilus-mime-actions.h"
+#include "nautilus-places-view.h"
#include "nautilus-special-location-bar.h"
#include "nautilus-trash-bar.h"
#include "nautilus-view.h"
@@ -121,6 +122,7 @@ struct NautilusWindowSlotDetails {
NautilusWindowGoToCallback open_callback;
gpointer open_callback_user_data;
gchar *view_mode_before_search;
+ gchar *view_mode_before_places;
};
static guint signals[LAST_SIGNAL] = { 0 };
@@ -133,15 +135,108 @@ static void nautilus_window_slot_sync_actions (NautilusWindowSlot *slot);
static void nautilus_window_slot_connect_new_content_view (NautilusWindowSlot *slot);
static void nautilus_window_slot_disconnect_content_view (NautilusWindowSlot *slot);
static void nautilus_window_slot_emit_location_change (NautilusWindowSlot *slot, GFile *from, GFile *to);
+static gboolean nautilus_window_slot_content_view_matches (NautilusWindowSlot *slot, const char *iid);
+static NautilusView* nautilus_window_slot_get_view_for_location (NautilusWindowSlot *slot, GFile *location);
+
+static NautilusView*
+nautilus_window_slot_get_view_for_location (NautilusWindowSlot *slot,
+ GFile *location)
+{
+ NautilusWindow *window;
+ NautilusView *view;
+ NautilusFile *file;
+
+ window = nautilus_window_slot_get_window (slot);
+ file = nautilus_file_get (location);
+ view = NULL;
+
+ /* FIXME bugzilla.gnome.org 41243:
+ * We should use inheritance instead of these special cases
+ * for the desktop window.
+ */
+ if (NAUTILUS_IS_DESKTOP_WINDOW (window)) {
+ view = NAUTILUS_VIEW (nautilus_files_view_new (NAUTILUS_DESKTOP_ICON_VIEW_IID, slot));
+ } else if (nautilus_file_is_other_locations (file)) {
+ view = NAUTILUS_VIEW (nautilus_places_view_new ());
+
+ /* Save the current view, so we can go back after places view */
+ if (slot->details->content_view && NAUTILUS_IS_FILES_VIEW (slot->details->content_view)) {
+ g_clear_pointer (&slot->details->view_mode_before_places, g_free);
+ slot->details->view_mode_before_places = g_strdup (nautilus_files_view_get_view_id (NAUTILUS_FILES_VIEW (slot->details->content_view)));
+ }
+ } else {
+ gchar *view_id;
+
+ view_id = NULL;
+
+ /* If we are in search, try to use by default list view. This will be deactivated
+ * if the user manually switch to a diferent view mode */
+ if (nautilus_file_is_in_search (file)) {
+ if (g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_LIST_VIEW_ON_SEARCH)) {
+ /* If it's already set, is because we already made the change to search mode,
+ * so the view mode of the current view will be the one search is using,
+ * which is not the one we are interested in */
+ if (slot->details->view_mode_before_search == NULL) {
+ slot->details->view_mode_before_search = g_strdup (nautilus_files_view_get_view_id (NAUTILUS_FILES_VIEW (slot->details->content_view)));
+ }
+ view_id = g_strdup (NAUTILUS_LIST_VIEW_IID);
+ } else {
+ g_free (slot->details->view_mode_before_search);
+ slot->details->view_mode_before_search = NULL;
+ }
+ }
+
+ /* If there is already a view, just use the view mode that it's currently using, or
+ * if we were on search before, use what we were using before entering
+ * search mode */
+ if (slot->details->content_view != NULL && view_id == NULL) {
+ if (slot->details->view_mode_before_search != NULL) {
+ view_id = slot->details->view_mode_before_search;
+ slot->details->view_mode_before_search = NULL;
+ } else if (NAUTILUS_IS_PLACES_VIEW (slot->details->content_view)) {
+ view_id = slot->details->view_mode_before_places;
+ slot->details->view_mode_before_places = NULL;
+ } else {
+ view_id = g_strdup (nautilus_files_view_get_view_id (NAUTILUS_FILES_VIEW (slot->details->content_view)));
+ }
+ }
+
+ /* If there is not previous view in this slot, use the default view mode
+ * from preferences */
+ if (view_id == NULL) {
+ view_id = nautilus_global_preferences_get_default_folder_viewer_preference_as_iid ();
+ }
+
+ /* Try to reuse the current view */
+ if (nautilus_window_slot_content_view_matches (slot, view_id)) {
+ view = slot->details->content_view;
+ } else {
+ view = NAUTILUS_VIEW (nautilus_files_view_new (view_id, slot));
+ }
+
+ g_free (view_id);
+ }
+
+ nautilus_file_unref (file);
+
+ return g_object_ref (view);
+}
static gboolean
-nautilus_window_slot_content_view_matches_iid (NautilusWindowSlot *slot,
- const char *iid)
+nautilus_window_slot_content_view_matches (NautilusWindowSlot *slot,
+ const char *iid)
{
if (slot->details->content_view == NULL) {
return FALSE;
}
- return g_strcmp0 (nautilus_files_view_get_view_id (NAUTILUS_FILES_VIEW (slot->details->content_view)), iid) == 0;
+
+ if (!iid && NAUTILUS_IS_PLACES_VIEW (slot->details->content_view)) {
+ return TRUE;
+ } else if (iid && NAUTILUS_IS_FILES_VIEW (slot->details->content_view)){
+ return g_strcmp0 (nautilus_files_view_get_view_id (NAUTILUS_FILES_VIEW (slot->details->content_view)), iid) == 0;
+ } else {
+ return FALSE;
+ }
}
static void
@@ -170,10 +265,11 @@ nautilus_window_slot_sync_actions (NautilusWindowSlot *slot)
/* View mode */
action = g_action_map_lookup_action (G_ACTION_MAP (slot->details->window), "view-mode");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
- if (nautilus_window_slot_content_view_matches_iid (slot, NAUTILUS_LIST_VIEW_ID)) {
+ if (nautilus_window_slot_content_view_matches (slot, NAUTILUS_LIST_VIEW_ID)) {
g_action_change_state (action, g_variant_new_string ("list"));
- } else if (nautilus_window_slot_content_view_matches_iid (slot, NAUTILUS_CANVAS_VIEW_ID)) {
+ } else if (nautilus_window_slot_content_view_matches (slot, NAUTILUS_CANVAS_VIEW_ID)) {
g_action_change_state (action, g_variant_new_string ("grid"));
} else {
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
@@ -521,8 +617,8 @@ static void end_location_change (NautilusWindowSlot
static void cancel_location_change (NautilusWindowSlot *slot);
static void got_file_info_for_view_selection_callback (NautilusFile *file,
gpointer callback_data);
-static gboolean create_content_view (NautilusWindowSlot *slot,
- const char *view_id,
+static gboolean setup_view (NautilusWindowSlot *slot,
+ NautilusView *view,
GError **error);
static void load_new_location (NautilusWindowSlot *slot,
GFile *location,
@@ -742,13 +838,11 @@ begin_location_change (NautilusWindowSlot *slot,
nautilus_profile_start (NULL);
- directory = nautilus_directory_get (location);
-
/* Avoid to update status from the current view in our async calls */
nautilus_window_slot_disconnect_content_view (slot);
/* We are going to change the location, so make sure we stop any loading
* or searching of the previous view, so we avoid to be slow */
- if (slot->details->content_view) {
+ if (slot->details->content_view && NAUTILUS_IS_FILES_VIEW (slot->details->content_view)) {
nautilus_files_view_stop_loading (NAUTILUS_FILES_VIEW (slot->details->content_view));
}
@@ -796,6 +890,8 @@ begin_location_change (NautilusWindowSlot *slot,
* after determining an initial view (in the components), then
* we end up fetching things twice.
*/
+ directory = nautilus_directory_get (location);
+
if (type == NAUTILUS_LOCATION_CHANGE_RELOAD) {
force_reload = TRUE;
} else if (!nautilus_monitor_active ()) {
@@ -819,7 +915,8 @@ begin_location_change (NautilusWindowSlot *slot,
/* Set current_bookmark scroll pos */
if (slot->details->current_location_bookmark != NULL &&
- slot->details->content_view != NULL) {
+ slot->details->content_view != NULL &&
+ NAUTILUS_IS_FILES_VIEW (slot->details->content_view)) {
current_pos = nautilus_files_view_get_first_visible_file (NAUTILUS_FILES_VIEW (slot->details->content_view));
nautilus_bookmark_set_scroll_pos (slot->details->current_location_bookmark, current_pos);
g_free (current_pos);
@@ -1016,10 +1113,10 @@ got_file_info_for_view_selection_callback (NautilusFile *file,
gpointer callback_data)
{
GError *error = NULL;
- char *view_id;
NautilusWindow *window;
NautilusWindowSlot *slot;
NautilusFile *viewed_file, *parent_file;
+ NautilusView *view;
GFile *location, *default_location;
GMountOperation *mount_op;
MountNotMountedData *data;
@@ -1116,54 +1213,16 @@ got_file_info_for_view_selection_callback (NautilusFile *file,
nautilus_file_unref (parent_file);
location = slot->details->pending_location;
- view_id = NULL;
-
- if (error == NULL ||
- (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_SUPPORTED)) {
- /* We got the information we need, now pick what view to use: */
+ view = NULL;
- /* If we are in search, try to use by default list view. This will be deactivated
- * if the user manually switch to a diferent view mode */
- if (nautilus_file_is_in_search (nautilus_file_get (location))) {
- if (g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_LIST_VIEW_ON_SEARCH)) {
- /* If it's already set, is because we already made the change to search mode,
- * so the view mode of the current view will be the one search is using,
- * which is not the one we are interested in */
- if (slot->details->view_mode_before_search == NULL) {
- slot->details->view_mode_before_search = g_strdup (nautilus_files_view_get_view_id (NAUTILUS_FILES_VIEW (slot->details->content_view)));
- }
- view_id = g_strdup (NAUTILUS_LIST_VIEW_IID);
- } else {
- g_free (slot->details->view_mode_before_search);
- slot->details->view_mode_before_search = NULL;
- }
- }
-
- /* If there is already a view, just use the view mode that it's currently using, or
- * if we were on search before, use what we were using before entering
- * search mode */
- if (slot->details->content_view != NULL && view_id == NULL) {
- if (slot->details->view_mode_before_search != NULL) {
- view_id = g_strdup (slot->details->view_mode_before_search);
- g_free (slot->details->view_mode_before_search);
- slot->details->view_mode_before_search = NULL;
- } else {
- view_id = g_strdup (nautilus_files_view_get_view_id (NAUTILUS_FILES_VIEW (slot->details->content_view)));
- }
- }
-
- /* If there is not previous view in this slot, use the default view mode
- * from preferences */
- if (view_id == NULL) {
- view_id = nautilus_global_preferences_get_default_folder_viewer_preference_as_iid ();
- }
+ if (!error || (error && error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_SUPPORTED)) {
+ view = nautilus_window_slot_get_view_for_location (slot, location);
}
- if (view_id != NULL) {
+ if (view != NULL) {
GError *err = NULL;
- create_content_view (slot, view_id, &err);
- g_free (view_id);
+ setup_view (slot, view, &err);
report_callback (slot, err);
g_clear_error (&err);
@@ -1240,7 +1299,6 @@ got_file_info_for_view_selection_callback (NautilusFile *file,
g_clear_error (&error);
nautilus_file_unref (file);
-
nautilus_profile_end (NULL);
}
@@ -1252,52 +1310,26 @@ got_file_info_for_view_selection_callback (NautilusFile *file,
* view, and the current location will be used.
*/
static gboolean
-create_content_view (NautilusWindowSlot *slot,
- const char *view_id,
- GError **error_out)
+setup_view (NautilusWindowSlot *slot,
+ NautilusView *view,
+ GError **error_out)
{
- NautilusWindow *window;
- NautilusView *view;
- GList *selection;
gboolean ret = TRUE;
GError *error = NULL;
GFile *old_location;
- window = nautilus_window_slot_get_window (slot);
-
nautilus_profile_start (NULL);
- /* FIXME bugzilla.gnome.org 41243:
- * We should use inheritance instead of these special cases
- * for the desktop window.
- */
- if (NAUTILUS_IS_DESKTOP_WINDOW (window)) {
- /* We force the desktop to use a desktop_icon_view. It's simpler
- * to fix it here than trying to make it pick the right view in
- * the first place.
- */
- view_id = NAUTILUS_DESKTOP_ICON_VIEW_IID;
- }
-
nautilus_window_slot_disconnect_content_view (slot);
- if (nautilus_window_slot_content_view_matches_iid (slot, view_id)) {
- /* reuse existing content view */
- view = slot->details->content_view;
- slot->details->new_content_view = view;
- g_object_ref (view);
- } else {
- /* create a new content view */
- view = NAUTILUS_VIEW (nautilus_files_view_new (view_id, slot));
- slot->details->new_content_view = view;
- }
+ slot->details->new_content_view = view;
+
nautilus_window_slot_connect_new_content_view (slot);
/* Forward search selection and state before loading the new model */
old_location = slot->details->content_view ? nautilus_view_get_location (slot->details->content_view) : NULL;
/* Actually load the pending location and selection: */
-
if (slot->details->pending_location != NULL) {
load_new_location (slot,
slot->details->pending_location,
@@ -1345,13 +1377,11 @@ load_new_location (NautilusWindowSlot *slot,
gboolean tell_current_content_view,
gboolean tell_new_content_view)
{
- GList *selection_copy;
NautilusView *view;
g_assert (slot != NULL);
g_assert (location != NULL);
- selection_copy = g_list_copy_deep (selection, (GCopyFunc) g_object_ref, NULL);
view = NULL;
nautilus_profile_start (NULL);
@@ -1373,8 +1403,6 @@ load_new_location (NautilusWindowSlot *slot,
nautilus_view_set_selection (view, selection);
}
- g_list_free_full (selection_copy, g_object_unref);
-
nautilus_profile_end (NULL);
}
@@ -1447,7 +1475,8 @@ cancel_location_change (NautilusWindowSlot *slot)
if (slot->details->pending_location != NULL
&& location != NULL
- && slot->details->content_view != NULL) {
+ && slot->details->content_view != NULL
+ && NAUTILUS_IS_FILES_VIEW (slot->details->content_view)) {
/* No need to tell the new view - either it is the
* same as the old view, in which case it will already
@@ -1548,7 +1577,7 @@ nautilus_window_slot_set_content_view (NautilusWindowSlot *slot,
DEBUG ("Change view of window %s to %s", uri, id);
g_free (uri);
- if (nautilus_window_slot_content_view_matches_iid (slot, id)) {
+ if (nautilus_window_slot_content_view_matches (slot, id)) {
return;
}
@@ -1564,14 +1593,13 @@ nautilus_window_slot_set_content_view (NautilusWindowSlot *slot,
* is currently visible */
slot->details->pending_scroll_to = nautilus_files_view_get_first_visible_file (NAUTILUS_FILES_VIEW (slot->details->content_view));
}
+
slot->details->location_change_type = NAUTILUS_LOCATION_CHANGE_RELOAD;
- if (!create_content_view (slot, id, NULL)) {
+ if (!setup_view (slot, NAUTILUS_VIEW (view), NULL)) {
/* Just load the homedir. */
nautilus_window_slot_go_home (slot, FALSE);
}
-
- nautilus_file_list_free (selection);
}
void
@@ -2152,7 +2180,7 @@ nautilus_window_slot_connect_new_content_view (NautilusWindowSlot *slot)
static void
nautilus_window_slot_disconnect_content_view (NautilusWindowSlot *slot)
{
- if (slot->details->content_view != NULL) {
+ if (slot->details->new_content_view && NAUTILUS_IS_FILES_VIEW (slot->details->new_content_view)) {
/* disconnect old view */
g_signal_handlers_disconnect_by_func (slot->details->content_view,
G_CALLBACK (view_is_loading_changed_cb),
diff --git a/src/nautilus-window.c b/src/nautilus-window.c
index 296ba533c..4353ee3ca 100644
--- a/src/nautilus-window.c
+++ b/src/nautilus-window.c
@@ -986,6 +986,18 @@ places_sidebar_show_connect_to_server_cb (GtkPlacesSidebar *sidebar,
gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
}
+static void
+places_sidebar_show_other_locations (NautilusWindow *window)
+{
+ GFile *location;
+
+ location = g_file_new_for_uri ("other-locations:///");
+
+ open_location_cb (window, location, GTK_PLACES_OPEN_NORMAL);
+
+ g_object_unref (location);
+}
+
static GList *
build_selection_list_from_gfile_list (GList *gfile_list)
{
@@ -1271,7 +1283,6 @@ nautilus_window_set_up_sidebar (NautilusWindow *window)
(GTK_PLACES_OPEN_NORMAL
| GTK_PLACES_OPEN_NEW_TAB
| GTK_PLACES_OPEN_NEW_WINDOW));
- gtk_places_sidebar_set_show_connect_to_server (GTK_PLACES_SIDEBAR (window->priv->places_sidebar), TRUE);
g_signal_connect_swapped (window->priv->places_sidebar, "open-location",
G_CALLBACK (open_location_cb), window);
@@ -2534,6 +2545,8 @@ nautilus_window_class_init (NautilusWindowClass *class)
gtk_widget_class_bind_template_child_private (wclass, NautilusWindow, notification_operation_open);
gtk_widget_class_bind_template_child_private (wclass, NautilusWindow, notification_operation_close);
+ gtk_widget_class_bind_template_callback (wclass, places_sidebar_show_other_locations);
+
properties[PROP_DISABLE_CHROME] =
g_param_spec_boolean ("disable-chrome",
"Disable chrome",
diff --git a/src/nautilus-window.ui b/src/nautilus-window.ui
index 7e7a7f28a..b23e3a5a4 100644
--- a/src/nautilus-window.ui
+++ b/src/nautilus-window.ui
@@ -33,6 +33,8 @@
<object class="GtkPlacesSidebar" id="places_sidebar">
<property name="visible">True</property>
<property name="populate-all">True</property>
+ <property name="show-other-locations">True</property>
+ <signal name="show-other-locations" handler="places_sidebar_show_other_locations" object="NautilusWindow" swapped="yes" />
</object>
<packing>
<property name="pack_type">start</property>
diff --git a/src/nautilus.gresource.xml b/src/nautilus.gresource.xml
index 5a3e324e9..c663e73a8 100644
--- a/src/nautilus.gresource.xml
+++ b/src/nautilus.gresource.xml
@@ -16,6 +16,8 @@
<file>nautilus-window.ui</file>
<file>nautilus-no-search-results.ui</file>
<file>nautilus-folder-is-empty.ui</file>
+ <file>gtk/gtkplacesview.ui</file>
+ <file>gtk/gtkplacesviewrow.ui</file>
<file alias="icons/thumbnail_frame.png">../icons/thumbnail_frame.png</file>
<file alias="icons/filmholes.png">../icons/filmholes.png</file>
<file alias="icons/knob.png">../icons/knob.png</file>