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 64 65 66 67 68
/**
 * 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
69
 * file system:  the user’s home directory, the user’s bookmarks, and volumes and drives.
70 71 72 73 74 75 76 77 78 79 80 81 82
 * 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
83
 * for a Clipart folder.  You can do this with gtk_places_sidebar_add_shortcut().
84 85 86 87 88 89 90 91
 *
 * 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
92
/* These are used when a destination-side DND operation is taking place.
93 94 95 96 97
 * 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
98 99
 */
typedef enum {
Matthias Clasen's avatar
Matthias Clasen committed
100 101
  DROP_STATE_NORMAL,
  DROP_STATE_NEW_BOOKMARK_ARMED,
102
  DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT,
103 104
} DropState;

105
struct _GtkPlacesSidebar {
Matthias Clasen's avatar
Matthias Clasen committed
106
  GtkScrolledWindow parent;
107

108 109 110
  GtkWidget *list_box;
  GtkWidget *new_bookmark_row;

Matthias Clasen's avatar
Matthias Clasen committed
111 112 113
  GtkBookmarksManager     *bookmarks_manager;
  GVolumeMonitor    *volume_monitor;
  GtkTrashMonitor   *trash_monitor;
114
  GtkSettings       *gtk_settings;
115
  GFile             *current_location;
116

117 118 119 120 121 122
  GtkWidget *rename_popover;
  GtkWidget *rename_entry;
  GtkWidget *rename_button;
  GtkWidget *rename_error;
  gchar *rename_uri;

Matthias Clasen's avatar
Matthias Clasen committed
123
  gulong trash_monitor_changed_id;
124

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

Matthias Clasen's avatar
Matthias Clasen committed
139 140
  /* volume mounting - delayed open process */
  GtkPlacesOpenFlags go_to_after_mount_open_flags;
141
  GCancellable *cancellable;
142

Matthias Clasen's avatar
Matthias Clasen committed
143 144
  GtkWidget *popup_menu;
  GSList *shortcuts;
145

Matthias Clasen's avatar
Matthias Clasen committed
146 147 148
  GDBusProxy *hostnamed_proxy;
  GCancellable *hostnamed_cancellable;
  gchar *hostname;
149

Matthias Clasen's avatar
Matthias Clasen committed
150
  GtkPlacesOpenFlags open_flags;
151

Matthias Clasen's avatar
Matthias Clasen committed
152 153 154
  guint mounting               : 1;
  guint  drag_data_received    : 1;
  guint drop_occured           : 1;
155 156
  guint show_recent_set        : 1;
  guint show_recent            : 1;
157
  guint show_desktop_set       : 1;
Matthias Clasen's avatar
Matthias Clasen committed
158 159
  guint show_desktop           : 1;
  guint show_connect_to_server : 1;
160
  guint show_enter_location    : 1;
161
  guint show_other_locations   : 1;
162
  guint show_trash             : 1;
163
  guint local_only             : 1;
164
};
165

166
struct _GtkPlacesSidebarClass {
Matthias Clasen's avatar
Matthias Clasen committed
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
  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);
190
  void    (* show_enter_location)    (GtkPlacesSidebar   *sidebar);
191 192

  void    (* show_other_locations)   (GtkPlacesSidebar   *sidebar);
193
};
194

195
enum {
Matthias Clasen's avatar
Matthias Clasen committed
196 197 198 199
  OPEN_LOCATION,
  POPULATE_POPUP,
  SHOW_ERROR_MESSAGE,
  SHOW_CONNECT_TO_SERVER,
200
  SHOW_ENTER_LOCATION,
Matthias Clasen's avatar
Matthias Clasen committed
201 202 203
  DRAG_ACTION_REQUESTED,
  DRAG_ACTION_ASK,
  DRAG_PERFORM_DROP,
204
  SHOW_OTHER_LOCATIONS,
Matthias Clasen's avatar
Matthias Clasen committed
205
  LAST_SIGNAL
206 207
};

208
enum {
Matthias Clasen's avatar
Matthias Clasen committed
209 210
  PROP_LOCATION = 1,
  PROP_OPEN_FLAGS,
211
  PROP_SHOW_RECENT,
Matthias Clasen's avatar
Matthias Clasen committed
212 213
  PROP_SHOW_DESKTOP,
  PROP_SHOW_CONNECT_TO_SERVER,
214
  PROP_SHOW_ENTER_LOCATION,
215
  PROP_SHOW_TRASH,
216
  PROP_LOCAL_ONLY,
217
  PROP_SHOW_OTHER_LOCATIONS,
Matthias Clasen's avatar
Matthias Clasen committed
218
  NUM_PROPERTIES
219 220
};

221
/* Names for themed icons */
Matthias Clasen's avatar
Matthias Clasen committed
222
#define ICON_NAME_HOME     "user-home-symbolic"
223
#define ICON_NAME_DESKTOP  "user-desktop-symbolic"
Matthias Clasen's avatar
Matthias Clasen committed
224 225 226
#define ICON_NAME_FILESYSTEM     "drive-harddisk-symbolic"
#define ICON_NAME_EJECT    "media-eject-symbolic"
#define ICON_NAME_NETWORK  "network-workgroup-symbolic"
227
#define ICON_NAME_NETWORK_SERVER "network-server-symbolic"
228
#define ICON_NAME_FOLDER_NETWORK "folder-remote-symbolic"
229
#define ICON_NAME_OTHER_LOCATIONS "list-add-symbolic"
230

231
#define ICON_NAME_FOLDER                "folder-symbolic"
232
#define ICON_NAME_FOLDER_DESKTOP  "user-desktop-symbolic"
Matthias Clasen's avatar
Matthias Clasen committed
233 234 235 236 237 238 239 240
#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"
241

242
static guint places_sidebar_signals [LAST_SIGNAL] = { 0 };
243
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
244

245 246 247 248 249 250 251 252 253 254 255 256 257
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);
258
static void popup_menu_cb (GtkSidebarRow *row);
259 260
static void stop_drop_feedback (GtkPlacesSidebar *sidebar);

261 262 263

/* Identifiers for target types */
enum {
264 265 266
  DND_UNKNOWN,
  DND_GTK_SIDEBAR_ROW,
  DND_TEXT_URI_LIST
267 268 269
};

/* Target types for dragging from the shortcuts list */
270
static const GtkTargetEntry dnd_source_targets[] = {
271
  { "DND_GTK_SIDEBAR_ROW", GTK_TARGET_SAME_WIDGET, DND_GTK_SIDEBAR_ROW }
272 273 274
};

/* Target types for dropping into the shortcuts list */
275
static const GtkTargetEntry dnd_drop_targets [] = {
276
  { "DND_GTK_SIDEBAR_ROW", GTK_TARGET_SAME_WIDGET, DND_GTK_SIDEBAR_ROW }
277 278
};

279
G_DEFINE_TYPE (GtkPlacesSidebar, gtk_places_sidebar, GTK_TYPE_SCROLLED_WINDOW);
280

281
static void
Matthias Clasen's avatar
Matthias Clasen committed
282 283 284
emit_open_location (GtkPlacesSidebar   *sidebar,
                    GFile              *location,
                    GtkPlacesOpenFlags  open_flags)
285
{
Matthias Clasen's avatar
Matthias Clasen committed
286 287
  if ((open_flags & sidebar->open_flags) == 0)
    open_flags = GTK_PLACES_OPEN_NORMAL;
288

Matthias Clasen's avatar
Matthias Clasen committed
289 290
  g_signal_emit (sidebar, places_sidebar_signals[OPEN_LOCATION], 0,
                 location, open_flags);
291 292
}

293
static void
294
emit_populate_popup (GtkPlacesSidebar *sidebar,
Matthias Clasen's avatar
Matthias Clasen committed
295 296 297
                     GtkMenu          *menu,
                     GFile            *selected_item,
                     GVolume          *selected_volume)
298
{
Matthias Clasen's avatar
Matthias Clasen committed
299 300
  g_signal_emit (sidebar, places_sidebar_signals[POPULATE_POPUP], 0,
                 menu, selected_item, selected_volume);
301 302
}

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

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

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

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

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

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

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

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

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

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

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

374
  gtk_list_box_row_set_header (row, NULL);
375

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

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

396 397 398 399 400 401 402 403 404 405 406 407 408
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
409 410
  gboolean show_eject, show_unmount;
  gboolean show_eject_button;
411
  gchar *tooltip_escaped;
412 413 414
  GtkWidget *row;
  GtkWidget *eject_button;
  GtkWidget *event_box;
Matthias Clasen's avatar
Matthias Clasen committed
415 416 417 418 419 420 421 422 423 424 425 426

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

427
  tooltip_escaped = g_markup_escape_text (tooltip, -1);
428 429 430 431 432 433 434 435 436 437 438 439 440 441
  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);
442 443

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

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

  return row;
458 459
}

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

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

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

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

#undef ICON_CASE
}

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

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

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

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

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

  return FALSE;
508 509
}

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

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

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

556 557 558
static void
add_special_dirs (GtkPlacesSidebar *sidebar)
{
Matthias Clasen's avatar
Matthias Clasen committed
559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
  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 ||
582
          path_is_home_dir (path) ||
Matthias Clasen's avatar
Matthias Clasen committed
583 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
          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 *
614 615
get_home_directory_uri (void)
{
Matthias Clasen's avatar
Matthias Clasen committed
616
  const gchar *home;
617

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

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

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

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

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

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

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

685 686 687
  g_list_free (rows);

  return found;
688 689
}

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

701
  if (info)
702 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
    {
      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);
    }
}

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

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

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

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

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

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

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

781 782 783
  info = g_file_query_info_finish (root, result, &error);
  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
    goto out;
784 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

  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);
816
  g_object_unref (icon);
817 818 819

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

824 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
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;
}

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

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

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

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

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

  network_mounts = network_volumes = NULL;

896
  /* add built-in places */
Matthias Clasen's avatar
Matthias Clasen committed
897 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
  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 ();
924 925 926 927 928 929 930 931 932 933 934
      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
935 936 937 938 939
    }

  /* XDG directories */
  add_special_dirs (sidebar);

940 941 942 943 944 945 946 947 948 949 950
  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
951
  /* Trash */
952
  if (!sidebar->local_only && sidebar->show_trash)
953 954 955 956 957 958 959 960 961 962
    {
      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
963 964 965 966 967

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

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

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

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

Matthias Clasen's avatar
Matthias Clasen committed
996 997 998 999 1000 1001 1002 1003 1004 1005 1006
              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,
1007
                             SECTION_MOUNTS,
Matthias Clasen's avatar
Matthias Clasen committed
1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028
                             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);
1029
                  tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
Matthias Clasen's avatar
Matthias Clasen committed
1030 1031

                  add_place (sidebar, PLACES_MOUNTED_VOLUME,
1032
                             SECTION_MOUNTS,
Matthias Clasen's avatar
Matthias Clasen committed
1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056
                             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);
1057
              tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
Matthias Clasen's avatar
Matthias Clasen committed
1058 1059

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

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

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

Matthias Clasen's avatar
Matthias Clasen committed
1100 1101 1102 1103 1104 1105 1106 1107 1108
      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,
1109
                     SECTION_MOUNTS,
Matthias Clasen's avatar
Matthias Clasen committed
1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124
                     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,
1125
                     SECTION_MOUNTS,
Matthias Clasen's avatar
Matthias Clasen committed
1126 1127 1128 1129
                     name, icon, NULL,
                     NULL, volume, NULL, 0, name);
          g_object_unref (icon);
          g_free (name);
1130
        }
Matthias Clasen's avatar
Matthias Clasen committed
1131 1132 1133 1134 1135
      g_object_unref (volume);
    }
  g_list_free (volumes);

  /* add mounts that has no volume (/etc/mtab mounts, ftp, sftp,...) */
1136
  mounts = g_volume_monitor_get_mounts (sidebar->volume_monitor);
Matthias Clasen's avatar
Matthias Clasen committed
1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 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

  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++)
    {
1184
      gboolean is_native;
1185
      BookmarkQueryClosure *clos;
Matthias Clasen's avatar
Matthias Clasen committed
1186 1187

      root = sl->data;
1188
      is_native = g_file_is_native (root);
1189

Matthias Clasen's avatar
Matthias Clasen committed
1190 1191 1192
      if (_gtk_bookmarks_manager_get_is_builtin (sidebar->bookmarks_manager, root))
        continue;

1193 1194 1195
      if (sidebar->local_only && !is_native)
        continue;

1196 1197 1198 1199 1200 1201 1202 1203
      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,
1204
                               sidebar->cancellable,
1205 1206
                               on_bookmark_query_info_complete,
                               clos);
Matthias Clasen's avatar
Matthias Clasen committed
1207 1208 1209 1210 1211
    }

  g_slist_foreach (bookmarks, (GFunc) g_object_unref, NULL);
  g_slist_free (bookmarks);

1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222
  /* 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
1223
  /* network */
1224
  if (!sidebar->local_only)
Matthias Clasen's avatar
Matthias Clasen committed
1225
    {
1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236
      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);
        }

1237 1238
      network_volumes = g_list_reverse (network_volumes);
      for (l = network_volumes; l != NULL; l = l->next)
Matthias Clasen's avatar
Matthias Clasen committed
1239
        {
1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251
          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);
1252
              tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
Matthias Clasen's avatar
Matthias Clasen committed
1253

1254
              add_place (sidebar, PLACES_MOUNTED_VOLUME,
1255
                         SECTION_MOUNTS,
1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272
                         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
1273
          add_place (sidebar, PLACES_MOUNTED_VOLUME,
1274
                     SECTION_MOUNTS,
1275 1276 1277
                     name, icon, mount_uri,
                     NULL, NULL, mount, 0, tooltip);
          g_object_unref (root);
Matthias Clasen's avatar
Matthias Clasen committed
1278 1279
          g_object_unref (icon);
          g_free (name);
1280
          g_free (mount_uri);
Matthias Clasen's avatar
Matthias Clasen committed
1281 1282 1283 1284 1285 1286 1287
          g_free (tooltip);
        }
    }

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

1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300
  /* 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,
                 _("Other Locations"), icon, NULL,
                 NULL, NULL, NULL, 0, _("Show other locations"));

      g_object_unref (icon);
    }

1301 1302 1303 1304
  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
1305 1306 1307 1308 1309 1310 1311 1312
  /* 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);
1313
      g_free (original_uri);
Matthias Clasen's avatar
Matthias Clasen committed
1314
    }
1315 1316 1317
}

static gboolean
1318 1319 1320
check_valid_drop_target (GtkPlacesSidebar *sidebar,
                         GtkSidebarRow    *row,
                         GdkDragContext   *context)
1321
{
1322 1323 1324
  GtkPlacesSidebarPlaceType place_type;
  GtkPlacesSidebarSectionType section_type;
  gboolean valid = FALSE;
1325
  gchar *uri;
1326 1327
  GFile *dest_file;
  gint drag_action;
1328

1329 1330
  if (row == NULL)
    return FALSE;
1331

1332 1333 1334 1335 1336
  g_object_get (row,
                "place-type", &place_type,
                "section_type", &section_type,
                "uri", &uri,
                NULL);
1337

1338
  if (place_type == PLACES_CONNECT_TO_SERVER)
1339 1340 1341 1342
    {
      g_free (uri);
      return FALSE;
    }
Matthias Clasen's avatar
Matthias Clasen committed
1343

1344
  if (place_type == PLACES_DROP_FEEDBACK)
1345 1346 1347 1348
    {
      g_free (uri);
      return TRUE;
    }
Matthias Clasen's avatar
Matthias Clasen committed
1349

1350 1351 1352 1353
  /* Disallow drops on recent:/// */
  if (place_type == PLACES_BUILT_IN)
    {
      if (g_strcmp0 (uri, "recent:///") == 0)
1354 1355 1356 1357
        {
          g_free (uri);
          return FALSE;
        }
Matthias Clasen's avatar
Matthias Clasen committed
1358 1359 1360 1361
    }

  /* Dragging a bookmark? */
  if (sidebar->drag_data_received &&
1362
      sidebar->drag_data_info == DND_GTK_SIDEBAR_ROW)
Matthias Clasen's avatar
Matthias Clasen committed
1363 1364
    {
      /* Don't allow reordering bookmarks into non-bookmark areas */
1365
      valid = section_type == SECTION_BOOKMARKS;
Matthias Clasen's avatar
Matthias Clasen committed
1366 1367
    }
  else
1368
    {
Matthias Clasen's avatar
Matthias Clasen committed
1369
      /* Dragging a file */
1370
      if (context)
Matthias Clasen's avatar
Matthias Clasen committed
1371
        {
1372
          if (uri != NULL)
Matthias Clasen's avatar
Matthias Clasen committed
1373
            {
1374 1375 1376 1377 1378 1379 1380 1381 1382
              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
1383 1384
            }
        }
1385 1386 1387 1388 1389 1390 1391 1392 1393
      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
1394 1395
    }

1396
  g_free (uri);
1397 1398
  return valid;
}
Matthias Clasen's avatar
Matthias Clasen committed
1399

1400 1401 1402 1403 1404 1405 1406 1407
static void
update_possible_drop_targets (GtkPlacesSidebar *sidebar,
                              gboolean          dragging,
                              GdkDragContext   *context)
{
  GList *rows;
  GList *l;
  gboolean sensitive;
1408

1409
  rows = gtk_container_get_children (GTK_CONTAINER (sidebar->list_box));
1410

1411
  for (l = rows; l != NULL; l = l->next)
Matthias Clasen's avatar
Matthias Clasen committed
1412
    {
1413
      sensitive = !dragging || check_valid_drop_target (sidebar, GTK_SIDEBAR_ROW (l->data), context);
1414
      gtk_widget_set_sensitive (GTK_WIDGET (l->data), sensitive);
Matthias Clasen's avatar
Matthias Clasen committed
1415
    }
1416

1417
  g_list_free (rows);
1418 1419 1420
}

static gboolean
1421
get_drag_data (GtkWidget      *list_box,
Matthias Clasen's avatar
Matthias Clasen committed
1422 1423
               GdkDragContext *context,
               guint           time)
1424
{
Matthias Clasen's avatar
Matthias Clasen committed
1425
  GdkAtom target;
1426

1427
  target = gtk_drag_dest_find_target (list_box, context, NULL);
1428

Matthias Clasen's avatar
Matthias Clasen committed
1429 1430
  if (target == GDK_NONE)
    return FALSE;
1431

1432
  gtk_drag_get_data (list_box, context, target, time);
1433

Matthias Clasen's avatar
Matthias Clasen committed
1434
  return TRUE;
1435 1436 1437
}

static void
1438
free_drag_data (GtkPlacesSidebar *sidebar)
1439
{
Matthias Clasen's avatar
Matthias Clasen committed
1440
  sidebar->drag_data_received = FALSE;
1441

Matthias Clasen's avatar
Matthias Clasen committed
1442 1443 1444 1445 1446
  if (sidebar->drag_list)
    {
      g_list_free_full (sidebar->drag_list, g_object_unref);
      sidebar->drag_list = NULL;
    }
1447 1448 1449

}

1450 1451 1452 1453
static void
start_drop_feedback (GtkPlacesSidebar *sidebar,
                     GtkSidebarRow    *row,
                     GdkDragContext   *context)
1454
{
1455 1456 1457
  if (sidebar->drag_data_info != DND_GTK_SIDEBAR_ROW)
    {
      gtk_sidebar_row_reveal (GTK_SIDEBAR_ROW (sidebar->new_bookmark_row));
1458
      /* If the state is permanent, don't change it. The application controls it. */
1459 1460 1461
      if (sidebar->drop_state != DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT)
        sidebar->drop_state = DROP_STATE_NEW_BOOKMARK_ARMED;
    }
1462

1463
  update_possible_drop_targets (sidebar, TRUE, context);
1464 1465 1466
}

static void
1467
stop_drop_feedback (GtkPlacesSidebar *sidebar)
1468
{
1469
  update_possible_drop_targets (sidebar, FALSE, NULL);
1470

1471
  free_drag_data (sidebar);
1472

1473 1474
  if (sidebar->drop_state != DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT &&
      sidebar->new_bookmark_row != NULL)
Matthias Clasen's avatar
Matthias Clasen committed
1475
    {
1476 1477
      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
1478
    }
1479

1480
  if (sidebar->drag_row != NULL)
Matthias Clasen's avatar
Matthias Clasen committed
1481
    {
1482 1483 1484
      gtk_widget_show (sidebar->drag_row);
      sidebar->drag_row = NULL;
    }
1485

1486 1487 1488 1489
  if (sidebar->row_placeholder != NULL)
    {
      gtk_widget_destroy (sidebar->row_placeholder);
      sidebar->row_placeholder = NULL;
Matthias Clasen's avatar
Matthias Clasen committed
1490
    }
1491 1492 1493

  sidebar->dragging_over = FALSE;
  sidebar->drag_data_info = DND_UNKNOWN;
1494 1495
}

1496 1497 1498 1499
static gboolean
on_motion_notify_event (GtkWidget      *widget,
                        GdkEventMotion *event,
                        gpointer        user_data)
Matthias Clasen's avatar
Matthias Clasen committed
1500
{
1501
  GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (user_data);
Matthias Clasen's avatar
Matthias Clasen committed
1502

1503
  if (sidebar->drag_row == NULL || sidebar->dragging_over)
1504
    return FALSE;
Matthias Clasen's avatar
Matthias Clasen committed
1505

1506
  if (!(event->state & GDK_BUTTON1_MASK))
1507
    return FALSE;
Matthias Clasen's avatar
Matthias Clasen committed
1508

1509
  if (gtk_drag_check_threshold (widget,
1510 1511
                                sidebar->drag_root_x, sidebar->drag_root_y,
                                event->x_root, event->y_root))
1512
    {
1513
      sidebar->dragging_over = TRUE;
Matthias Clasen's avatar
Matthias Clasen committed
1514

1515 1516 1517
      gtk_drag_begin_with_coordinates (widget, sidebar->source_targets, GDK_ACTION_MOVE,
                                       GDK_BUTTON_PRIMARY, (GdkEvent*)event,
                                       -1, -1);
Matthias Clasen's avatar
Matthias Clasen committed
1518
    }
1519 1520

  return FALSE;
1521 1522 1523
}

static void
1524 1525 1526
drag_begin_callback (GtkWidget      *widget,
                     GdkDragContext *context,
                     gpointer        user_data)
1527
{
Carlos Soriano Sánchez's avatar