gtkplacessidebar.c 175 KB
Newer Older
1
/* GtkPlacesSidebar - sidebar widget for places in the filesystem
2
 *
3 4 5 6
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
7
 *
8 9 10 11
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
12
 *
13 14
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
15
 *
16
 * This code is originally from Nautilus.
17
 *
18 19 20 21
 * Authors : Mr Jamie McCracken (jamiemcc at blueyonder dot co dot uk)
 *           Cosimo Cecchi <cosimoc@gnome.org>
 *           Federico Mena Quintero <federico@gnome.org>
 *           Carlos Soriano <csoriano@gnome.org>
22
 */
23

24
#include "config.h"
25

26
#include <gio/gio.h>
27
#ifdef HAVE_CLOUDPROVIDERS
28 29 30
#include <cloudproviders/cloudproviderscollector.h>
#include <cloudproviders/cloudprovidersaccount.h>
#include <cloudproviders/cloudprovidersprovider.h>
31
#endif
32

33 34
#include "gtkplacessidebarprivate.h"
#include "gtksidebarrowprivate.h"
35
#include "gdk/gdkkeysyms.h"
36
#include "gtkbookmarksmanager.h"
37 38 39
#include "gtkcelllayout.h"
#include "gtkcellrenderertext.h"
#include "gtkcellrendererpixbuf.h"
40
#include "gtkfilesystem.h"
41 42 43
#include "gtkicontheme.h"
#include "gtkintl.h"
#include "gtkmain.h"
44
#include "gtkmarshalers.h"
45 46
#include "gtkmenuitem.h"
#include "gtkmountoperation.h"
47 48
#include "gtkplacessidebar.h"
#include "gtkscrolledwindow.h"
49
#include "gtkseparatormenuitem.h"
50
#include "gtksettings.h"
51
#include "gtktrashmonitor.h"
52
#include "gtktypebuiltins.h"
53
#include "gtkwindow.h"
54 55 56 57
#include "gtkpopover.h"
#include "gtkgrid.h"
#include "gtklabel.h"
#include "gtkbutton.h"
58 59
#include "gtklistbox.h"
#include "gtkselection.h"
60
#include "gtkdragdest.h"
61 62 63
#include "gtkdnd.h"
#include "gtkseparator.h"
#include "gtkentry.h"
64 65 66
#include "gtkgesturelongpress.h"
#include "gtkbox.h"
#include "gtkmodelbutton.h"
67

68 69 70 71 72 73 74
/**
 * SECTION:gtkplacessidebar
 * @Short_description: Sidebar that displays frequently-used places in the file system
 * @Title: GtkPlacesSidebar
 * @See_also: #GtkFileChooser
 *
 * #GtkPlacesSidebar is a widget that displays a list of frequently-used places in the
75
 * file system:  the user’s home directory, the user’s bookmarks, and volumes and drives.
76 77 78 79 80 81 82 83 84 85 86 87 88
 * This widget is used as a sidebar in #GtkFileChooser and may be used by file managers
 * and similar programs.
 *
 * The places sidebar displays drives and volumes, and will automatically mount
 * or unmount them when the user selects them.
 *
 * Applications can hook to various signals in the places sidebar to customize
 * its behavior.  For example, they can add extra commands to the context menu
 * of the sidebar.
 *
 * While bookmarks are completely in control of the user, the places sidebar also
 * allows individual applications to provide extra shortcut folders that are unique
 * to each application.  For example, a Paint program may want to add a shortcut
89
 * for a Clipart folder.  You can do this with gtk_places_sidebar_add_shortcut().
90 91 92 93 94 95
 *
 * To make use of the places sidebar, an application at least needs to connect
 * to the #GtkPlacesSidebar::open-location signal.  This is emitted when the
 * user selects in the sidebar a location to open.  The application should also
 * call gtk_places_sidebar_set_location() when it changes the currently-viewed
 * location.
96 97 98
 *
 * # CSS nodes
 *
99
 * GtkPlacesSidebar uses a single CSS node with name placessidebar and style
100
 * class .sidebar.
101 102 103 104 105 106
 *
 * Among the children of the places sidebar, the following style classes can
 * be used:
 * - .sidebar-new-bookmark-row for the 'Add new bookmark' row
 * - .sidebar-placeholder-row for a row that is a placeholder
 * - .has-open-popup when a popup is open for a row
107 108
 */

Matthias Clasen's avatar
Matthias Clasen committed
109
/* These are used when a destination-side DND operation is taking place.
110 111 112 113 114
 * Normally, when a common drag action is taking place, the state will be
 * DROP_STATE_NEW_BOOKMARK_ARMED, however, if the client of GtkPlacesSidebar
 * wants to show hints about the valid targets, we sill set it as
 * DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT, so the sidebar will show drop hints
 * until the client says otherwise
115 116
 */
typedef enum {
Matthias Clasen's avatar
Matthias Clasen committed
117 118
  DROP_STATE_NORMAL,
  DROP_STATE_NEW_BOOKMARK_ARMED,
119
  DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT,
120 121
} DropState;

122
struct _GtkPlacesSidebar {
Matthias Clasen's avatar
Matthias Clasen committed
123
  GtkScrolledWindow parent;
124

125 126 127
  GtkWidget *list_box;
  GtkWidget *new_bookmark_row;

Matthias Clasen's avatar
Matthias Clasen committed
128
  GtkBookmarksManager     *bookmarks_manager;
129

130
#ifdef HAVE_CLOUDPROVIDERS
131 132
  CloudProvidersCollector *cloud_manager;
  GList *unready_accounts;
133
#endif
134

Matthias Clasen's avatar
Matthias Clasen committed
135 136
  GVolumeMonitor    *volume_monitor;
  GtkTrashMonitor   *trash_monitor;
137
  GtkSettings       *gtk_settings;
138
  GFile             *current_location;
139

140 141 142 143 144 145
  GtkWidget *rename_popover;
  GtkWidget *rename_entry;
  GtkWidget *rename_button;
  GtkWidget *rename_error;
  gchar *rename_uri;

Matthias Clasen's avatar
Matthias Clasen committed
146
  gulong trash_monitor_changed_id;
147
  GtkWidget *trash_row;
148

Matthias Clasen's avatar
Matthias Clasen committed
149 150 151
  /* DND */
  GList     *drag_list; /* list of GFile */
  gint       drag_data_info;
152
  gboolean   dragging_over;
153 154 155 156 157 158 159 160 161
  GtkTargetList *source_targets;
  GtkWidget *drag_row;
  gint drag_row_height;
  gint drag_row_x;
  gint drag_row_y;
  gint drag_root_x;
  gint drag_root_y;
  GtkWidget *row_placeholder;
  DropState drop_state;
162
  GtkGesture *long_press_gesture;
163

Matthias Clasen's avatar
Matthias Clasen committed
164 165
  /* volume mounting - delayed open process */
  GtkPlacesOpenFlags go_to_after_mount_open_flags;
166
  GCancellable *cancellable;
167

168 169
  GtkWidget *popover;
  GtkSidebarRow *context_row;
Matthias Clasen's avatar
Matthias Clasen committed
170
  GSList *shortcuts;
171

Matthias Clasen's avatar
Matthias Clasen committed
172 173 174
  GDBusProxy *hostnamed_proxy;
  GCancellable *hostnamed_cancellable;
  gchar *hostname;
175

Matthias Clasen's avatar
Matthias Clasen committed
176
  GtkPlacesOpenFlags open_flags;
177

178 179
  GActionGroup *action_group;

Matthias Clasen's avatar
Matthias Clasen committed
180 181
  guint mounting               : 1;
  guint  drag_data_received    : 1;
Matthias Clasen's avatar
Matthias Clasen committed
182
  guint drop_occurred          : 1;
183 184
  guint show_recent_set        : 1;
  guint show_recent            : 1;
185
  guint show_desktop_set       : 1;
Matthias Clasen's avatar
Matthias Clasen committed
186 187
  guint show_desktop           : 1;
  guint show_connect_to_server : 1;
188
  guint show_enter_location    : 1;
189
  guint show_other_locations   : 1;
190
  guint show_trash             : 1;
191
  guint show_starred_location  : 1;
192
  guint local_only             : 1;
193
  guint populate_all           : 1;
194
};
195

196
struct _GtkPlacesSidebarClass {
Matthias Clasen's avatar
Matthias Clasen committed
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
  GtkScrolledWindowClass parent;

  void    (* open_location)          (GtkPlacesSidebar   *sidebar,
                                      GFile              *location,
                                      GtkPlacesOpenFlags  open_flags);
  void    (* populate_popup)         (GtkPlacesSidebar   *sidebar,
                                      GtkMenu            *menu,
                                      GFile              *selected_item,
                                      GVolume            *selected_volume);
  void    (* show_error_message)     (GtkPlacesSidebar   *sidebar,
                                      const gchar        *primary,
                                      const gchar        *secondary);
  void    (* show_connect_to_server) (GtkPlacesSidebar   *sidebar);
  GdkDragAction (* drag_action_requested)  (GtkPlacesSidebar   *sidebar,
                                      GdkDragContext     *context,
                                      GFile              *dest_file,
                                      GList              *source_file_list);
  GdkDragAction (* drag_action_ask)  (GtkPlacesSidebar   *sidebar,
                                      GdkDragAction       actions);
  void    (* drag_perform_drop)      (GtkPlacesSidebar   *sidebar,
                                      GFile              *dest_file,
                                      GList              *source_file_list,
                                      GdkDragAction       action);
220
  void    (* show_enter_location)    (GtkPlacesSidebar   *sidebar);
221 222

  void    (* show_other_locations)   (GtkPlacesSidebar   *sidebar);
223

224 225 226
  void    (* show_other_locations_with_flags)   (GtkPlacesSidebar   *sidebar,
                                                 GtkPlacesOpenFlags  open_flags);

227 228
  void    (* show_starred_location)    (GtkPlacesSidebar   *sidebar);

229 230 231 232
  void    (* mount)                  (GtkPlacesSidebar   *sidebar,
                                      GMountOperation    *mount_operation);
  void    (* unmount)                (GtkPlacesSidebar   *sidebar,
                                      GMountOperation    *unmount_operation);
233
};
234

235
enum {
Matthias Clasen's avatar
Matthias Clasen committed
236 237 238 239
  OPEN_LOCATION,
  POPULATE_POPUP,
  SHOW_ERROR_MESSAGE,
  SHOW_CONNECT_TO_SERVER,
240
  SHOW_ENTER_LOCATION,
Matthias Clasen's avatar
Matthias Clasen committed
241 242 243
  DRAG_ACTION_REQUESTED,
  DRAG_ACTION_ASK,
  DRAG_PERFORM_DROP,
244
  SHOW_OTHER_LOCATIONS,
245
  SHOW_OTHER_LOCATIONS_WITH_FLAGS,
246
  SHOW_STARRED_LOCATION,
247 248
  MOUNT,
  UNMOUNT,
Matthias Clasen's avatar
Matthias Clasen committed
249
  LAST_SIGNAL
250 251
};

252
enum {
Matthias Clasen's avatar
Matthias Clasen committed
253 254
  PROP_LOCATION = 1,
  PROP_OPEN_FLAGS,
255
  PROP_SHOW_RECENT,
Matthias Clasen's avatar
Matthias Clasen committed
256 257
  PROP_SHOW_DESKTOP,
  PROP_SHOW_CONNECT_TO_SERVER,
258
  PROP_SHOW_ENTER_LOCATION,
259
  PROP_SHOW_TRASH,
260
  PROP_SHOW_STARRED_LOCATION,
261
  PROP_LOCAL_ONLY,
262
  PROP_SHOW_OTHER_LOCATIONS,
263
  PROP_POPULATE_ALL,
Matthias Clasen's avatar
Matthias Clasen committed
264
  NUM_PROPERTIES
265 266
};

267
/* Names for themed icons */
Matthias Clasen's avatar
Matthias Clasen committed
268
#define ICON_NAME_HOME     "user-home-symbolic"
269
#define ICON_NAME_DESKTOP  "user-desktop-symbolic"
Matthias Clasen's avatar
Matthias Clasen committed
270 271 272
#define ICON_NAME_FILESYSTEM     "drive-harddisk-symbolic"
#define ICON_NAME_EJECT    "media-eject-symbolic"
#define ICON_NAME_NETWORK  "network-workgroup-symbolic"
273
#define ICON_NAME_NETWORK_SERVER "network-server-symbolic"
274
#define ICON_NAME_FOLDER_NETWORK "folder-remote-symbolic"
275
#define ICON_NAME_OTHER_LOCATIONS "list-add-symbolic"
276

277
#define ICON_NAME_FOLDER                "folder-symbolic"
278
#define ICON_NAME_FOLDER_DESKTOP  "user-desktop-symbolic"
Matthias Clasen's avatar
Matthias Clasen committed
279 280 281 282 283 284 285 286
#define ICON_NAME_FOLDER_DOCUMENTS      "folder-documents-symbolic"
#define ICON_NAME_FOLDER_DOWNLOAD       "folder-download-symbolic"
#define ICON_NAME_FOLDER_MUSIC    "folder-music-symbolic"
#define ICON_NAME_FOLDER_PICTURES       "folder-pictures-symbolic"
#define ICON_NAME_FOLDER_PUBLIC_SHARE   "folder-publicshare-symbolic"
#define ICON_NAME_FOLDER_TEMPLATES      "folder-templates-symbolic"
#define ICON_NAME_FOLDER_VIDEOS   "folder-videos-symbolic"
#define ICON_NAME_FOLDER_SAVED_SEARCH   "folder-saved-search-symbolic"
287

288
static guint places_sidebar_signals [LAST_SIGNAL] = { 0 };
289
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
290

291 292 293 294 295 296 297 298 299 300 301 302 303
static gboolean eject_or_unmount_bookmark  (GtkSidebarRow *row);
static gboolean eject_or_unmount_selection (GtkPlacesSidebar *sidebar);
static void  check_unmount_and_eject       (GMount   *mount,
                                            GVolume  *volume,
                                            GDrive   *drive,
                                            gboolean *show_unmount,
                                            gboolean *show_eject);
static gboolean on_button_press_event (GtkWidget      *widget,
                                       GdkEventButton *event,
                                       GtkSidebarRow  *sidebar);
static gboolean on_button_release_event (GtkWidget      *widget,
                                         GdkEventButton *event,
                                         GtkSidebarRow  *sidebar);
304 305 306 307 308
static void popup_menu_cb    (GtkSidebarRow   *row);
static void long_press_cb    (GtkGesture      *gesture,
                              gdouble          x,
                              gdouble          y,
                              GtkPlacesSidebar *sidebar);
309
static void stop_drop_feedback (GtkPlacesSidebar *sidebar);
310 311
static GMountOperation * get_mount_operation (GtkPlacesSidebar *sidebar);
static GMountOperation * get_unmount_operation (GtkPlacesSidebar *sidebar);
312

313 314 315

/* Identifiers for target types */
enum {
316 317 318
  DND_UNKNOWN,
  DND_GTK_SIDEBAR_ROW,
  DND_TEXT_URI_LIST
319 320 321
};

/* Target types for dragging from the shortcuts list */
322
static const GtkTargetEntry dnd_source_targets[] = {
323
  { "DND_GTK_SIDEBAR_ROW", GTK_TARGET_SAME_WIDGET, DND_GTK_SIDEBAR_ROW }
324 325 326
};

/* Target types for dropping into the shortcuts list */
327
static const GtkTargetEntry dnd_drop_targets [] = {
328
  { "DND_GTK_SIDEBAR_ROW", GTK_TARGET_SAME_WIDGET, DND_GTK_SIDEBAR_ROW }
329 330
};

331
G_DEFINE_TYPE (GtkPlacesSidebar, gtk_places_sidebar, GTK_TYPE_SCROLLED_WINDOW);
332

333
static void
Matthias Clasen's avatar
Matthias Clasen committed
334 335 336
emit_open_location (GtkPlacesSidebar   *sidebar,
                    GFile              *location,
                    GtkPlacesOpenFlags  open_flags)
337
{
Matthias Clasen's avatar
Matthias Clasen committed
338 339
  if ((open_flags & sidebar->open_flags) == 0)
    open_flags = GTK_PLACES_OPEN_NORMAL;
340

Matthias Clasen's avatar
Matthias Clasen committed
341 342
  g_signal_emit (sidebar, places_sidebar_signals[OPEN_LOCATION], 0,
                 location, open_flags);
343 344
}

345
static void
Matthias Clasen's avatar
Matthias Clasen committed
346 347 348
emit_show_error_message (GtkPlacesSidebar *sidebar,
                         const gchar      *primary,
                         const gchar      *secondary)
349
{
Matthias Clasen's avatar
Matthias Clasen committed
350 351
  g_signal_emit (sidebar, places_sidebar_signals[SHOW_ERROR_MESSAGE], 0,
                 primary, secondary);
352 353
}

354 355 356
static void
emit_show_connect_to_server (GtkPlacesSidebar *sidebar)
{
Matthias Clasen's avatar
Matthias Clasen committed
357
  g_signal_emit (sidebar, places_sidebar_signals[SHOW_CONNECT_TO_SERVER], 0);
358 359
}

360 361 362 363 364 365
static void
emit_show_enter_location (GtkPlacesSidebar *sidebar)
{
  g_signal_emit (sidebar, places_sidebar_signals[SHOW_ENTER_LOCATION], 0);
}

366
static void
367
emit_show_other_locations (GtkPlacesSidebar *sidebar)
368
{
369
  g_signal_emit (sidebar, places_sidebar_signals[SHOW_OTHER_LOCATIONS], 0);
370 371
}

372 373 374 375 376 377 378 379
static void
emit_show_other_locations_with_flags (GtkPlacesSidebar   *sidebar,
                                      GtkPlacesOpenFlags  open_flags)
{
  g_signal_emit (sidebar, places_sidebar_signals[SHOW_OTHER_LOCATIONS_WITH_FLAGS],
                 0, open_flags);
}

380 381 382 383 384 385 386 387 388
static void
emit_show_starred_location (GtkPlacesSidebar  *sidebar,
                            GtkPlacesOpenFlags open_flags)
{
  g_signal_emit (sidebar, places_sidebar_signals[SHOW_STARRED_LOCATION], 0,
                 open_flags);
}


389 390 391 392 393 394 395 396 397 398 399 400 401 402
static void
emit_mount_operation (GtkPlacesSidebar *sidebar,
                      GMountOperation  *mount_op)
{
  g_signal_emit (sidebar, places_sidebar_signals[MOUNT], 0, mount_op);
}

static void
emit_unmount_operation (GtkPlacesSidebar *sidebar,
                        GMountOperation  *mount_op)
{
  g_signal_emit (sidebar, places_sidebar_signals[UNMOUNT], 0, mount_op);
}

403
static GdkDragAction
404
emit_drag_action_requested (GtkPlacesSidebar *sidebar,
Matthias Clasen's avatar
Matthias Clasen committed
405 406 407
                            GdkDragContext   *context,
                            GFile            *dest_file,
                            GList            *source_file_list)
408
{
Matthias Clasen's avatar
Matthias Clasen committed
409
  GdkDragAction ret_action = 0;
410

Matthias Clasen's avatar
Matthias Clasen committed
411 412
  g_signal_emit (sidebar, places_sidebar_signals[DRAG_ACTION_REQUESTED], 0,
                 context, dest_file, source_file_list, &ret_action);
413

Matthias Clasen's avatar
Matthias Clasen committed
414
  return ret_action;
415 416
}

417 418
static GdkDragAction
emit_drag_action_ask (GtkPlacesSidebar *sidebar,
Matthias Clasen's avatar
Matthias Clasen committed
419
                      GdkDragAction     actions)
420
{
Matthias Clasen's avatar
Matthias Clasen committed
421
  GdkDragAction ret_action = 0;
422

Matthias Clasen's avatar
Matthias Clasen committed
423 424
  g_signal_emit (sidebar, places_sidebar_signals[DRAG_ACTION_ASK], 0,
                 actions, &ret_action);
425

Matthias Clasen's avatar
Matthias Clasen committed
426
  return ret_action;
427 428
}

429 430
static void
emit_drag_perform_drop (GtkPlacesSidebar *sidebar,
Matthias Clasen's avatar
Matthias Clasen committed
431 432 433
                        GFile            *dest_file,
                        GList            *source_file_list,
                        GdkDragAction     action)
434
{
Matthias Clasen's avatar
Matthias Clasen committed
435 436
  g_signal_emit (sidebar, places_sidebar_signals[DRAG_PERFORM_DROP], 0,
                 dest_file, source_file_list, action);
437
}
438 439 440 441
static void
list_box_header_func (GtkListBoxRow *row,
                      GtkListBoxRow *before,
                      gpointer       user_data)
442
{
443 444 445
  GtkPlacesSidebarSectionType row_section_type;
  GtkPlacesSidebarSectionType before_section_type;
  GtkWidget *separator;
446

447
  gtk_list_box_row_set_header (row, NULL);
448

449 450
  g_object_get (row, "section-type", &row_section_type, NULL);
  if (before)
Matthias Clasen's avatar
Matthias Clasen committed
451
    {
452 453 454 455 456 457 458
      g_object_get (before, "section-type", &before_section_type, NULL);
    }
  else
    {
      before_section_type = SECTION_INVALID;
      gtk_widget_set_margin_top (GTK_WIDGET (row), 4);
    }
Matthias Clasen's avatar
Matthias Clasen committed
459

460 461 462 463 464 465 466
  if (before && before_section_type != row_section_type)
    {
      separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
      gtk_widget_set_margin_top (separator, 4);
      gtk_widget_set_margin_bottom (separator, 4);
      gtk_list_box_row_set_header (row, separator);
    }
467 468
}

469 470 471 472 473
static GtkWidget*
add_place (GtkPlacesSidebar            *sidebar,
           GtkPlacesSidebarPlaceType    place_type,
           GtkPlacesSidebarSectionType  section_type,
           const gchar                 *name,
474 475
           GIcon                       *start_icon,
           GIcon                       *end_icon,
476 477 478 479
           const gchar                 *uri,
           GDrive                      *drive,
           GVolume                     *volume,
           GMount                      *mount,
480
#ifdef HAVE_CLOUDPROVIDERS
481
           CloudProvidersAccount       *cloud_provider_account,
482
#else
483
           gpointer                    *cloud_provider_account,
484
#endif
485 486 487
           const gint                   index,
           const gchar                 *tooltip)
{
Matthias Clasen's avatar
Matthias Clasen committed
488 489
  gboolean show_eject, show_unmount;
  gboolean show_eject_button;
490 491 492
  GtkWidget *row;
  GtkWidget *eject_button;
  GtkWidget *event_box;
Matthias Clasen's avatar
Matthias Clasen committed
493 494 495 496 497 498 499

  check_unmount_and_eject (mount, volume, drive,
                           &show_unmount, &show_eject);

  if (show_unmount || show_eject)
    g_assert (place_type != PLACES_BOOKMARK);

500
  show_eject_button = (show_unmount || show_eject);
Matthias Clasen's avatar
Matthias Clasen committed
501

502 503
  row = g_object_new (GTK_TYPE_SIDEBAR_ROW,
                      "sidebar", sidebar,
504 505
                      "start-icon", start_icon,
                      "end-icon", end_icon,
506
                      "label", name,
507
                      "tooltip", tooltip,
508 509 510 511 512 513 514 515
                      "ejectable", show_eject_button,
                      "order-index", index,
                      "section-type", section_type,
                      "place-type", place_type,
                      "uri", uri,
                      "drive", drive,
                      "volume", volume,
                      "mount", mount,
516
#ifdef HAVE_CLOUDPROVIDERS
517
                      "cloud-provider-account", cloud_provider_account,
518
#endif
519
                      NULL);
520

521 522 523 524 525 526 527 528 529
  eject_button = gtk_sidebar_row_get_eject_button (GTK_SIDEBAR_ROW (row));
  event_box = gtk_sidebar_row_get_event_box (GTK_SIDEBAR_ROW (row));

  g_signal_connect_swapped (eject_button, "clicked",
                            G_CALLBACK (eject_or_unmount_bookmark), row);
  g_signal_connect (event_box, "button-press-event",
                    G_CALLBACK (on_button_press_event), row);
  g_signal_connect (event_box, "button-release-event",
                    G_CALLBACK (on_button_release_event), row);
530 531 532 533 534

  gtk_container_add (GTK_CONTAINER (sidebar->list_box), GTK_WIDGET (row));
  gtk_widget_show_all (row);

  return row;
535 536
}

537
static GIcon *
538
special_directory_get_gicon (GUserDirectory directory)
539
{
Matthias Clasen's avatar
Matthias Clasen committed
540 541 542
#define ICON_CASE(x)                      \
  case G_USER_DIRECTORY_ ## x:                                    \
          return g_themed_icon_new_with_default_fallbacks (ICON_NAME_FOLDER_ ## x);
543

Matthias Clasen's avatar
Matthias Clasen committed
544 545
  switch (directory)
    {
546

547
    ICON_CASE (DESKTOP);
Matthias Clasen's avatar
Matthias Clasen committed
548 549 550 551 552 553 554
    ICON_CASE (DOCUMENTS);
    ICON_CASE (DOWNLOAD);
    ICON_CASE (MUSIC);
    ICON_CASE (PICTURES);
    ICON_CASE (PUBLIC_SHARE);
    ICON_CASE (TEMPLATES);
    ICON_CASE (VIDEOS);
555

Matthias Clasen's avatar
Matthias Clasen committed
556
    default:
557
      return g_themed_icon_new_with_default_fallbacks (ICON_NAME_FOLDER);
Matthias Clasen's avatar
Matthias Clasen committed
558
    }
559 560 561 562 563

#undef ICON_CASE
}

static gboolean
564 565
recent_files_setting_is_enabled (GtkPlacesSidebar *sidebar)
{
Matthias Clasen's avatar
Matthias Clasen committed
566 567 568
  GtkSettings *settings;
  gboolean enabled;

569
  settings = gtk_widget_get_settings (GTK_WIDGET (sidebar));
Matthias Clasen's avatar
Matthias Clasen committed
570
  g_object_get (settings, "gtk-recent-files-enabled", &enabled, NULL);
571

Matthias Clasen's avatar
Matthias Clasen committed
572
  return enabled;
573 574 575 576
}

static gboolean
recent_scheme_is_supported (void)
577
{
Matthias Clasen's avatar
Matthias Clasen committed
578
  const gchar * const *supported;
579

Matthias Clasen's avatar
Matthias Clasen committed
580
  supported = g_vfs_get_supported_uri_schemes (g_vfs_get_default ());
581 582
  if (supported != NULL)
    return g_strv_contains (supported, "recent");
Matthias Clasen's avatar
Matthias Clasen committed
583 584

  return FALSE;
585 586
}

587 588 589
static gboolean
should_show_recent (GtkPlacesSidebar *sidebar)
{
590 591 592
  return recent_files_setting_is_enabled (sidebar) &&
         ((sidebar->show_recent_set && sidebar->show_recent) ||
          (!sidebar->show_recent_set && recent_scheme_is_supported ()));
593 594
}

595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
static gboolean
path_is_home_dir (const gchar *path)
{
  GFile *home_dir;
  GFile *location;
  const gchar *home_path;
  gboolean res;

  home_path = g_get_home_dir ();
  if (!home_path)
    return FALSE;

  home_dir = g_file_new_for_path (home_path);
  location = g_file_new_for_path (path);
  res = g_file_equal (home_dir, location);

  g_object_unref (home_dir);
  g_object_unref (location);

  return res;
}

617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
static void
open_home (GtkPlacesSidebar *sidebar)
{
  const gchar *home_path;
  GFile       *home_dir;

  home_path = g_get_home_dir ();
  if (!home_path)
    return;

  home_dir = g_file_new_for_path (home_path);
  emit_open_location (sidebar, home_dir, 0);

  g_object_unref (home_dir);
}

633 634 635
static void
add_special_dirs (GtkPlacesSidebar *sidebar)
{
Matthias Clasen's avatar
Matthias Clasen committed
636 637 638 639 640 641 642 643
  GList *dirs;
  gint index;

  dirs = NULL;
  for (index = 0; index < G_USER_N_DIRECTORIES; index++)
    {
      const gchar *path;
      GFile *root;
644
      GIcon *start_icon;
Matthias Clasen's avatar
Matthias Clasen committed
645 646 647 648 649 650 651 652 653 654 655 656 657 658
      gchar *name;
      gchar *mount_uri;
      gchar *tooltip;

      if (!_gtk_bookmarks_manager_get_is_xdg_dir_builtin (index))
        continue;

      path = g_get_user_special_dir (index);

      /* XDG resets special dirs to the home directory in case
       * it's not finiding what it expects. We don't want the home
       * to be added multiple times in that weird configuration.
       */
      if (path == NULL ||
659
          path_is_home_dir (path) ||
Matthias Clasen's avatar
Matthias Clasen committed
660 661 662 663 664 665 666 667 668
          g_list_find_custom (dirs, path, (GCompareFunc) g_strcmp0) != NULL)
        continue;

      root = g_file_new_for_path (path);

      name = _gtk_bookmarks_manager_get_bookmark_label (sidebar->bookmarks_manager, root);
      if (!name)
        name = g_file_get_basename (root);

669
      start_icon = special_directory_get_gicon (index);
Matthias Clasen's avatar
Matthias Clasen committed
670 671 672 673 674
      mount_uri = g_file_get_uri (root);
      tooltip = g_file_get_parse_name (root);

      add_place (sidebar, PLACES_XDG_DIR,
                 SECTION_COMPUTER,
675 676
                 name, start_icon, NULL, mount_uri,
                 NULL, NULL, NULL, NULL, 0,
Matthias Clasen's avatar
Matthias Clasen committed
677 678 679
                 tooltip);
      g_free (name);
      g_object_unref (root);
680
      g_object_unref (start_icon);
Matthias Clasen's avatar
Matthias Clasen committed
681 682 683 684 685 686 687 688 689 690
      g_free (mount_uri);
      g_free (tooltip);

      dirs = g_list_prepend (dirs, (gchar *)path);
    }

  g_list_free (dirs);
}

static gchar *
691 692
get_home_directory_uri (void)
{
Matthias Clasen's avatar
Matthias Clasen committed
693
  const gchar *home;
694

Matthias Clasen's avatar
Matthias Clasen committed
695 696 697
  home = g_get_home_dir ();
  if (!home)
    return NULL;
698

699
  return g_filename_to_uri (home, NULL, NULL);
700 701
}

Matthias Clasen's avatar
Matthias Clasen committed
702
static gchar *
703
get_desktop_directory_uri (void)
704
{
Matthias Clasen's avatar
Matthias Clasen committed
705 706 707 708 709 710 711
  const gchar *name;

  name = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);

  /* "To disable a directory, point it to the homedir."
   * See http://freedesktop.org/wiki/Software/xdg-user-dirs
   */
712
  if (path_is_home_dir (name))
Matthias Clasen's avatar
Matthias Clasen committed
713
    return NULL;
714

715
  return g_filename_to_uri (name, NULL, NULL);
716 717
}

718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
static gboolean
should_show_file (GtkPlacesSidebar *sidebar,
                  GFile            *file)
{
  gchar *path;

  if (!sidebar->local_only)
    return TRUE;

  path = g_file_get_path (file);
  if (path)
    {
      g_free (path);
      return TRUE;
    }

  return FALSE;
}

737 738 739 740 741
static gboolean
file_is_shown (GtkPlacesSidebar *sidebar,
               GFile            *file)
{
  gchar *uri;
742 743 744
  GList *rows;
  GList *l;
  gboolean found = FALSE;
745

746 747 748
  rows = gtk_container_get_children (GTK_CONTAINER (sidebar->list_box));
  l = rows;
  while (l != NULL && !found)
749
    {
750
      g_object_get (l->data, "uri", &uri, NULL);
751 752 753 754 755 756
      if (uri)
        {
          GFile *other;
          other = g_file_new_for_uri (uri);
          found = g_file_equal (file, other);
          g_object_unref (other);
757
          g_free (uri);
758
        }
759
      l = l->next;
760 761
    }

762 763 764
  g_list_free (rows);

  return found;
765 766
}

767 768 769 770 771 772 773 774 775 776 777
static void
on_app_shortcuts_query_complete (GObject      *source,
                                 GAsyncResult *result,
                                 gpointer      data)
{
  GtkPlacesSidebar *sidebar = data;
  GFile *file = G_FILE (source);
  GFileInfo *info;

  info = g_file_query_info_finish (file, result, NULL);

778
  if (info)
779 780 781 782
    {
      gchar *uri;
      gchar *tooltip;
      const gchar *name;
783
      GIcon *start_icon;
784 785 786
      int pos = 0;

      name = g_file_info_get_display_name (info);
787
      start_icon = g_file_info_get_symbolic_icon (info);
788 789 790 791 792 793 794 795 796 797 798 799
      uri = g_file_get_uri (file);
      tooltip = g_file_get_parse_name (file);

      /* XXX: we could avoid this by using an ancillary closure
       * with the index coming from add_application_shortcuts(),
       * but in terms of algorithmic overhead, the application
       * shortcuts is not going to be really big
       */
      pos = g_slist_index (sidebar->shortcuts, file);

      add_place (sidebar, PLACES_BUILT_IN,
                 SECTION_COMPUTER,
800 801
                 name, start_icon, NULL, uri,
                 NULL, NULL, NULL, NULL,
802 803 804 805 806 807 808 809 810 811
                 pos,
                 tooltip);

      g_free (uri);
      g_free (tooltip);

      g_object_unref (info);
    }
}

812 813 814
static void
add_application_shortcuts (GtkPlacesSidebar *sidebar)
{
Matthias Clasen's avatar
Matthias Clasen committed
815
  GSList *l;
816

Matthias Clasen's avatar
Matthias Clasen committed
817 818
  for (l = sidebar->shortcuts; l; l = l->next)
    {
819
      GFile *file = l->data;
820

821 822 823
      if (!should_show_file (sidebar, file))
        continue;

824 825 826
      if (file_is_shown (sidebar, file))
        continue;

827 828 829 830
      g_file_query_info_async (file,
                               "standard::display-name,standard::symbolic-icon",
                               G_FILE_QUERY_INFO_NONE,
                               G_PRIORITY_DEFAULT,
831
                               sidebar->cancellable,
832 833
                               on_app_shortcuts_query_complete,
                               sidebar);
Matthias Clasen's avatar
Matthias Clasen committed
834
    }
835 836
}

837 838 839 840 841 842 843
typedef struct {
  GtkPlacesSidebar *sidebar;
  int index;
  gboolean is_native;
} BookmarkQueryClosure;

static void
844
on_bookmark_query_info_complete (GObject      *source,
845 846 847 848 849 850
                                 GAsyncResult *result,
                                 gpointer      data)
{
  BookmarkQueryClosure *clos = data;
  GtkPlacesSidebar *sidebar = clos->sidebar;
  GFile *root = G_FILE (source);
851
  GError *error = NULL;
852 853 854 855
  GFileInfo *info;
  gchar *bookmark_name;
  gchar *mount_uri;
  gchar *tooltip;
856
  GIcon *start_icon;
857

858 859 860
  info = g_file_query_info_finish (root, result, &error);
  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
    goto out;
861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876

  bookmark_name = _gtk_bookmarks_manager_get_bookmark_label (sidebar->bookmarks_manager, root);
  if (bookmark_name == NULL && info != NULL)
    bookmark_name = g_strdup (g_file_info_get_display_name (info));
  else if (bookmark_name == NULL)
    {
      /* Don't add non-UTF-8 bookmarks */
      bookmark_name = g_file_get_basename (root);
      if (!g_utf8_validate (bookmark_name, -1, NULL))
        {
          g_free (bookmark_name);
          goto out;
        }
    }

  if (info)
877
    start_icon = g_object_ref (g_file_info_get_symbolic_icon (info));
878
  else
879
    start_icon = g_themed_icon_new_with_default_fallbacks (clos->is_native ? ICON_NAME_FOLDER : ICON_NAME_FOLDER_NETWORK);
880 881 882 883 884 885

  mount_uri = g_file_get_uri (root);
  tooltip = g_file_get_parse_name (root);

  add_place (sidebar, PLACES_BOOKMARK,
             SECTION_BOOKMARKS,
886 887
             bookmark_name, start_icon, NULL, mount_uri,
             NULL, NULL, NULL, NULL, clos->index,
888 889 890 891 892
             tooltip);

  g_free (mount_uri);
  g_free (tooltip);
  g_free (bookmark_name);
893
  g_object_unref (start_icon);
894 895 896

out:
  g_clear_object (&info);
897
  g_clear_error (&error);
898 899 900
  g_slice_free (BookmarkQueryClosure, clos);
}

901
static gboolean
902
is_external_volume (GVolume *volume)
903
{
904
  gboolean is_external;
905 906 907 908 909 910
  GDrive *drive;
  gchar *id;

  drive = g_volume_get_drive (volume);
  id = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);

911
  is_external = g_volume_can_eject (volume);
912 913

  /* NULL volume identifier only happens on removable devices */
914
  is_external |= !id;
915 916

  if (drive)
917
    is_external |= g_drive_is_removable (drive);
918 919 920 921

  g_clear_object (&drive);
  g_free (id);

922
  return is_external;
923 924
}

925 926 927 928
static void
update_trash_icon (GtkPlacesSidebar *sidebar)
{
  if (sidebar->trash_row)
929 930 931 932
    {
      GIcon *icon;

      icon = _gtk_trash_monitor_get_icon (sidebar->trash_monitor);
933
      gtk_sidebar_row_set_start_icon (GTK_SIDEBAR_ROW (sidebar->trash_row), icon);
934 935
      g_object_unref (icon);
    }
936 937
}

938
#ifdef HAVE_CLOUDPROVIDERS
939 940 941 942

static gboolean
create_cloud_provider_account_row (GtkPlacesSidebar      *sidebar,
                                   CloudProvidersAccount *account)
943 944
{
  GIcon *end_icon;
945 946 947 948 949 950 951 952 953 954 955 956 957 958
  GIcon *start_icon;
  gchar *mount_uri;
  gchar *name;
  gchar *tooltip;
  guint provider_account_status;

  start_icon = cloud_providers_account_get_icon (account);
  name = cloud_providers_account_get_name (account);
  provider_account_status = cloud_providers_account_get_status (account);
  mount_uri = cloud_providers_account_get_path (account);
  if (start_icon != NULL
      && name != NULL
      && provider_account_status != CLOUD_PROVIDERS_ACCOUNT_STATUS_INVALID
      && mount_uri != NULL)
959
    {
960 961 962 963 964
      switch (provider_account_status)
        {
          case CLOUD_PROVIDERS_ACCOUNT_STATUS_IDLE:
            end_icon = NULL;
          break;
965

966 967 968
          case CLOUD_PROVIDERS_ACCOUNT_STATUS_SYNCING:
            end_icon = g_themed_icon_new ("emblem-synchronizing-symbolic");
          break;
969

970 971 972
          case CLOUD_PROVIDERS_ACCOUNT_STATUS_ERROR:
            end_icon = g_themed_icon_new ("dialog-warning-symbolic");
          break;
973

974 975 976
          default:
            return FALSE;
        }
977

978
      mount_uri = g_strconcat ("file://", mount_uri, NULL);
979

980 981
      /* translators: %s is the name of a cloud provider for files */
      tooltip = g_strdup_printf (_("Open %s"), name);
982

983 984 985 986 987
      add_place (sidebar, PLACES_BUILT_IN,
                 SECTION_CLOUD,
                 name, start_icon, end_icon, mount_uri,
                 NULL, NULL, NULL, account, 0,
                 tooltip);
988

989 990 991
      return TRUE;
    }
  else
992
    {
993
      return FALSE;
994 995
    }
}
996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012

static void
on_account_updated (GObject    *object,
                    GParamSpec *pspec,
                    gpointer    user_data)
{
    CloudProvidersAccount *account = CLOUD_PROVIDERS_ACCOUNT (object);
    GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (user_data);

    if (create_cloud_provider_account_row (sidebar, account))
      {
          g_signal_handlers_disconnect_by_data (account, sidebar);
          sidebar->unready_accounts = g_list_remove (sidebar->unready_accounts, account);
          g_object_unref (account);
      }
}

1013 1014
#endif

1015
static void
1016
update_places (GtkPlacesSidebar *sidebar)
1017
{
Matthias Clasen's avatar
Matthias Clasen committed
1018 1019 1020 1021 1022 1023 1024 1025 1026
  GList *mounts, *l, *ll;
  GMount *mount;
  GList *drives;
  GDrive *drive;
  GList *volumes;
  GVolume *volume;
  GSList *bookmarks, *sl;
  gint index;
  gchar *original_uri, *mount_uri, *name, *identifier;
1027
  GtkListBoxRow *selected;
Matthias Clasen's avatar
Matthias Clasen committed
1028
  gchar *home_uri;
1029
  GIcon *start_icon;
Matthias Clasen's avatar
Matthias Clasen committed
1030 1031 1032
  GFile *root;
  gchar *tooltip;
  GList *network_mounts, *network_volumes;
1033
  GIcon *new_bookmark_icon;
1034
#ifdef HAVE_CLOUDPROVIDERS
1035 1036 1037 1038
  GList *cloud_providers;
  GList *cloud_providers_accounts;
  CloudProvidersAccount *cloud_provider_account;
  CloudProvidersProvider *cloud_provider;
1039
#endif
1040
  GtkStyleContext *context;
Matthias Clasen's avatar
Matthias Clasen committed
1041 1042

  /* save original selection */
1043 1044
  selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box));
  if (selected)
1045
    g_object_get (selected, "uri", &original_uri, NULL);
Matthias Clasen's avatar
Matthias Clasen committed
1046 1047 1048
  else
    original_uri = NULL;

1049 1050 1051 1052 1053
  g_cancellable_cancel (sidebar->cancellable);

  g_object_unref (sidebar->cancellable);
  sidebar->cancellable = g_cancellable_new ();

1054 1055 1056 1057 1058 1059
  /* Reset drag state, just in case we update the places while dragging or
   * ending a drag */
  stop_drop_feedback (sidebar);
  gtk_container_foreach (GTK_CONTAINER (sidebar->list_box),
                         (GtkCallback) gtk_widget_destroy,
                         NULL);
Matthias Clasen's avatar
Matthias Clasen committed
1060 1061 1062

  network_mounts = network_volumes = NULL;

1063
  /* add built-in places */
Matthias Clasen's avatar
Matthias Clasen committed
1064 1065 1066
  if (should_show_recent (sidebar))
    {
      mount_uri = "recent:///";
1067
      start_icon = g_themed_icon_new_with_default_fallbacks ("document-open-recent-symbolic");
Matthias Clasen's avatar
Matthias Clasen committed
1068 1069
      add_place (sidebar, PLACES_BUILT_IN,
                 SECTION_COMPUTER,
1070 1071
                 _("Recent"), start_icon, NULL, mount_uri,
                 NULL, NULL, NULL, NULL, 0,
Matthias Clasen's avatar
Matthias Clasen committed
1072
                 _("Recent files"));
1073
      g_object_unref (start_icon);
Matthias Clasen's avatar
Matthias Clasen committed
1074 1075
    }

1076 1077
  if (sidebar->show_starred_location)
    {
1078
      mount_uri = "starred:///";
1079 1080 1081 1082 1083
      start_icon = g_themed_icon_new_with_default_fallbacks ("starred-symbolic");
      add_place (sidebar, PLACES_STARRED_LOCATION,
                 SECTION_COMPUTER,
                 _("Starred"), start_icon, NULL, mount_uri,
                 NULL, NULL, NULL, NULL, 0,
1084
                /* TODO: Rename to 'Starred files' */
1085 1086 1087 1088
                 _("Favorite files"));
      g_object_unref (start_icon);
    }

Matthias Clasen's avatar
Matthias Clasen committed
1089 1090
  /* home folder */
  home_uri = get_home_directory_uri ();
1091
  start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_HOME);
Matthias Clasen's avatar
Matthias Clasen committed
1092 1093
  add_place (sidebar, PLACES_BUILT_IN,
             SECTION_COMPUTER,
1094 1095
             _("Home"), start_icon, NULL, home_uri,
             NULL, NULL, NULL, NULL, 0,
Matthias Clasen's avatar
Matthias Clasen committed
1096
             _("Open your personal folder"));
1097
  g_object_unref (start_icon);
Matthias Clasen's avatar
Matthias Clasen committed
1098 1099 1100 1101 1102 1103
  g_free (home_uri);

  /* desktop */
  if (sidebar->show_desktop)
    {
      mount_uri = get_desktop_directory_uri ();
1104 1105
      if (mount_uri)
        {
1106
          start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_DESKTOP);
1107 1108
          add_place (sidebar, PLACES_BUILT_IN,
                     SECTION_COMPUTER,
1109 1110
                     _("Desktop"), start_icon, NULL, mount_uri,
                     NULL, NULL, NULL, NULL, 0,
1111
                     _("Open the contents of your desktop in a folder"));
1112
          g_object_unref (start_icon);
1113 1114
          g_free (mount_uri);
        }
Matthias Clasen's avatar
Matthias Clasen committed
1115 1116 1117 1118 1119
    }

  /* XDG directories */
  add_special_dirs (sidebar);

1120 1121
  if (sidebar->show_enter_location)
    {
1122
      start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_NETWORK_SERVER);
1123 1124
      add_place (sidebar, PLACES_ENTER_LOCATION,
                 SECTION_COMPUTER,
1125 1126
                 _("Enter Location"), start_icon, NULL, NULL,
                 NULL, NULL, NULL, NULL, 0,
1127
                 _("Manually enter a location"));
1128
      g_object_unref (start_icon);
1129 1130
    }

Matthias Clasen's avatar
Matthias Clasen committed
1131
  /* Trash */
1132
  if (!sidebar->local_only && sidebar->show_trash)
1133
    {
1134
      start_icon = _gtk_trash_monitor_get_icon (sidebar->trash_monitor);
1135 1136
      sidebar->trash_row = add_place (sidebar, PLACES_BUILT_IN,
                                      SECTION_COMPUTER,
1137 1138
                                      _("Trash"), start_icon, NULL, "trash:///",
                                      NULL, NULL, NULL, NULL, 0,
1139
                                      _("Open the trash"));
1140 1141
      g_object_add_weak_pointer (G_OBJECT (sidebar->trash_row),
                                 (gpointer *) &sidebar->trash_row);
1142
      g_object_unref (start_icon);
1143
    }
Matthias Clasen's avatar
Matthias Clasen committed
1144 1145 1146 1147

  /* Application-side shortcuts */
  add_application_shortcuts (sidebar);

1148 1149
  /* Cloud providers */
#ifdef HAVE_CLOUDPROVIDERS
1150 1151
  cloud_providers = cloud_providers_collector_get_providers (sidebar->cloud_manager);
  for (l = sidebar->unready_accounts; l != NULL; l = l->next)
1152
    {
1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163
        g_signal_handlers_disconnect_by_data (l->data, sidebar);
    }
  g_list_free_full (sidebar->unready_accounts, g_object_unref);
  sidebar->unready_accounts = NULL;
  for (l = cloud_providers; l != NULL; l = l->next)
    {
      cloud_provider = CLOUD_PROVIDERS_PROVIDER (l->data);
      g_signal_connect_swapped (cloud_provider, "accounts-changed",
                                G_CALLBACK (update_places), sidebar);
      cloud_providers_accounts = cloud_providers_provider_get_accounts (cloud_provider);
      for (ll = cloud_providers_accounts; ll != NULL; ll = ll->next)
1164
        {
1165 1166 1167
          cloud_provider_account = CLOUD_PROVIDERS_ACCOUNT (ll->data);
          if (!create_cloud_provider_account_row (sidebar, cloud_provider_account))
            {
1168

1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180
              g_signal_connect (cloud_provider_account, "notify::name",
                                G_CALLBACK (on_account_updated), sidebar);
              g_signal_connect (cloud_provider_account, "notify::status",
                                G_CALLBACK (on_account_updated), sidebar);
              g_signal_connect (cloud_provider_account, "notify::status-details",
                                G_CALLBACK (on_account_updated), sidebar);
              g_signal_connect (cloud_provider_account, "notify::path",
                                G_CALLBACK (on_account_updated), sidebar);
              sidebar->unready_accounts = g_list_append (sidebar->unready_accounts,
                                                         g_object_ref (cloud_provider_account));
              continue;
            }
1181 1182 1183 1184 1185

        }
    }
#endif

Matthias Clasen's avatar
Matthias Clasen committed
1186
  /* go through all connected drives */
1187
  drives = g_volume_monitor_get_connected_drives (sidebar->volume_monitor);
Matthias Clasen's avatar
Matthias Clasen committed
1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208

  for (l = drives; l != NULL; l = l->next)
    {
      drive = l->data;

      volumes = g_drive_get_volumes (drive);
      if (volumes != NULL)
        {
          for (ll = volumes; ll != NULL; ll = ll->next)
            {
              volume = ll->data;
              identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);

              if (g_strcmp0 (identifier, "network") == 0)
                {
                  g_free (identifier);
                  network_volumes = g_list_prepend (network_volumes, volume);
                  continue;
                }
              g_free (identifier);

1209
              if (sidebar->show_other_locations && !is_external_volume (volume))
1210 1211 1212 1213 1214
                {
                  g_object_unref (volume);
                  continue;
                }

Matthias Clasen's avatar
Matthias Clasen committed
1215 1216 1217 1218
              mount = g_volume_get_mount (volume);
              if (mount != NULL)
                {
                  /* Show mounted volume in the sidebar */
1219
                  start_icon = g_mount_get_symbolic_icon (mount);
Matthias Clasen's avatar
Matthias Clasen committed
1220 1221 1222 1223 1224 1225
                  root = g_mount_get_default_location (mount);
                  mount_uri = g_file_get_uri (root);
                  name = g_mount_get_name (mount);
                  tooltip = g_file_get_parse_name (root);

                  add_place (sidebar, PLACES_MOUNTED_VOLUME,
1226
                             SECTION_MOUNTS,
1227 1228
                             name, start_icon, NULL, mount_uri,
                             drive, volume, mount, NULL, 0, tooltip);
Matthias Clasen's avatar
Matthias Clasen committed
1229 1230
                  g_object_unref (root);
                  g_object_unref (mount);
1231
                  g_object_unref (start_icon);
Matthias Clasen's avatar
Matthias Clasen committed
1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245
                  g_free (tooltip);
                  g_free (name);
                  g_free (mount_uri);
                }
              else
                {
                  /* Do show the unmounted volumes in the sidebar;
                   * this is so the user can mount it (in case automounting
                   * is off).
                   *
                   * Also, even if automounting is enabled, this gives a visual
                   * cue that the user should remember to yank out the media if
                   * he just unmounted it.
                   */
1246
                  start_icon = g_volume_get_symbolic_icon (volume);
Matthias Clasen's avatar
Matthias Clasen committed
1247
                  name = g_volume_get_name (volume);
1248
                  tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
Matthias Clasen's avatar
Matthias Clasen committed
1249 1250

                  add_place (sidebar, PLACES_MOUNTED_VOLUME,
1251
                             SECTION_MOUNTS,
1252 1253 1254
                             name, start_icon, NULL, NULL,
                             drive, volume, NULL, NULL, 0, tooltip);
                  g_object_unref (start_icon);
Matthias Clasen's avatar
Matthias Clasen committed
1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273
                  g_free (name);
                  g_free (tooltip);
                }
              g_object_unref (volume);
            }
          g_list_free (volumes);
        }
      else
        {
          if (g_drive_is_media_removable (drive) && !g_drive_is_media_check_automatic (drive))
            {
              /* If the drive has no mountable volumes and we cannot detect media change.. we
               * display the drive in the sidebar so the user can manually poll the drive by
               * right clicking and selecting "Rescan..."
               *
               * This is mainly for drives like floppies where media detection doesn't
               * work.. but it's also for human beings who like to turn off media detection
               * in the OS to save battery juice.
               */
1274
              start_icon = g_drive_get_symbolic_icon (drive);
Matthias Clasen's avatar
Matthias Clasen committed
1275
              name = g_drive_get_name (drive);
1276
              tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
Matthias Clasen's avatar
Matthias Clasen committed
1277 1278

              add_place (sidebar, PLACES_BUILT_IN,
1279
                         SECTION_MOUNTS,
1280 1281 1282
                         name, start_icon, NULL, NULL,
                         drive, NULL, NULL, NULL, 0, tooltip);
              g_object_unref (start_icon);
Matthias Clasen's avatar
Matthias Clasen committed
1283 1284 1285 1286 1287
              g_free (tooltip);
              g_free (name);
            }
        }
    }
1288
  g_list_free_full (drives, g_object_unref);
Matthias Clasen's avatar
Matthias Clasen committed
1289

1290
  /* add all network volumes that is not associated with a drive */
1291
  volumes = g_volume_monitor_get_volumes (sidebar->volume_monitor);
Matthias Clasen's avatar
Matthias Clasen committed
1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312
  for (l = volumes; l != NULL; l = l->next)
    {
      volume = l->data;
      drive = g_volume_get_drive (volume);
      if (drive != NULL)
        {
          g_object_unref (volume);
          g_object_unref (drive);
          continue;
        }

      identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);

      if (g_strcmp0 (identifier, "network") == 0)
        {
          g_free (identifier);
          network_volumes = g_list_prepend (network_volumes, volume);
          continue;
        }
      g_free (identifier);

1313
      if (sidebar->show_other_locations && !is_external_volume (volume))
1314 1315 1316 1317 1318
        {
          g_object_unref (volume);
          continue;
        }

Matthias Clasen's avatar
Matthias Clasen committed
1319 1320 1321
      mount = g_volume_get_mount (volume);
      if (mount != NULL)
        {
1322 1323
          char *mount_uri;
          start_icon = g_mount_get_symbolic_icon (mount);
Matthias Clasen's avatar
Matthias Clasen committed
1324 1325 1326 1327 1328
          root = g_mount_get_default_location (mount);
          mount_uri = g_file_get_uri (root);
          tooltip = g_file_get_parse_name (root);
          name = g_mount_get_name (mount);
          add_place (sidebar, PLACES_MOUNTED_VOLUME,
1329
                     SECTION_MOUNTS,
1330 1331
                     name, start_icon, NULL, mount_uri,
                     NULL, volume, mount, NULL, 0, tooltip);
Matthias Clasen's avatar
Matthias Clasen committed
1332 1333
          g_object_unref (mount);
          g_object_unref (root);