[gtk+/wip/gbsneto/placessidebar-locations: 2/3] placesview: initial implementation



commit baab6a38b892ba9e1517954281fc6f96155582db
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Wed Jun 10 08:15:42 2015 -0300

    placesview: initial implementation

 gtk/Makefile.am            |    6 +
 gtk/gtkplacesview.c        |  954 ++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkplacesview.h        |   84 ++++
 gtk/gtkplacesviewrow.c     |  214 ++++++++++
 gtk/gtkplacesviewrow.h     |   43 ++
 gtk/ui/gtkplacesview.ui    |  307 ++++++++++++++
 gtk/ui/gtkplacesviewrow.ui |   67 +++
 7 files changed, 1675 insertions(+), 0 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 72ee47f..6046750 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -236,6 +236,7 @@ gtk_public_h_sources =              \
        gtkpaned.h              \
        gtkpapersize.h          \
        gtkplacessidebar.h      \
+       gtkplacesview.h         \
        gtkplug.h               \
        gtkpopover.h            \
        gtkpopovermenu.h        \
@@ -474,6 +475,7 @@ gtk_private_h_sources =             \
        gtkorientableprivate.h  \
        gtkpango.h              \
        gtkpathbar.h            \
+       gtkplacesviewrow.h      \
        gtkpopoverprivate.h     \
        gtkprintoperation-private.h \
        gtkprintutils.h         \
@@ -752,6 +754,8 @@ gtk_base_c_sources =                \
        gtkpapersize.c          \
        gtkpathbar.c            \
        gtkplacessidebar.c      \
+       gtkplacesview.c         \
+       gtkplacesviewrow.c      \
        gtkprintcontext.c       \
        gtkprintoperation.c     \
        gtkprintoperationpreview.c \
@@ -1081,6 +1085,8 @@ templates =                               \
        ui/gtkmessagedialog.ui  \
        ui/gtkpagesetupunixdialog.ui    \
        ui/gtkpathbar.ui                \
+       ui/gtkplacesview.ui             \
+       ui/gtkplacesviewrow.ui          \
        ui/gtkprintunixdialog.ui        \
        ui/gtkrecentchooserdefault.ui   \
        ui/gtksearchbar.ui              \
diff --git a/gtk/gtkplacesview.c b/gtk/gtkplacesview.c
new file mode 100644
index 0000000..edcb3ba
--- /dev/null
+++ b/gtk/gtkplacesview.c
@@ -0,0 +1,954 @@
+/* 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 <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include "gtkintl.h"
+#include "gtkplacesview.h"
+#include "gtkplacesviewrow.h"
+#include "gtktypebuiltins.h"
+
+struct _GtkPlacesViewPrivate
+{
+  GVolumeMonitor                *volume_monitor;
+  GtkPlacesOpenFlags             open_flags;
+
+  GCancellable                  *connection_cancellable;
+
+  GtkWidget                     *actionbar;
+  GtkWidget                     *address_entry;
+  GtkWidget                     *connect_button;
+  GtkWidget                     *drives_listbox;
+  GtkWidget                     *network_grid;
+  GtkWidget                     *network_listbox;
+  GtkWidget                     *recent_servers_listbox;
+
+  guint local_only             : 1;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtkPlacesView, gtk_places_view, GTK_TYPE_BOX)
+
+/* GtkPlacesView properties & signals */
+enum {
+  PROP_0,
+  PROP_LOCAL_ONLY,
+  LAST_PROP
+};
+
+enum {
+  OPEN_LOCATION,
+  LAST_SIGNAL
+};
+
+const char *unsupported_protocols [] =
+{
+  "file", "afc", "obex", "http",
+  "trash", "burn", "computer",
+  "archive", "recent", "localtest",
+  NULL
+};
+
+static guint places_view_signals [LAST_SIGNAL] = { 0 };
+static GParamSpec *properties [LAST_PROP];
+
+static void
+emit_open_location (GtkPlacesView      *view,
+                    GFile              *location,
+                    GtkPlacesOpenFlags  open_flags)
+{
+  GtkPlacesViewPrivate *priv;
+
+  g_return_if_fail (GTK_IS_PLACES_VIEW (view));
+
+  priv = view->priv;
+
+  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 GBookmarkFile *
+server_list_load (void)
+{
+       GBookmarkFile *bookmarks;
+       GError *error = NULL;
+       char *datadir;
+       char *filename;
+
+       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 != NULL)
+    {
+      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_error_free (error);
+      g_bookmark_file_free (bookmarks);
+      bookmarks = NULL;
+    }
+
+  g_free (datadir);
+  g_free (filename);
+
+       return bookmarks;
+}
+
+static void
+server_list_save (GBookmarkFile *bookmarks)
+{
+       char *filename;
+
+       filename = g_build_filename (g_get_user_config_dir (), "gtk-3.0", "servers", NULL);
+       g_bookmark_file_to_file (bookmarks, filename, NULL);
+       g_free (filename);
+}
+
+static gboolean
+gtk_places_view_real_get_local_only (GtkPlacesView *view)
+{
+  return view->priv->local_only;
+}
+
+static void
+gtk_places_view_real_set_local_only (GtkPlacesView *view,
+                                     gboolean       local_only)
+{
+  if (view->priv->local_only != local_only)
+    {
+      view->priv->local_only = local_only;
+
+      gtk_widget_set_visible (view->priv->actionbar, !local_only);
+      gtk_widget_set_visible (view->priv->network_grid, !local_only);
+
+      g_object_notify_by_pspec (G_OBJECT (view), properties [PROP_LOCAL_ONLY]);
+    }
+}
+
+static void
+gtk_places_view_finalize (GObject *object)
+{
+  GtkPlacesView *self = (GtkPlacesView *)object;
+  GtkPlacesViewPrivate *priv = gtk_places_view_get_instance_private (self);
+
+  g_clear_object (&priv->volume_monitor);
+
+  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;
+
+    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_external_device (GVolume *volume)
+{
+  gboolean external;
+  GDrive *drive;
+  GMount *mount;
+
+  external = FALSE;
+  drive = g_volume_get_drive (volume);
+  mount = g_volume_get_mount (volume);
+
+  if (drive)
+    {
+      external = g_drive_can_eject (drive);
+
+      if (volume)
+        external |= g_volume_can_eject (volume);
+
+      if (mount)
+        external |= g_mount_can_eject (mount) && !g_mount_can_unmount (mount);
+    }
+  else
+    {
+      /*
+       * If no GDrive is associated with the given volume, it is assured
+       * this is not an external device (e.g. USB sticks or external hard
+       * drives).
+       */
+      external = FALSE;
+    }
+
+  g_clear_object (&drive);
+  g_clear_object (&mount);
+
+  return external;
+}
+
+static void
+populate_servers (GtkPlacesView *view)
+{
+  GtkPlacesViewPrivate *priv;
+  GBookmarkFile *server_list;
+  gchar **uris;
+  gsize num_uris;
+  gint i;
+
+  g_return_if_fail (GTK_IS_PLACES_VIEW (view));
+
+  priv = view->priv;
+  server_list = server_list_load ();
+
+  if (!server_list)
+    return;
+
+  uris = g_bookmark_file_get_uris (server_list, &num_uris);
+
+  if (!uris)
+    {
+      g_bookmark_file_free (server_list);
+      return;
+    }
+
+  for (i = 0; i < num_uris; i++)
+    {
+      GtkWidget *row;
+      GtkWidget *label;
+      char *name;
+
+      name = g_bookmark_file_get_title (server_list, uris[i], NULL);
+
+      row = gtk_list_box_row_new ();
+
+      label = gtk_label_new (name);
+      gtk_widget_set_hexpand (label, TRUE);
+      gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+
+      gtk_container_add (GTK_CONTAINER (row), label);
+      gtk_container_add (GTK_CONTAINER (priv->recent_servers_listbox), row);
+
+      gtk_widget_show_all (row);
+
+                       g_free (name);
+    }
+
+  g_strfreev (uris);
+  g_bookmark_file_free (server_list);
+}
+
+static void
+add_volume (GtkPlacesView *view,
+            GVolume       *volume)
+{
+  GtkPlacesViewPrivate *priv;
+  gboolean is_network;
+  GDrive *drive;
+  GMount *mount;
+  GFile *root;
+  GIcon *icon;
+  gchar *identifier;
+  gchar *name;
+  gchar *path;
+
+  g_return_if_fail (GTK_IS_PLACES_VIEW (view));
+  g_return_if_fail (G_IS_VOLUME (volume));
+
+  priv = view->priv;
+
+  if (is_external_device (volume))
+    return;
+
+  drive = g_volume_get_drive (volume);
+
+  if (drive)
+    {
+      gboolean is_removable;
+
+      is_removable = g_drive_is_media_removable (drive);
+      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_root (mount) : NULL;
+  icon = g_volume_get_icon (volume);
+  name = g_volume_get_name (volume);
+
+  if (root)
+    path = is_network ? g_file_get_uri (root) : g_file_get_path (root);
+  else
+    path = 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,
+                      NULL);
+
+      if (is_network)
+        gtk_container_add (GTK_CONTAINER (priv->network_listbox), row);
+      else
+        gtk_container_add (GTK_CONTAINER (priv->drives_listbox), row);
+    }
+
+  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)
+{
+  GtkPlacesViewPrivate *priv;
+  gboolean is_network;
+  GVolume *volume;
+  GDrive *drive;
+  GFile *root;
+  GIcon *icon;
+  gchar *name;
+  gchar *path;
+
+  g_return_if_fail (GTK_IS_PLACES_VIEW (view));
+  g_return_if_fail (G_IS_MOUNT (mount));
+
+  priv = view->priv;
+
+  /* Don't add mounts with removable drives, sidebar will handle them */
+  drive = g_mount_get_drive (mount);
+  if (drive)
+    {
+      gboolean is_removable = g_drive_is_media_removable (drive);
+
+      g_object_unref (drive);
+
+      if (is_removable)
+        return;
+    }
+
+  /* Don't add mounts with a volume, as they'll be already added by add_volume */
+  volume = g_mount_get_volume (mount);
+  if (volume)
+    {
+      g_object_unref (volume);
+      return;
+    }
+
+  icon = g_mount_get_icon (mount);
+  name = g_mount_get_name (mount);
+  root = g_mount_get_root (mount);
+  is_network = root ? g_file_is_native (root) : FALSE;
+
+  if (root)
+    path = is_network ? g_file_get_uri (root) : g_file_get_path (root);
+  else
+    path = NULL;
+
+  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,
+                      NULL);
+
+      if (is_network)
+        gtk_container_add (GTK_CONTAINER (priv->network_listbox), row);
+      else
+        gtk_container_add (GTK_CONTAINER (priv->drives_listbox), row);
+    }
+
+  g_clear_object (&root);
+  g_clear_object (&icon);
+  g_free (name);
+  g_free (path);
+}
+
+static void
+add_drive (GtkPlacesView *view,
+           GDrive        *drive)
+{
+  GList *volumes;
+  GList *l;
+
+  g_return_if_fail (GTK_IS_PLACES_VIEW (view));
+  g_return_if_fail (G_IS_DRIVE (drive));
+
+  /* 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
+update_places (GtkPlacesView *view)
+{
+  GtkPlacesViewPrivate *priv;
+  GList *children;
+  GList *mounts;
+  GList *volumes;
+  GList *drives;
+  GList *l;
+
+  g_return_if_fail (GTK_IS_PLACES_VIEW (view));
+
+  priv = view->priv;
+
+  /* Clear all previously added items */
+  children = gtk_container_get_children (GTK_CONTAINER (priv->drives_listbox));
+  g_list_free_full (children, (GDestroyNotify) gtk_widget_destroy);
+
+  children = gtk_container_get_children (GTK_CONTAINER (priv->network_listbox));
+  g_list_free_full (children, (GDestroyNotify) gtk_widget_destroy);
+
+  children = gtk_container_get_children (GTK_CONTAINER (priv->recent_servers_listbox));
+  g_list_free_full (children, (GDestroyNotify) gtk_widget_destroy);
+
+  /* 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);
+
+  /* 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);
+
+  /* Add mounts that has no volume (/etc/mtab mounts, ftp, sftp,...) */
+  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);
+}
+
+static gboolean
+parse_error (GError *error)
+{
+  if (error->domain == G_IO_ERROR &&
+      error->code == G_IO_ERROR_ALREADY_MOUNTED)
+    {
+      return 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 */
+           g_warning ("Unable to access location: %s", error->message);
+      return FALSE;
+    }
+
+  return FALSE;
+}
+
+static void
+location_mount_ready_callback (GObject      *source_file,
+                               GAsyncResult *res,
+                               gpointer      user_data)
+{
+  GtkPlacesViewPrivate *priv;
+  gboolean should_show;
+  GError *error;
+  GFile *location;
+
+  g_return_if_fail (GTK_IS_PLACES_VIEW (user_data));
+  g_return_if_fail (G_IS_FILE (source_file));
+
+  priv = GTK_PLACES_VIEW (user_data)->priv;
+  location = G_FILE (source_file);
+  should_show = TRUE;
+  error = NULL;
+
+  g_clear_object (&priv->connection_cancellable);
+
+  g_file_mount_enclosing_volume_finish (location, res, &error);
+
+  if (error)
+    {
+      should_show = parse_error (error);
+      g_clear_error (&error);
+         }
+
+  if (should_show)
+    emit_open_location (GTK_PLACES_VIEW (user_data), location, GTK_PLACES_OPEN_NORMAL);
+}
+
+static void
+volume_mount_ready_callback (GObject      *source_volume,
+                             GAsyncResult *res,
+                             gpointer      user_data)
+{
+  GtkPlacesViewPrivate *priv;
+  gboolean should_show;
+  GVolume *volume;
+  GError *error;
+
+  g_return_if_fail (GTK_IS_PLACES_VIEW (user_data));
+  g_return_if_fail (G_IS_VOLUME (source_volume));
+
+  priv = GTK_PLACES_VIEW (user_data)->priv;
+  volume = G_VOLUME (source_volume);
+  should_show = TRUE;
+  error = NULL;
+
+  g_clear_object (&priv->connection_cancellable);
+
+  g_volume_mount_finish (volume, res, &error);
+
+  if (error)
+    {
+      should_show = parse_error (error);
+      g_clear_error (&error);
+         }
+
+  if (should_show)
+    {
+      GMount *mount;
+      GFile *root;
+
+      mount = g_volume_get_mount (volume);
+      root = g_mount_get_root (mount);
+
+      g_signal_emit (user_data, places_view_signals [OPEN_LOCATION], 0, root, GTK_PLACES_OPEN_NORMAL);
+
+      g_object_unref (mount);
+      g_object_unref (root);
+    }
+}
+
+static void
+mount_location (GtkPlacesView *view,
+                GFile         *location)
+{
+  GtkPlacesViewPrivate *priv;
+  GMountOperation *operation;
+  GtkWidget *toplevel;
+
+  g_return_if_fail (GTK_IS_PLACES_VIEW (view));
+
+  priv = view->priv;
+  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
+  operation = gtk_mount_operation_new (GTK_WINDOW (toplevel));
+
+  priv->connection_cancellable = g_cancellable_new ();
+
+  g_mount_operation_set_password_save (operation, G_PASSWORD_SAVE_FOR_SESSION);
+
+  g_file_mount_enclosing_volume (location,
+                                 0,
+                                 operation,
+                                 priv->connection_cancellable,
+                                 location_mount_ready_callback,
+                                 view);
+
+  /* unref operation here - g_file_mount_enclosing_volume() does ref for itself */
+  g_object_unref (operation);
+  g_object_unref (priv->connection_cancellable);
+}
+
+static void
+mount_volume (GtkPlacesView *view,
+              GVolume       *volume)
+{
+  GtkPlacesViewPrivate *priv;
+  GMountOperation *operation;
+  GtkWidget *toplevel;
+
+  g_return_if_fail (GTK_IS_PLACES_VIEW (view));
+
+  priv = view->priv;
+  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
+  operation = gtk_mount_operation_new (GTK_WINDOW (toplevel));
+
+  priv->connection_cancellable = g_cancellable_new ();
+
+  g_mount_operation_set_password_save (operation, G_PASSWORD_SAVE_FOR_SESSION);
+
+  g_volume_mount (volume,
+                  0,
+                  operation,
+                  priv->connection_cancellable,
+                  volume_mount_ready_callback,
+                  view);
+
+  /* unref operation here - g_file_mount_enclosing_volume() does ref for itself */
+  g_object_unref (operation);
+  g_object_unref (priv->connection_cancellable);
+}
+
+static void
+on_connect_button_clicked (GtkButton     *button,
+                           GtkPlacesView *view)
+{
+  GtkPlacesViewPrivate *priv;
+  const gchar *uri;
+  GFile *file;
+
+  g_return_if_fail (GTK_IS_PLACES_VIEW (view));
+
+  priv = view->priv;
+  file = NULL;
+
+       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)
+    {
+      g_warning ("Unable to get remote server location");
+      return;
+    }
+
+  mount_location (view, file);
+}
+
+static void
+on_address_entry_text_changed (GtkPlacesView *view)
+{
+  GtkPlacesViewPrivate *priv;
+  const gchar* const *supported_protocols;
+  gchar *address, *scheme;
+  gboolean supported;
+
+  g_return_if_fail (GTK_IS_PLACES_VIEW (view));
+
+  priv = view->priv;
+  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_listbox_row_activated (GtkPlacesView    *view,
+                          GtkPlacesViewRow *row)
+{
+  GVolume *volume;
+  GFile *location;
+  GMount *mount;
+
+  g_return_if_fail (GTK_IS_PLACES_VIEW (view));
+  g_return_if_fail (GTK_IS_PLACES_VIEW_ROW (row));
+
+  mount = gtk_places_view_row_get_mount (row);
+  volume = gtk_places_view_row_get_volume (row);
+
+  if (mount)
+    {
+      location = g_mount_get_root (mount);
+
+      emit_open_location (view, location, GTK_PLACES_OPEN_NORMAL);
+
+      g_object_unref (location);
+    }
+  else if (volume && g_volume_can_mount (volume))
+    {
+        mount_volume (view, volume);
+    }
+}
+
+static void
+gtk_places_view_constructed (GObject *object)
+{
+  GtkPlacesViewPrivate *priv = GTK_PLACES_VIEW (object)->priv;
+
+  if (G_OBJECT_CLASS (gtk_places_view_parent_class)->constructed)
+    G_OBJECT_CLASS (gtk_places_view_parent_class)->constructed (object);
+
+  /* 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_class_init (GtkPlacesViewClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  klass->set_local_only = gtk_places_view_real_set_local_only;
+  klass->get_local_only = gtk_places_view_real_get_local_only;
+
+  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;
+
+  /**
+   * GtkPlacesView::open-location:
+   * @view: the object which received the signal.
+   * @location: (type Gio.File): #GFile to which the caller should switch.
+   *
+   * 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 (I_("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);
+
+  properties[PROP_LOCAL_ONLY] =
+          g_param_spec_boolean ("local-only",
+                                P_("Local Only"),
+                                P_("Whether the sidebar only includes local files"),
+                                FALSE,
+                                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/gtk/libgtk/ui/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, connect_button);
+  gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, drives_listbox);
+  gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, network_grid);
+  gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, network_listbox);
+  gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, recent_servers_listbox);
+
+  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_listbox_row_activated);
+}
+
+static void
+gtk_places_view_init (GtkPlacesView *self)
+{
+  self->priv = gtk_places_view_get_instance_private (self);
+  self->priv->volume_monitor = g_volume_monitor_get ();
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+/**
+ * gtk_places_view_new:
+ *
+ * Creates a new #GtkPlacesView widget.
+ *
+ * The application should connect to at least the
+ * #GtkPlacesSidebar::open-location signal to be notified
+ * when the user makes a selection in the sidebar.
+ *
+ * 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_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)
+{
+  GtkPlacesViewClass *class;
+
+  g_return_val_if_fail (GTK_IS_PLACES_VIEW (view), FALSE);
+
+  class = GTK_PLACES_VIEW_GET_CLASS (view);
+
+  g_assert (class->get_local_only != NULL);
+  return class->get_local_only (view);
+}
+
+/**
+ * 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)
+{
+  GtkPlacesViewClass *class;
+
+  g_return_if_fail (GTK_IS_PLACES_VIEW (view));
+
+  class = GTK_PLACES_VIEW_GET_CLASS (view);
+
+  g_assert (class->get_local_only != NULL);
+  class->set_local_only (view, local_only);
+}
diff --git a/gtk/gtkplacesview.h b/gtk/gtkplacesview.h
new file mode 100644
index 0000000..7670338
--- /dev/null
+++ b/gtk/gtkplacesview.h
@@ -0,0 +1,84 @@
+/* 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
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gtk/gtkbox.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;
+
+  gboolean (* get_local_only)       (GtkPlacesView          *view);
+  void     (* set_local_only)       (GtkPlacesView          *view,
+                                     gboolean                local_only);
+
+  void     (* open_location)        (GtkPlacesView          *view,
+                                     GFile                  *location,
+                                     GtkPlacesOpenFlags  open_flags);
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  gpointer reserved[10];
+};
+
+struct _GtkPlacesView
+{
+  GtkBox parent_instance;
+
+  /*< private >*/
+
+  GtkPlacesViewPrivate *priv;
+};
+
+GDK_AVAILABLE_IN_3_18
+GType              gtk_places_view_get_type                      (void) G_GNUC_CONST;
+
+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
+GtkWidget *        gtk_places_view_new                           (void);
+
+G_END_DECLS
+
+#endif /* GTK_PLACES_VIEW_H */
diff --git a/gtk/gtkplacesviewrow.c b/gtk/gtkplacesviewrow.c
new file mode 100644
index 0000000..30edbbf
--- /dev/null
+++ b/gtk/gtkplacesviewrow.c
@@ -0,0 +1,214 @@
+/* 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 <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include "gtkintl.h"
+#include "gtkplacesviewrow.h"
+#include "gtktypebuiltins.h"
+
+struct _GtkPlacesViewRow
+{
+  GtkListBoxRow  parent_instance;
+
+  GtkImage      *icon_image;
+  GtkLabel      *name_label;
+  GtkLabel      *path_label;
+
+  GVolume       *volume;
+  GMount        *mount;
+};
+
+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,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+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;
+
+    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:
+      self->volume = g_value_get_object (value);
+      break;
+
+    case PROP_MOUNT:
+      self->mount = g_value_get_object (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->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",
+                               P_("Icon of the row"),
+                               P_("The icon representing the volume"),
+                               G_TYPE_ICON,
+                               G_PARAM_READWRITE);
+
+  properties[PROP_NAME] =
+          g_param_spec_string ("name",
+                               P_("Name of the volume"),
+                               P_("The name of the volume"),
+                               "",
+                               G_PARAM_READWRITE);
+
+  properties[PROP_PATH] =
+          g_param_spec_string ("path",
+                               P_("Path of the volume"),
+                               P_("The path of the volume"),
+                               "",
+                               G_PARAM_READWRITE);
+
+  properties[PROP_VOLUME] =
+          g_param_spec_object ("volume",
+                               P_("Volume represented by the row"),
+                               P_("The volume represented by the row"),
+                               G_TYPE_VOLUME,
+                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+  properties[PROP_MOUNT] =
+          g_param_spec_object ("mount",
+                               P_("Mount represented by the row"),
+                               P_("The mount point represented by the row, if any"),
+                               G_TYPE_MOUNT,
+                               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/gtk/libgtk/ui/gtkplacesviewrow.ui");
+
+  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;
+}
diff --git a/gtk/gtkplacesviewrow.h b/gtk/gtkplacesviewrow.h
new file mode 100644
index 0000000..2746a35
--- /dev/null
+++ b/gtk/gtkplacesviewrow.h
@@ -0,0 +1,43 @@
+/* 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
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gtk/gtkwidget.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);
+
+GMount*            gtk_places_view_row_get_mount                 (GtkPlacesViewRow   *row);
+
+GVolume*           gtk_places_view_row_get_volume                (GtkPlacesViewRow   *row);
+
+G_END_DECLS
+
+#endif /* GTK_PLACES_VIEW_ROW_H */
diff --git a/gtk/ui/gtkplacesview.ui b/gtk/ui/gtkplacesview.ui
new file mode 100644
index 0000000..e3f005a
--- /dev/null
+++ b/gtk/ui/gtkplacesview.ui
@@ -0,0 +1,307 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.16"/>
+  <object class="GtkPopover" id="recent_servers_popover">
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkBox" id="box">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="border_width">12</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">12</property>
+        <child>
+          <object class="GtkLabel" id="recent_servers_title_label">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label" translatable="yes">&lt;b&gt;Recent Servers&lt;/b&gt;</property>
+            <property name="use_markup">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow" id="recent_servers_list_scroll">
+            <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" id="recent_servers_list_viewport">
+                <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">False</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</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>
+    <child>
+      <object class="GtkScrolledWindow" id="scrolled_window">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="hexpand">True</property>
+        <property name="vexpand">True</property>
+        <child>
+          <object class="GtkViewport" id="viewport">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="shadow_type">none</property>
+            <child>
+              <object class="GtkBox" id="main_box">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="hexpand">True</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkBox" id="drives_box">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <object class="GtkLabel" id="drives_title_label">
+                        <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="label" translatable="yes">&lt;b&gt;This Computer&lt;/b&gt;</property>
+                        <property name="use_markup">True</property>
+                        <property name="xalign">0</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkSeparator" id="separator1">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkListBox" id="drives_listbox">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="selection_mode">none</property>
+                        <signal name="row-activated" handler="on_listbox_row_activated" 
object="GtkPlacesView" swapped="yes" />
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">2</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="GtkGrid" id="network_grid">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="column_spacing">12</property>
+                    <child>
+                      <object class="GtkLabel" id="network_title_label">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="margin_start">12</property>
+                        <property name="margin_top">6</property>
+                        <property name="margin_bottom">6</property>
+                        <property name="hexpand">False</property>
+                        <property name="label" translatable="yes">&lt;b&gt;Network&lt;/b&gt;</property>
+                        <property name="use_markup">True</property>
+                        <property name="ellipsize">end</property>
+                        <property name="xalign">0</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkSpinner" id="network_spinner">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">start</property>
+                        <property name="hexpand">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkSeparator" id="separator2">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">1</property>
+                        <property name="width">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkListBox" id="network_listbox">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="selection_mode">none</property>
+                        <signal name="row-activated" handler="on_listbox_row_activated" 
object="GtkPlacesView" swapped="yes" />
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">2</property>
+                        <property name="width">2</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+            <style>
+              <class name="view"/>
+            </style>
+          </object>
+        </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="can_focus">False</property>
+        <property name="hexpand">True</property>
+        <child>
+          <object class="GtkLabel" id="connect_to_server_label">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label" translatable="yes">&lt;b&gt;Connect to Server&lt;/b&gt;</property>
+            <property name="use_markup">True</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkButton" id="connect_button">
+            <property name="label" translatable="yes">Connect</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="no" />
+          </object>
+          <packing>
+            <property name="pack_type">end</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="address_box">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="hexpand">True</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">For example: 
smb://foo.example.org</property>
+                <signal name="notify::text" handler="on_address_entry_text_changed" 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" id="arrow_up_image">
+                    <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>
+        <child>
+          <object class="GtkLabel" id="address_label">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="hexpand">True</property>
+            <property name="label" translatable="yes">Address</property>
+            <property name="xalign">1</property>
+          </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/gtk/ui/gtkplacesviewrow.ui b/gtk/ui/gtkplacesviewrow.ui
new file mode 100644
index 0000000..c17a1ed
--- /dev/null
+++ b/gtk/ui/gtkplacesviewrow.ui
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.1 -->
+<interface>
+  <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="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>
+            <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>
+    </child>
+  </template>
+</interface>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]