gtkplacessidebar.c 157 KB
Newer Older
1
/*
2 3
 *  GtkPlacesSidebar - sidebar widget for places in the filesystem
 *
4
 *  This code comes from Nautilus, GNOME’s file manager.
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License as
 *  published by the Free Software Foundation; either version 2 of the
 *  License, or (at your option) any later version.
 *
 *  This 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
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this library; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  Authors : Mr Jamie McCracken (jamiemcc at blueyonder dot co dot uk)
 *            Cosimo Cecchi <cosimoc@gnome.org>
22
 *            Federico Mena Quintero <federico@gnome.org>
23
 *            Carlos Soriano <csoriano@gnome.org>
24 25
 *
 */
26

27
#include "config.h"
28

29 30
#include <gio/gio.h>

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

65 66 67 68 69 70 71
/**
 * 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
72
 * file system:  the user’s home directory, the user’s bookmarks, and volumes and drives.
73 74 75 76 77 78 79 80 81 82 83 84 85
 * 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
86
 * for a Clipart folder.  You can do this with gtk_places_sidebar_add_shortcut().
87 88 89 90 91 92 93 94
 *
 * 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.
 */

Matthias Clasen's avatar
Matthias Clasen committed
95
/* These are used when a destination-side DND operation is taking place.
96 97 98 99 100
 * 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
101 102
 */
typedef enum {
Matthias Clasen's avatar
Matthias Clasen committed
103 104
  DROP_STATE_NORMAL,
  DROP_STATE_NEW_BOOKMARK_ARMED,
105
  DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT,
106 107
} DropState;

108
struct _GtkPlacesSidebar {
Matthias Clasen's avatar
Matthias Clasen committed
109
  GtkScrolledWindow parent;
110

111 112 113
  GtkWidget *list_box;
  GtkWidget *new_bookmark_row;

Matthias Clasen's avatar
Matthias Clasen committed
114 115 116
  GtkBookmarksManager     *bookmarks_manager;
  GVolumeMonitor    *volume_monitor;
  GtkTrashMonitor   *trash_monitor;
117
  GtkSettings       *gtk_settings;
118
  GFile             *current_location;
119

120 121 122 123 124 125
  GtkWidget *rename_popover;
  GtkWidget *rename_entry;
  GtkWidget *rename_button;
  GtkWidget *rename_error;
  gchar *rename_uri;

Matthias Clasen's avatar
Matthias Clasen committed
126
  gulong trash_monitor_changed_id;
127

Matthias Clasen's avatar
Matthias Clasen committed
128 129 130
  /* DND */
  GList     *drag_list; /* list of GFile */
  gint       drag_data_info;
131
  gboolean   dragging_over;
132 133 134 135 136 137 138 139 140
  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;
141
  GtkGesture *long_press_gesture;
142

Matthias Clasen's avatar
Matthias Clasen committed
143 144
  /* volume mounting - delayed open process */
  GtkPlacesOpenFlags go_to_after_mount_open_flags;
145
  GCancellable *cancellable;
146

147 148
  GtkWidget *popover;
  GtkSidebarRow *context_row;
Matthias Clasen's avatar
Matthias Clasen committed
149
  GSList *shortcuts;
150

Matthias Clasen's avatar
Matthias Clasen committed
151 152 153
  GDBusProxy *hostnamed_proxy;
  GCancellable *hostnamed_cancellable;
  gchar *hostname;
154

Matthias Clasen's avatar
Matthias Clasen committed
155
  GtkPlacesOpenFlags open_flags;
156

Matthias Clasen's avatar
Matthias Clasen committed
157 158 159
  guint mounting               : 1;
  guint  drag_data_received    : 1;
  guint drop_occured           : 1;
160 161
  guint show_recent_set        : 1;
  guint show_recent            : 1;
162
  guint show_desktop_set       : 1;
Matthias Clasen's avatar
Matthias Clasen committed
163 164
  guint show_desktop           : 1;
  guint show_connect_to_server : 1;
165
  guint show_enter_location    : 1;
166
  guint show_other_locations   : 1;
167
  guint show_trash             : 1;
168
  guint local_only             : 1;
169
  guint populate_all           : 1;
170
};
171

172
struct _GtkPlacesSidebarClass {
Matthias Clasen's avatar
Matthias Clasen committed
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
  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);
196
  void    (* show_enter_location)    (GtkPlacesSidebar   *sidebar);
197 198

  void    (* show_other_locations)   (GtkPlacesSidebar   *sidebar);
199
};
200

201
enum {
Matthias Clasen's avatar
Matthias Clasen committed
202 203 204 205
  OPEN_LOCATION,
  POPULATE_POPUP,
  SHOW_ERROR_MESSAGE,
  SHOW_CONNECT_TO_SERVER,
206
  SHOW_ENTER_LOCATION,
Matthias Clasen's avatar
Matthias Clasen committed
207 208 209
  DRAG_ACTION_REQUESTED,
  DRAG_ACTION_ASK,
  DRAG_PERFORM_DROP,
210
  SHOW_OTHER_LOCATIONS,
Matthias Clasen's avatar
Matthias Clasen committed
211
  LAST_SIGNAL
212 213
};

214
enum {
Matthias Clasen's avatar
Matthias Clasen committed
215 216
  PROP_LOCATION = 1,
  PROP_OPEN_FLAGS,
217
  PROP_SHOW_RECENT,
Matthias Clasen's avatar
Matthias Clasen committed
218 219
  PROP_SHOW_DESKTOP,
  PROP_SHOW_CONNECT_TO_SERVER,
220
  PROP_SHOW_ENTER_LOCATION,
221
  PROP_SHOW_TRASH,
222
  PROP_LOCAL_ONLY,
223
  PROP_SHOW_OTHER_LOCATIONS,
224
  PROP_POPULATE_ALL,
Matthias Clasen's avatar
Matthias Clasen committed
225
  NUM_PROPERTIES
226 227
};

228
/* Names for themed icons */
Matthias Clasen's avatar
Matthias Clasen committed
229
#define ICON_NAME_HOME     "user-home-symbolic"
230
#define ICON_NAME_DESKTOP  "user-desktop-symbolic"
Matthias Clasen's avatar
Matthias Clasen committed
231 232 233
#define ICON_NAME_FILESYSTEM     "drive-harddisk-symbolic"
#define ICON_NAME_EJECT    "media-eject-symbolic"
#define ICON_NAME_NETWORK  "network-workgroup-symbolic"
234
#define ICON_NAME_NETWORK_SERVER "network-server-symbolic"
235
#define ICON_NAME_FOLDER_NETWORK "folder-remote-symbolic"
236
#define ICON_NAME_OTHER_LOCATIONS "list-add-symbolic"
237

238
#define ICON_NAME_FOLDER                "folder-symbolic"
239
#define ICON_NAME_FOLDER_DESKTOP  "user-desktop-symbolic"
Matthias Clasen's avatar
Matthias Clasen committed
240 241 242 243 244 245 246 247
#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"
248

249
static guint places_sidebar_signals [LAST_SIGNAL] = { 0 };
250
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
251

252 253 254 255 256 257 258 259 260 261 262 263 264
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);
265 266 267 268 269
static void popup_menu_cb    (GtkSidebarRow   *row);
static void long_press_cb    (GtkGesture      *gesture,
                              gdouble          x,
                              gdouble          y,
                              GtkPlacesSidebar *sidebar);
270 271
static void stop_drop_feedback (GtkPlacesSidebar *sidebar);

272 273 274

/* Identifiers for target types */
enum {
275 276 277
  DND_UNKNOWN,
  DND_GTK_SIDEBAR_ROW,
  DND_TEXT_URI_LIST
278 279 280
};

/* Target types for dragging from the shortcuts list */
281
static const GtkTargetEntry dnd_source_targets[] = {
282
  { "DND_GTK_SIDEBAR_ROW", GTK_TARGET_SAME_WIDGET, DND_GTK_SIDEBAR_ROW }
283 284 285
};

/* Target types for dropping into the shortcuts list */
286
static const GtkTargetEntry dnd_drop_targets [] = {
287
  { "DND_GTK_SIDEBAR_ROW", GTK_TARGET_SAME_WIDGET, DND_GTK_SIDEBAR_ROW }
288 289
};

290
G_DEFINE_TYPE (GtkPlacesSidebar, gtk_places_sidebar, GTK_TYPE_SCROLLED_WINDOW);
291

292
static void
Matthias Clasen's avatar
Matthias Clasen committed
293 294 295
emit_open_location (GtkPlacesSidebar   *sidebar,
                    GFile              *location,
                    GtkPlacesOpenFlags  open_flags)
296
{
Matthias Clasen's avatar
Matthias Clasen committed
297 298
  if ((open_flags & sidebar->open_flags) == 0)
    open_flags = GTK_PLACES_OPEN_NORMAL;
299

Matthias Clasen's avatar
Matthias Clasen committed
300 301
  g_signal_emit (sidebar, places_sidebar_signals[OPEN_LOCATION], 0,
                 location, open_flags);
302 303
}

304
static void
Matthias Clasen's avatar
Matthias Clasen committed
305 306 307
emit_show_error_message (GtkPlacesSidebar *sidebar,
                         const gchar      *primary,
                         const gchar      *secondary)
308
{
Matthias Clasen's avatar
Matthias Clasen committed
309 310
  g_signal_emit (sidebar, places_sidebar_signals[SHOW_ERROR_MESSAGE], 0,
                 primary, secondary);
311 312
}

313 314 315
static void
emit_show_connect_to_server (GtkPlacesSidebar *sidebar)
{
Matthias Clasen's avatar
Matthias Clasen committed
316
  g_signal_emit (sidebar, places_sidebar_signals[SHOW_CONNECT_TO_SERVER], 0);
317 318
}

319 320 321 322 323 324
static void
emit_show_enter_location (GtkPlacesSidebar *sidebar)
{
  g_signal_emit (sidebar, places_sidebar_signals[SHOW_ENTER_LOCATION], 0);
}

325 326 327 328 329 330
static void
emit_show_other_locations (GtkPlacesSidebar *sidebar)
{
  g_signal_emit (sidebar, places_sidebar_signals[SHOW_OTHER_LOCATIONS], 0);
}

331
static GdkDragAction
332
emit_drag_action_requested (GtkPlacesSidebar *sidebar,
Matthias Clasen's avatar
Matthias Clasen committed
333 334 335
                            GdkDragContext   *context,
                            GFile            *dest_file,
                            GList            *source_file_list)
336
{
Matthias Clasen's avatar
Matthias Clasen committed
337
  GdkDragAction ret_action = 0;
338

Matthias Clasen's avatar
Matthias Clasen committed
339 340
  g_signal_emit (sidebar, places_sidebar_signals[DRAG_ACTION_REQUESTED], 0,
                 context, dest_file, source_file_list, &ret_action);
341

Matthias Clasen's avatar
Matthias Clasen committed
342
  return ret_action;
343 344
}

345 346
static GdkDragAction
emit_drag_action_ask (GtkPlacesSidebar *sidebar,
Matthias Clasen's avatar
Matthias Clasen committed
347
                      GdkDragAction     actions)
348
{
Matthias Clasen's avatar
Matthias Clasen committed
349
  GdkDragAction ret_action = 0;
350

Matthias Clasen's avatar
Matthias Clasen committed
351 352
  g_signal_emit (sidebar, places_sidebar_signals[DRAG_ACTION_ASK], 0,
                 actions, &ret_action);
353

Matthias Clasen's avatar
Matthias Clasen committed
354
  return ret_action;
355 356
}

357 358
static void
emit_drag_perform_drop (GtkPlacesSidebar *sidebar,
Matthias Clasen's avatar
Matthias Clasen committed
359 360 361
                        GFile            *dest_file,
                        GList            *source_file_list,
                        GdkDragAction     action)
362
{
Matthias Clasen's avatar
Matthias Clasen committed
363 364
  g_signal_emit (sidebar, places_sidebar_signals[DRAG_PERFORM_DROP], 0,
                 dest_file, source_file_list, action);
365
}
366 367 368 369
static void
list_box_header_func (GtkListBoxRow *row,
                      GtkListBoxRow *before,
                      gpointer       user_data)
370
{
371 372 373
  GtkPlacesSidebarSectionType row_section_type;
  GtkPlacesSidebarSectionType before_section_type;
  GtkWidget *separator;
374

375
  gtk_list_box_row_set_header (row, NULL);
376

377 378
  g_object_get (row, "section-type", &row_section_type, NULL);
  if (before)
Matthias Clasen's avatar
Matthias Clasen committed
379
    {
380 381 382 383 384 385 386
      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
387

388 389 390 391 392 393 394
  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);
    }
395 396
}

397 398 399 400 401 402 403 404 405 406 407 408 409
static GtkWidget*
add_place (GtkPlacesSidebar            *sidebar,
           GtkPlacesSidebarPlaceType    place_type,
           GtkPlacesSidebarSectionType  section_type,
           const gchar                 *name,
           GIcon                       *icon,
           const gchar                 *uri,
           GDrive                      *drive,
           GVolume                     *volume,
           GMount                      *mount,
           const gint                   index,
           const gchar                 *tooltip)
{
Matthias Clasen's avatar
Matthias Clasen committed
410 411
  gboolean show_eject, show_unmount;
  gboolean show_eject_button;
412
  gchar *tooltip_escaped;
413 414 415
  GtkWidget *row;
  GtkWidget *eject_button;
  GtkWidget *event_box;
Matthias Clasen's avatar
Matthias Clasen committed
416 417 418 419 420 421 422 423 424 425 426 427

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

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

  if (mount == NULL)
    show_eject_button = FALSE;
  else
    show_eject_button = (show_unmount || show_eject);

428
  tooltip_escaped = g_markup_escape_text (tooltip, -1);
429 430 431 432 433 434 435 436 437 438 439 440 441 442
  row = g_object_new (GTK_TYPE_SIDEBAR_ROW,
                      "sidebar", sidebar,
                      "icon", icon,
                      "label", name,
                      "tooltip", tooltip_escaped,
                      "ejectable", show_eject_button,
                      "order-index", index,
                      "section-type", section_type,
                      "place-type", place_type,
                      "uri", uri,
                      "drive", drive,
                      "volume", volume,
                      "mount", mount,
                      NULL);
443 444

  g_free (tooltip_escaped);
445 446 447 448 449 450 451 452 453
  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);
454 455 456 457 458

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

  return row;
459 460
}

461
static GIcon *
462
special_directory_get_gicon (GUserDirectory directory)
463
{
Matthias Clasen's avatar
Matthias Clasen committed
464 465 466
#define ICON_CASE(x)                      \
  case G_USER_DIRECTORY_ ## x:                                    \
          return g_themed_icon_new_with_default_fallbacks (ICON_NAME_FOLDER_ ## x);
467

Matthias Clasen's avatar
Matthias Clasen committed
468 469
  switch (directory)
    {
470

471
    ICON_CASE (DESKTOP);
Matthias Clasen's avatar
Matthias Clasen committed
472 473 474 475 476 477 478
    ICON_CASE (DOCUMENTS);
    ICON_CASE (DOWNLOAD);
    ICON_CASE (MUSIC);
    ICON_CASE (PICTURES);
    ICON_CASE (PUBLIC_SHARE);
    ICON_CASE (TEMPLATES);
    ICON_CASE (VIDEOS);
479

Matthias Clasen's avatar
Matthias Clasen committed
480
    default:
481
      return g_themed_icon_new_with_default_fallbacks (ICON_NAME_FOLDER);
Matthias Clasen's avatar
Matthias Clasen committed
482
    }
483 484 485 486 487

#undef ICON_CASE
}

static gboolean
488 489
recent_files_setting_is_enabled (GtkPlacesSidebar *sidebar)
{
Matthias Clasen's avatar
Matthias Clasen committed
490 491 492
  GtkSettings *settings;
  gboolean enabled;

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

Matthias Clasen's avatar
Matthias Clasen committed
496
  return enabled;
497 498 499 500
}

static gboolean
recent_scheme_is_supported (void)
501
{
Matthias Clasen's avatar
Matthias Clasen committed
502
  const gchar * const *supported;
503

Matthias Clasen's avatar
Matthias Clasen committed
504
  supported = g_vfs_get_supported_uri_schemes (g_vfs_get_default ());
505 506
  if (supported != NULL)
    return g_strv_contains (supported, "recent");
Matthias Clasen's avatar
Matthias Clasen committed
507 508

  return FALSE;
509 510
}

511 512 513
static gboolean
should_show_recent (GtkPlacesSidebar *sidebar)
{
514 515 516
  return recent_files_setting_is_enabled (sidebar) &&
         ((sidebar->show_recent_set && sidebar->show_recent) ||
          (!sidebar->show_recent_set && recent_scheme_is_supported ()));
517 518
}

519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540
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;
}

541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
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);
}

557 558 559
static void
add_special_dirs (GtkPlacesSidebar *sidebar)
{
Matthias Clasen's avatar
Matthias Clasen committed
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582
  GList *dirs;
  gint index;

  dirs = NULL;
  for (index = 0; index < G_USER_N_DIRECTORIES; index++)
    {
      const gchar *path;
      GFile *root;
      GIcon *icon;
      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 ||
583
          path_is_home_dir (path) ||
Matthias Clasen's avatar
Matthias Clasen committed
584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614
          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);

      icon = special_directory_get_gicon (index);
      mount_uri = g_file_get_uri (root);
      tooltip = g_file_get_parse_name (root);

      add_place (sidebar, PLACES_XDG_DIR,
                 SECTION_COMPUTER,
                 name, icon, mount_uri,
                 NULL, NULL, NULL, 0,
                 tooltip);
      g_free (name);
      g_object_unref (root);
      g_object_unref (icon);
      g_free (mount_uri);
      g_free (tooltip);

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

  g_list_free (dirs);
}

static gchar *
615 616
get_home_directory_uri (void)
{
Matthias Clasen's avatar
Matthias Clasen committed
617
  const gchar *home;
618

Matthias Clasen's avatar
Matthias Clasen committed
619 620 621
  home = g_get_home_dir ();
  if (!home)
    return NULL;
622

623
  return g_filename_to_uri (home, NULL, NULL);
624 625
}

Matthias Clasen's avatar
Matthias Clasen committed
626
static gchar *
627
get_desktop_directory_uri (void)
628
{
Matthias Clasen's avatar
Matthias Clasen committed
629 630 631 632 633 634 635
  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
   */
636
  if (path_is_home_dir (name))
Matthias Clasen's avatar
Matthias Clasen committed
637
    return NULL;
638

639
  return g_filename_to_uri (name, NULL, NULL);
640 641
}

642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
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;
}

661 662 663 664 665
static gboolean
file_is_shown (GtkPlacesSidebar *sidebar,
               GFile            *file)
{
  gchar *uri;
666 667 668
  GList *rows;
  GList *l;
  gboolean found = FALSE;
669

670 671 672
  rows = gtk_container_get_children (GTK_CONTAINER (sidebar->list_box));
  l = rows;
  while (l != NULL && !found)
673
    {
674
      g_object_get (l->data, "uri", &uri, NULL);
675 676 677 678 679 680
      if (uri)
        {
          GFile *other;
          other = g_file_new_for_uri (uri);
          found = g_file_equal (file, other);
          g_object_unref (other);
681
          g_free (uri);
682
        }
683
      l = l->next;
684 685
    }

686 687 688
  g_list_free (rows);

  return found;
689 690
}

691 692 693 694 695 696 697 698 699 700 701
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);

702
  if (info)
703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735
    {
      gchar *uri;
      gchar *tooltip;
      const gchar *name;
      GIcon *icon;
      int pos = 0;

      name = g_file_info_get_display_name (info);
      icon = g_file_info_get_symbolic_icon (info);
      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,
                 name, icon, uri,
                 NULL, NULL, NULL,
                 pos,
                 tooltip);

      g_free (uri);
      g_free (tooltip);

      g_object_unref (info);
    }
}

736 737 738
static void
add_application_shortcuts (GtkPlacesSidebar *sidebar)
{
Matthias Clasen's avatar
Matthias Clasen committed
739
  GSList *l;
740

Matthias Clasen's avatar
Matthias Clasen committed
741 742
  for (l = sidebar->shortcuts; l; l = l->next)
    {
743
      GFile *file = l->data;
744

745 746 747
      if (!should_show_file (sidebar, file))
        continue;

748 749 750
      if (file_is_shown (sidebar, file))
        continue;

751 752 753 754
      g_file_query_info_async (file,
                               "standard::display-name,standard::symbolic-icon",
                               G_FILE_QUERY_INFO_NONE,
                               G_PRIORITY_DEFAULT,
755
                               sidebar->cancellable,
756 757
                               on_app_shortcuts_query_complete,
                               sidebar);
Matthias Clasen's avatar
Matthias Clasen committed
758
    }
759 760
}

761 762 763 764 765 766 767
typedef struct {
  GtkPlacesSidebar *sidebar;
  int index;
  gboolean is_native;
} BookmarkQueryClosure;

static void
768
on_bookmark_query_info_complete (GObject      *source,
769 770 771 772 773 774
                                 GAsyncResult *result,
                                 gpointer      data)
{
  BookmarkQueryClosure *clos = data;
  GtkPlacesSidebar *sidebar = clos->sidebar;
  GFile *root = G_FILE (source);
775
  GError *error = NULL;
776 777 778 779 780 781
  GFileInfo *info;
  gchar *bookmark_name;
  gchar *mount_uri;
  gchar *tooltip;
  GIcon *icon;

782 783 784
  info = g_file_query_info_finish (root, result, &error);
  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
    goto out;
785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816

  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)
    icon = g_object_ref (g_file_info_get_symbolic_icon (info));
  else
    icon = g_themed_icon_new_with_default_fallbacks (clos->is_native ? ICON_NAME_FOLDER : ICON_NAME_FOLDER_NETWORK);

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

  add_place (sidebar, PLACES_BOOKMARK,
             SECTION_BOOKMARKS,
             bookmark_name, icon, mount_uri,
             NULL, NULL, NULL, clos->index,
             tooltip);

  g_free (mount_uri);
  g_free (tooltip);
  g_free (bookmark_name);
817
  g_object_unref (icon);
818 819 820

out:
  g_clear_object (&info);
821
  g_clear_error (&error);
822 823 824
  g_slice_free (BookmarkQueryClosure, clos);
}

825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854
static gboolean
is_removable_volume (GVolume *volume)
{
  gboolean is_removable;
  GDrive *drive;
  GMount *mount;
  gchar *id;

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

  is_removable = g_volume_can_eject (volume);

  /* NULL volume identifier only happens on removable devices */
  is_removable |= !id;

  if (drive)
    is_removable |= g_drive_can_eject (drive);

  if (mount)
    is_removable |= (g_mount_can_eject (mount) && !g_mount_can_unmount (mount));

  g_clear_object (&drive);
  g_clear_object (&mount);
  g_free (id);

  return is_removable;
}

855
static void
856
update_places (GtkPlacesSidebar *sidebar)
857
{
Matthias Clasen's avatar
Matthias Clasen committed
858 859 860 861 862 863 864 865 866
  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;
867
  GtkListBoxRow *selected;
Matthias Clasen's avatar
Matthias Clasen committed
868 869 870 871 872
  gchar *home_uri;
  GIcon *icon;
  GFile *root;
  gchar *tooltip;
  GList *network_mounts, *network_volumes;
873 874
  GIcon *new_bookmark_icon;
  GtkStyleContext *context;
Matthias Clasen's avatar
Matthias Clasen committed
875 876

  /* save original selection */
877 878
  selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box));
  if (selected)
879
    g_object_get (selected, "uri", &original_uri, NULL);
Matthias Clasen's avatar
Matthias Clasen committed
880 881 882
  else
    original_uri = NULL;

883 884 885 886 887
  g_cancellable_cancel (sidebar->cancellable);

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

888 889 890 891 892 893
  /* 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
894 895 896

  network_mounts = network_volumes = NULL;

897
  /* add built-in places */
Matthias Clasen's avatar
Matthias Clasen committed
898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924
  if (should_show_recent (sidebar))
    {
      mount_uri = "recent:///";
      icon = g_themed_icon_new_with_default_fallbacks ("document-open-recent-symbolic");
      add_place (sidebar, PLACES_BUILT_IN,
                 SECTION_COMPUTER,
                 _("Recent"), icon, mount_uri,
                 NULL, NULL, NULL, 0,
                 _("Recent files"));
      g_object_unref (icon);
    }

  /* home folder */
  home_uri = get_home_directory_uri ();
  icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_HOME);
  add_place (sidebar, PLACES_BUILT_IN,
             SECTION_COMPUTER,
             _("Home"), icon, home_uri,
             NULL, NULL, NULL, 0,
             _("Open your personal folder"));
  g_object_unref (icon);
  g_free (home_uri);

  /* desktop */
  if (sidebar->show_desktop)
    {
      mount_uri = get_desktop_directory_uri ();
925 926 927 928 929 930 931 932 933 934 935
      if (mount_uri)
        {
          icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_DESKTOP);
          add_place (sidebar, PLACES_BUILT_IN,
                     SECTION_COMPUTER,
                     _("Desktop"), icon, mount_uri,
                     NULL, NULL, NULL, 0,
                     _("Open the contents of your desktop in a folder"));
          g_object_unref (icon);
          g_free (mount_uri);
        }
Matthias Clasen's avatar
Matthias Clasen committed
936 937 938 939 940
    }

  /* XDG directories */
  add_special_dirs (sidebar);

941 942 943 944 945 946 947 948 949 950 951
  if (sidebar->show_enter_location)
    {
      icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_NETWORK_SERVER);
      add_place (sidebar, PLACES_ENTER_LOCATION,
                 SECTION_COMPUTER,
                 _("Enter Location"), icon, NULL,
                 NULL, NULL, NULL, 0,
                 _("Manually enter a location"));
      g_object_unref (icon);
    }

Matthias Clasen's avatar
Matthias Clasen committed
952
  /* Trash */
953
  if (!sidebar->local_only && sidebar->show_trash)
954 955 956 957 958 959 960 961 962 963
    {
      mount_uri = "trash:///"; /* No need to strdup */
      icon = _gtk_trash_monitor_get_icon (sidebar->trash_monitor);
      add_place (sidebar, PLACES_BUILT_IN,
                 SECTION_COMPUTER,
                 _("Trash"), icon, mount_uri,
                 NULL, NULL, NULL, 0,
                 _("Open the trash"));
      g_object_unref (icon);
    }
Matthias Clasen's avatar
Matthias Clasen committed
964 965 966 967 968

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

  /* go through all connected drives */
969
  drives = g_volume_monitor_get_connected_drives (sidebar->volume_monitor);
Matthias Clasen's avatar
Matthias Clasen committed
970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990

  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);

991
              if (sidebar->show_other_locations && !is_removable_volume (volume))
992 993 994 995 996
                {
                  g_object_unref (volume);
                  continue;
                }

Matthias Clasen's avatar
Matthias Clasen committed
997 998 999 1000 1001 1002 1003 1004 1005 1006 1007
              mount = g_volume_get_mount (volume);
              if (mount != NULL)
                {
                  /* Show mounted volume in the sidebar */
                  icon = g_mount_get_symbolic_icon (mount);
                  root = g_mount_get_default_location (mount);
                  mount_uri = g_file_get_uri (root);
                  name = g_mount_get_name (mount);
                  tooltip = g_file_get_parse_name (root);

                  add_place (sidebar, PLACES_MOUNTED_VOLUME,
1008
                             SECTION_MOUNTS,
Matthias Clasen's avatar
Matthias Clasen committed
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029
                             name, icon, mount_uri,
                             drive, volume, mount, 0, tooltip);
                  g_object_unref (root);
                  g_object_unref (mount);
                  g_object_unref (icon);
                  g_free (tooltip);
                  g_free (name);
                  g_free (mount_uri);
                }
              else
                {
                  /* Do show the unmounted volumes in the sidebar;
                   * this is so the user can mount it (in case automounting
                   * is off).
                   *
                   * Also, even if automounting is enabled, this gives a visual
                   * cue that the user should remember to yank out the media if
                   * he just unmounted it.
                   */
                  icon = g_volume_get_symbolic_icon (volume);
                  name = g_volume_get_name (volume);
1030
                  tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
Matthias Clasen's avatar
Matthias Clasen committed
1031 1032

                  add_place (sidebar, PLACES_MOUNTED_VOLUME,
1033
                             SECTION_MOUNTS,
Matthias Clasen's avatar
Matthias Clasen committed
1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057
                             name, icon, NULL,
                             drive, volume, NULL, 0, tooltip);
                  g_object_unref (icon);
                  g_free (name);
                  g_free (tooltip);
                }
              g_object_unref (volume);
            }
          g_list_free (volumes);
        }
      else
        {
          if (g_drive_is_media_removable (drive) && !g_drive_is_media_check_automatic (drive))
            {
              /* If the drive has no mountable volumes and we cannot detect media change.. we
               * display the drive in the sidebar so the user can manually poll the drive by
               * right clicking and selecting "Rescan..."
               *
               * This is mainly for drives like floppies where media detection doesn't
               * work.. but it's also for human beings who like to turn off media detection
               * in the OS to save battery juice.
               */
              icon = g_drive_get_symbolic_icon (drive);
              name = g_drive_get_name (drive);
1058
              tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
Matthias Clasen's avatar
Matthias Clasen committed
1059 1060

              add_place (sidebar, PLACES_BUILT_IN,
1061
                         SECTION_MOUNTS,
Matthias Clasen's avatar
Matthias Clasen committed
1062 1063 1064 1065 1066 1067 1068 1069
                         name, icon, NULL,
                         drive, NULL, NULL, 0, tooltip);
              g_object_unref (icon);
              g_free (tooltip);
              g_free (name);
            }
        }
    }
1070
  g_list_free_full (drives, g_object_unref);
Matthias Clasen's avatar
Matthias Clasen committed
1071

1072
  /* add all network volumes that is not associated with a drive */
1073
  volumes = g_volume_monitor_get_volumes (sidebar->volume_monitor);
Matthias Clasen's avatar
Matthias Clasen committed
1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094
  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);

1095
      if (sidebar->show_other_locations && !is_removable_volume (volume))
1096 1097 1098 1099 1100
        {
          g_object_unref (volume);
          continue;
        }

Matthias Clasen's avatar
Matthias Clasen committed
1101 1102 1103 1104 1105 1106 1107 1108 1109
      mount = g_volume_get_mount (volume);
      if (mount != NULL)
        {
          icon = g_mount_get_symbolic_icon (mount);
          root = g_mount_get_default_location (mount);
          mount_uri = g_file_get_uri (root);
          tooltip = g_file_get_parse_name (root);
          name = g_mount_get_name (mount);
          add_place (sidebar, PLACES_MOUNTED_VOLUME,
1110
                     SECTION_MOUNTS,
Matthias Clasen's avatar
Matthias Clasen committed
1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125
                     name, icon, mount_uri,
                     NULL, volume, mount, 0, tooltip);
          g_object_unref (mount);
          g_object_unref (root);
          g_object_unref (icon);
          g_free (name);
          g_free (tooltip);
          g_free (mount_uri);
        }
      else
        {
          /* see comment above in why we add an icon for an unmounted mountable volume */
          icon = g_volume_get_symbolic_icon (volume);
          name = g_volume_get_name (volume);
          add_place (sidebar, PLACES_MOUNTED_VOLUME,
1126
                     SECTION_MOUNTS,
Matthias Clasen's avatar
Matthias Clasen committed
1127 1128 1129 1130
                     name, icon, NULL,
                     NULL, volume, NULL, 0, name);
          g_object_unref (icon);
          g_free (name);
1131
        }
Matthias Clasen's avatar
Matthias Clasen committed
1132 1133 1134 1135
      g_object_unref (volume);
    }
  g_list_free (volumes);

1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148
  /* file system root */
  if (!sidebar->show_other_locations)
    {
      mount_uri = "file:///"; /* No need to strdup */
      icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_FILESYSTEM);
      add_place (sidebar, PLACES_BUILT_IN,
                 SECTION_MOUNTS,
                 sidebar->hostname, icon, mount_uri,
                 NULL, NULL, NULL, 0,
                 _("Open the contents of the file system"));
      g_object_unref (icon);
    }

Matthias Clasen's avatar
Matthias Clasen committed
1149
  /* add mounts that has no volume (/etc/mtab mounts, ftp, sftp,...) */
1150
  mounts = g_volume_monitor_get_mounts (sidebar->volume_monitor);
Matthias Clasen's avatar
Matthias Clasen committed
1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197

  for (l = mounts; l != NULL; l = l->next)
    {
      mount = l->data;
      if (g_mount_is_shadowed (mount))
        {
          g_object_unref (mount);
          continue;
        }
      volume = g_mount_get_volume (mount);
      if (volume != NULL)
        {
          g_object_unref (volume);
          g_object_unref (mount);
          continue;
        }
      root = g_mount_get_default_location (mount);

      if (!g_file_is_native (root))
        {
          network_mounts = g_list_prepend (network_mounts, mount);
          g_object_unref (root);
          continue;
        }

      icon = g_mount_get_symbolic_icon (mount);
      mount_uri = g_file_get_uri (root);
      name = g_mount_get_name (mount);
      tooltip = g_file_get_parse_name (root);
      add_place (sidebar, PLACES_MOUNTED_VOLUME,
                 SECTION_COMPUTER,
                 name, icon, mount_uri,
                 NULL, NULL, mount, 0, tooltip);
      g_object_unref (root);
      g_object_unref (mount);
      g_object_unref (icon);
      g_free (name);
      g_free (mount_uri);
      g_free (tooltip);
    }
  g_list_free (mounts);

  /* add bookmarks */
  bookmarks = _gtk_bookmarks_manager_list_bookmarks (sidebar->bookmarks_manager);

  for (sl = bookmarks, index = 0; sl; sl = sl->next, index++)
    {
1198
      gboolean is_native;
1199
      BookmarkQueryClosure *clos;
Matthias Clasen's avatar
Matthias Clasen committed
1200 1201

      root = sl->data;
1202
      is_native = g_file_is_native (root);
1203

Matthias Clasen's avatar
Matthias Clasen committed
1204 1205 1206
      if (_gtk_bookmarks_manager_get_is_builtin (sidebar->bookmarks_manager, root))
        continue;

1207 1208 1209
      if (sidebar->local_only && !is_native)
        continue;

1210 1211 1212 1213 1214 1215 1216 1217
      clos = g_slice_new (BookmarkQueryClosure);
      clos->sidebar = sidebar;
      clos->index = index;
      clos->is_native = is_native;
      g_file_query_info_async (root,
                               "standard::display-name,standard::symbolic-icon",
                               G_FILE_QUERY_INFO_NONE,
                               G_PRIORITY_DEFAULT,
1218
                               sidebar->cancellable,
1219 1220
                               on_bookmark_query_info_complete,
                               clos);
Matthias Clasen's avatar
Matthias Clasen committed
1221 1222
    }

Matthias Clasen's avatar
Matthias Clasen committed
1223
  g_slist_free_full (bookmarks, g_object_unref);
Matthias Clasen's avatar
Matthias Clasen committed
1224

1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235
  /* Add new bookmark row */
  new_bookmark_icon = g_themed_icon_new ("bookmark-new-symbolic");
  sidebar->new_bookmark_row = add_place (sidebar, PLACES_DROP_FEEDBACK,
                                         SECTION_BOOKMARKS,
                                         _("New bookmark"), new_bookmark_icon, NULL,
                                         NULL, NULL, NULL, 0,
                                         _("Add a new bookmark"));
  context = gtk_widget_get_style_context (sidebar->new_bookmark_row);
  gtk_style_context_add_class (context, "sidebar-new-bookmark-row");
  g_object_unref (new_bookmark_icon);

Matthias Clasen's avatar
Matthias Clasen committed
1236
  /* network */
1237
  if (!sidebar->local_only)
Matthias Clasen's avatar
Matthias Clasen committed
1238
    {
1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249
      if (sidebar->show_connect_to_server)
        {
          icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_NETWORK_SERVER);
          add_place (sidebar, PLACES_CONNECT_TO_SERVER,
                     SECTION_MOUNTS,
                     _("Connect to Server"), icon, NULL,
                     NULL, NULL, NULL, 0,
                     _("Connect to a network server address"));
          g_object_unref (icon);
        }

1250 1251
      network_volumes = g_list_reverse (network_volumes);
      for (l = network_volumes; l != NULL; l = l->next)
Matthias Clasen's avatar
Matthias Clasen committed
1252
        {
1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264
          volume = l->data;
          mount = g_volume_get_mount (volume);

          if (mount != NULL)
            {
              network_mounts = g_list_prepend (network_mounts, mount);
              continue;
            }
          else
            {
              icon = g_volume_get_symbolic_icon (volume);
              name = g_volume_get_name (volume);
1265
              tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
Matthias Clasen's avatar
Matthias Clasen committed
1266

1267
              add_place (sidebar, PLACES_MOUNTED_VOLUME,
1268
                         SECTION_MOUNTS,
1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285
                         name, icon, NULL,
                         NULL, volume, NULL, 0, tooltip);
              g_object_unref (icon);
              g_free (name);
              g_free (tooltip);
            }
        }

      network_mounts = g_list_reverse (network_mounts);
      for (l = network_mounts; l != NULL; l = l->next)
        {
          mount = l->data;
          root = g_mount_get_default_location (mount);
          icon = g_mount_get_symbolic_icon (mount);
          mount_uri = g_file_get_uri (root);
          name = g_mount_get_name (mount);
          tooltip = g_file_get_parse_name (root);
Matthias Clasen's avatar
Matthias Clasen committed
1286
          add_place (sidebar, PLACES_MOUNTED_VOLUME,
1287
                     SECTION_MOUNTS,
1288 1289 1290
                     name, icon, mount_uri,
                     NULL, NULL, mount, 0, tooltip);
          g_object_unref (root);
Matthias Clasen's avatar
Matthias Clasen committed
1291 1292
          g_object_unref (icon);
          g_free (name);
1293
          g_free (mount_uri);
Matthias Clasen's avatar
Matthias Clasen committed
1294 1295 1296 1297 1298 1299 1300
          g_free (tooltip);
        }
    }

  g_list_free_full (network_volumes, g_object_unref);
  g_list_free_full (network_mounts, g_object_unref);

1301 1302 1303 1304 1305 1306 1307
  /* Other locations */
  if (sidebar->show_other_locations)
    {
      icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_OTHER_LOCATIONS);

      add_place (sidebar, PLACES_OTHER_LOCATIONS,
                 SECTION_OTHER_LOCATIONS,
1308
                 _("Other Locations"), icon, "other-locations:///",
1309 1310 1311 1312 1313
                 NULL, NULL, NULL, 0, _("Show other locations"));

      g_object_unref (icon);
    }

1314 1315 1316 1317
  gtk_widget_show_all (GTK_WIDGET (sidebar));
  /* We want this hidden by default, but need to do it after the show_all call */
  gtk_sidebar_row_hide (GTK_SIDEBAR_ROW (sidebar->new_bookmark_row), TRUE);

Matthias Clasen's avatar
Matthias Clasen committed
1318 1319 1320 1321 1322 1323 1324 1325
  /* restore original selection */
  if (original_uri)
    {
      GFile *restore;

      restore = g_file_new_for_uri (original_uri);
      gtk_places_sidebar_set_location (sidebar, restore);
      g_object_unref (restore);
1326
      g_free (original_uri);
Matthias Clasen's avatar
Matthias Clasen committed
1327
    }
1328 1329 1330
}

static gboolean
1331 1332 1333
check_valid_drop_target (GtkPlacesSidebar *sidebar,
                         GtkSidebarRow    *row,
                         GdkDragContext   *context)
1334
{
1335 1336 1337
  GtkPlacesSidebarPlaceType place_type;
  GtkPlacesSidebarSectionType section_type;
  gboolean valid = FALSE;
1338
  gchar *uri;
1339 1340
  GFile *dest_file;
  gint drag_action;
1341

1342 1343
  if (row == NULL)
    return FALSE;
1344

1345 1346 1347 1348 1349
  g_object_get (row,
                "place-type", &place_type,
                "section_type", &section_type,
                "uri", &uri,
                NULL);
1350

1351
  if (place_type == PLACES_CONNECT_TO_SERVER)
1352 1353 1354 1355
    {
      g_free (uri);
      return FALSE;
    }
Matthias Clasen's avatar
Matthias Clasen committed
1356

1357
  if (place_type == PLACES_DROP_FEEDBACK)
1358 1359 1360 1361
    {
      g_free (uri);
      return TRUE;
    }
Matthias Clasen's avatar
Matthias Clasen committed
1362

1363 1364 1365 1366
  /* Disallow drops on recent:/// */
  if (place_type == PLACES_BUILT_IN)
    {
      if (g_strcmp0 (uri, "recent:///") == 0)
1367 1368 1369 1370
        {
          g_free (uri);
          return FALSE;
        }
Matthias Clasen's avatar
Matthias Clasen committed
1371 1372 1373 1374
    }

  /* Dragging a bookmark? */
  if (sidebar->drag_data_received &&
1375
      sidebar->drag_data_info == DND_GTK_SIDEBAR_ROW)
Matthias Clasen's avatar
Matthias Clasen committed
1376 1377
    {
      /* Don't allow reordering bookmarks into non-bookmark areas */
1378
      valid = section_type == SECTION_BOOKMARKS;
Matthias Clasen's avatar
Matthias Clasen committed
1379 1380
    }
  else
1381
    {
Matthias Clasen's avatar
Matthias Clasen committed
1382
      /* Dragging a file */
1383
      if (context)
Matthias Clasen's avatar
Matthias Clasen committed
1384
        {
1385
          if (uri != NULL)
Matthias Clasen's avatar
Matthias Clasen committed
1386
            {
1387 1388 1389 1390 1391 1392 1393 1394 1395
              dest_file = g_file_new_for_uri (uri);
              drag_action = emit_drag_action_requested (sidebar, context, dest_file, sidebar->drag_list);
              valid = drag_action > 0;

              g_object_unref (dest_file);
            }
          else
            {
              valid = FALSE;
Matthias Clasen's avatar
Matthias Clasen committed
1396 1397
            }
        }
1398 1399 1400 1401 1402 1403 1404 1405 1406
      else
        {
          /* We cannot discern if it is valid or not because there is not drag
           * context available to ask the client.
           * Simply make insensitive the drop targets we know are not valid for
           * files, that are the ones remaining.
           */
          valid = TRUE;
        }
Matthias Clasen's avatar
Matthias Clasen committed
1407 1408
    }

1409
  g_free (uri);
1410 1411
  return valid;
}
Matthias Clasen's avatar
Matthias Clasen committed
1412

1413 1414 1415 1416 1417 1418 1419 1420
static void
update_possible_drop_targets (GtkPlacesSidebar *sidebar,
                              gboolean          dragging,
                              GdkDragContext   *context)
{
  GList *rows;
  GList *l;
  gboolean sensitive;
1421

1422
  rows = gtk_container_get_children (GTK_CONTAINER (sidebar->list_box));
1423

1424
  for (l = rows; l != NULL; l = l->next)
Matthias Clasen's avatar
Matthias Clasen committed
1425
    {
1426
      sensitive = !dragging || check_valid_drop_target (sidebar, GTK_SIDEBAR_ROW (l->data), context);
1427
      gtk_widget_set_sensitive (GTK_WIDGET (l->data), sensitive);
Matthias Clasen's avatar
Matthias Clasen committed
1428
    }
1429

1430
  g_list_free (rows);
1431 1432 1433
}

static gboolean
1434
get_drag_data (GtkWidget      *list_box,
Matthias Clasen's avatar
Matthias Clasen committed
1435 1436
               GdkDragContext *context,
               guint           time)
1437
{
Matthias Clasen's avatar
Matthias Clasen committed
1438
  GdkAtom target;
1439

1440
  target = gtk_drag_dest_find_target (list_box, context, NULL);
1441

Matthias Clasen's avatar
Matthias Clasen committed
1442 1443
  if (target == GDK_NONE)
    return FALSE;
1444

1445
  gtk_drag_get_data (list_box, context, target, time);
1446

Matthias Clasen's avatar
Matthias Clasen committed
1447
  return TRUE;
1448 1449 1450
}

static void
1451
free_drag_data (GtkPlacesSidebar *sidebar)
1452
{
Matthias Clasen's avatar
Matthias Clasen committed
1453
  sidebar->drag_data_received = FALSE;
1454

Matthias Clasen's avatar
Matthias Clasen committed
1455 1456 1457 1458 1459
  if (sidebar->drag_list)
    {
      g_list_free_full (sidebar->drag_list, g_object_unref);
      sidebar->drag_list = NULL;
    }
1460 1461 1462

}

1463 1464 1465 1466
static void
start_drop_feedback (GtkPlacesSidebar *sidebar,
                     GtkSidebarRow    *row,
                     GdkDragContext   *context)
1467
{
1468 1469 1470
  if (sidebar->drag_data_info != DND_GTK_SIDEBAR_ROW)
    {
      gtk_sidebar_row_reveal (GTK_SIDEBAR_ROW (sidebar->new_bookmark_row));
1471
      /* If the state is permanent, don't change it. The application controls it. */
1472 1473 1474
      if (sidebar->drop_state != DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT)
        sidebar->drop_state = DROP_STATE_NEW_BOOKMARK_ARMED;
    }
1475

1476
  update_possible_drop_targets (sidebar, TRUE, context);
1477 1478 1479
}

static void
1480
stop_drop_feedback (GtkPlacesSidebar *sidebar)
1481
{
1482
  update_possible_drop_targets (sidebar, FALSE, NULL);
1483

1484
  free_drag_data (sidebar);
1485

1486 1487
  if (sidebar->drop_state != DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT &&
      sidebar->new_bookmark_row != NULL)
Matthias Clasen's avatar
Matthias Clasen committed
1488
    {
1489 1490
      gtk_sidebar_row_hide (GTK_SIDEBAR_ROW (sidebar->new_bookmark_row), FALSE);
      sidebar->drop_state = DROP_STATE_NORMAL;
Matthias Clasen's avatar
Matthias Clasen committed
1491
    }
1492

1493
  if (sidebar->drag_row != NULL)
Matthias Clasen's avatar
Matthias Clasen committed
1494
    {
1495 1496 1497
      gtk_widget_show (sidebar->drag_row);
      sidebar->drag_row = NULL;
    }
1498

1499 1500 1501 1502
  if (sidebar->row_placeholder != NULL)
    {
      gtk_widget_destroy (sidebar->row_placeholder);
      sidebar->row_placeholder = NULL;
Matthias Clasen's avatar
Matthias Clasen committed
1503
    }
1504 1505 1506

  sidebar->dragging_over = FALSE;
  sidebar->drag_data_info = DND_UNKNOWN;
1507 1508
}

1509 1510 1511 1512
static gboolean
on_motion_notify_event (GtkWidget      *widget,
                        GdkEventMotion *event,
                        gpointer        user_data)
Matthias Clasen's avatar
Matthias Clasen committed
1513
{
1514
  GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (user_data);
Matthias Clasen's avatar
Matthias Clasen committed
1515

1516
  if (sidebar->drag_row == NULL || sidebar->dragging_over)
1517
    return FALSE;
Matthias Clasen's avatar
Matthias Clasen committed
1518

1519
  if (!(event->state & GDK_BUTTON1_MASK))
1520
    return FALSE;
Matthias Clasen's avatar
Matthias Clasen committed
1521

1522
  if (gtk_drag_check_threshold (widget,
1523 1524
                                sidebar->drag_root_x, sidebar->drag_root_y,
                                event->x_root, event->y_root))
1525
    {
1526
      sidebar->dragging_over = TRUE;
Matthias Clasen's avatar
Matthias Clasen committed
1527

1528