nautilus-places-sidebar.c 95.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */

/*
 *  Nautilus
 *
 *  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.
 *
20 21
 *  Authors : Mr Jamie McCracken (jamiemcc at blueyonder dot co dot uk)
 *            Cosimo Cecchi <cosimoc@gnome.org>
22 23 24 25 26
 *
 */
 
#include <config.h>

27
#include <gdk/gdkkeysyms.h>
28
#include <gtk/gtk.h>
29
#include <glib/gi18n.h>
30 31
#include <gio/gio.h>

32
#include <libnautilus-private/nautilus-dnd.h>
33 34 35
#include <libnautilus-private/nautilus-bookmark.h>
#include <libnautilus-private/nautilus-global-preferences.h>
#include <libnautilus-private/nautilus-module.h>
Alexander Larsson's avatar
Alexander Larsson committed
36
#include <libnautilus-private/nautilus-file.h>
37
#include <libnautilus-private/nautilus-file-utilities.h>
38
#include <libnautilus-private/nautilus-file-operations.h>
39
#include <libnautilus-private/nautilus-trash-monitor.h>
Luca Ferretti's avatar
Luca Ferretti committed
40
#include <libnautilus-private/nautilus-icon-names.h>
41 42 43 44

#include <eel/eel-debug.h>
#include <eel/eel-gtk-extensions.h>
#include <eel/eel-glib-extensions.h>
45
#include <eel/eel-graphic-effects.h>
46 47
#include <eel/eel-string.h>
#include <eel/eel-stock-dialogs.h>
48

49
#include "nautilus-application.h"
50 51
#include "nautilus-bookmark-list.h"
#include "nautilus-places-sidebar.h"
52
#include "nautilus-properties-window.h"
53
#include "nautilus-window.h"
54
#include "nautilus-window-slot.h"
55

56 57 58
#define DEBUG_FLAG NAUTILUS_DEBUG_PLACES
#include <libnautilus-private/nautilus-debug.h>

59 60
#define EJECT_BUTTON_XPAD 6
#define ICON_CELL_XPAD 6
61

62 63 64
typedef struct {
	GtkScrolledWindow  parent;
	GtkTreeView        *tree_view;
65
	GtkCellRenderer    *eject_icon_cell_renderer;
66 67
	char 	           *uri;
	GtkListStore       *store;
68
	NautilusWindow *window;
69
	NautilusBookmarkList *bookmarks;
Alexander Larsson's avatar
Alexander Larsson committed
70
	GVolumeMonitor *volume_monitor;
71

72 73 74
	gboolean devices_header_added;
	gboolean bookmarks_header_added;

75 76 77
	/* DnD */
	GList     *drag_list;
	gboolean  drag_data_received;
78
	int       drag_data_info;
79 80
	gboolean  drop_occured;

81
	GtkWidget *popup_menu;
82
	GtkWidget *popup_menu_open_in_new_tab_item;
83
	GtkWidget *popup_menu_add_shortcut_item;
84 85
	GtkWidget *popup_menu_remove_item;
	GtkWidget *popup_menu_rename_item;
86 87 88 89
	GtkWidget *popup_menu_separator_item;
	GtkWidget *popup_menu_mount_item;
	GtkWidget *popup_menu_unmount_item;
	GtkWidget *popup_menu_eject_item;
90
	GtkWidget *popup_menu_rescan_item;
91
	GtkWidget *popup_menu_empty_trash_item;
92 93
	GtkWidget *popup_menu_start_item;
	GtkWidget *popup_menu_stop_item;
94 95
	GtkWidget *popup_menu_properties_separator_item;
	GtkWidget *popup_menu_properties_item;
96 97 98

	/* volume mounting - delayed open process */
	gboolean mounting;
99
	NautilusWindowSlot *go_to_after_mount_slot;
100
	NautilusWindowOpenFlags go_to_after_mount_flags;
101 102

	GtkTreePath *eject_highlight_path;
103 104

	guint bookmarks_changed_id;
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
} NautilusPlacesSidebar;

typedef struct {
	GtkScrolledWindowClass parent;
} NautilusPlacesSidebarClass;

typedef struct {
        GObject parent;
} NautilusPlacesSidebarProvider;

typedef struct {
        GObjectClass parent;
} NautilusPlacesSidebarProviderClass;

enum {
	PLACES_SIDEBAR_COLUMN_ROW_TYPE,
	PLACES_SIDEBAR_COLUMN_URI,
122
	PLACES_SIDEBAR_COLUMN_DRIVE,
123
	PLACES_SIDEBAR_COLUMN_VOLUME,
124
	PLACES_SIDEBAR_COLUMN_MOUNT,
125 126
	PLACES_SIDEBAR_COLUMN_NAME,
	PLACES_SIDEBAR_COLUMN_ICON,
127
	PLACES_SIDEBAR_COLUMN_INDEX,
128
	PLACES_SIDEBAR_COLUMN_EJECT,
129 130
	PLACES_SIDEBAR_COLUMN_NO_EJECT,
	PLACES_SIDEBAR_COLUMN_BOOKMARK,
131
	PLACES_SIDEBAR_COLUMN_TOOLTIP,
132
	PLACES_SIDEBAR_COLUMN_EJECT_ICON,
133 134 135
	PLACES_SIDEBAR_COLUMN_SECTION_TYPE,
	PLACES_SIDEBAR_COLUMN_HEADING_TEXT,

136 137 138 139 140
	PLACES_SIDEBAR_COLUMN_COUNT
};

typedef enum {
	PLACES_BUILT_IN,
141
	PLACES_XDG_DIR,
142 143
	PLACES_MOUNTED_VOLUME,
	PLACES_BOOKMARK,
144
	PLACES_HEADING,
145 146
} PlaceType;

147 148 149 150 151 152 153
typedef enum {
	SECTION_DEVICES,
	SECTION_BOOKMARKS,
	SECTION_COMPUTER,
	SECTION_NETWORK,
} SectionType;

154 155
static void  open_selected_bookmark                    (NautilusPlacesSidebar        *sidebar,
							GtkTreeModel                 *model,
156
							GtkTreeIter                  *iter,
157
							NautilusWindowOpenFlags flags);
158 159
static void  nautilus_places_sidebar_style_set         (GtkWidget                    *widget,
							GtkStyle                     *previous_style);
160 161 162 163 164 165 166 167
static gboolean eject_or_unmount_bookmark              (NautilusPlacesSidebar *sidebar,
							GtkTreePath *path);
static gboolean eject_or_unmount_selection             (NautilusPlacesSidebar *sidebar);
static void  check_unmount_and_eject                   (GMount *mount,
							GVolume *volume,
							GDrive *drive,
							gboolean *show_unmount,
							gboolean *show_eject);
168

169 170
static void bookmarks_check_popup_sensitivity          (NautilusPlacesSidebar *sidebar);

171 172 173 174 175 176 177
/* Identifiers for target types */
enum {
  GTK_TREE_MODEL_ROW,
  TEXT_URI_LIST
};

/* Target types for dragging from the shortcuts list */
178
static const GtkTargetEntry nautilus_shortcuts_source_targets[] = {
179 180 181
	{ "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, GTK_TREE_MODEL_ROW }
};

182
/* Target types for dropping into the shortcuts list */
183
static const GtkTargetEntry nautilus_shortcuts_drop_targets [] = {
184 185
	{ "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, GTK_TREE_MODEL_ROW },
	{ "text/uri-list", 0, TEXT_URI_LIST }
186 187
};

188 189
/* Drag and drop interface declarations */
typedef struct {
190
  GtkListStore parent;
191 192

  NautilusPlacesSidebar *sidebar;
193
} NautilusShortcutsModel;
194 195

typedef struct {
196 197
  GtkListStoreClass parent_class;
} NautilusShortcutsModelClass;
198

199 200
#define NAUTILUS_TYPE_SHORTCUTS_MODEL (_nautilus_shortcuts_model_get_type ())
#define NAUTILUS_SHORTCUTS_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_SHORTCUTS_MODEL_TYPE, NautilusShortcutsModel))
201

202 203 204
GType _nautilus_shortcuts_model_get_type (void);
static void _nautilus_shortcuts_model_drag_source_init (GtkTreeDragSourceIface *iface);
G_DEFINE_TYPE_WITH_CODE (NautilusShortcutsModel, _nautilus_shortcuts_model, GTK_TYPE_LIST_STORE,
205
			 G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE,
206 207
						_nautilus_shortcuts_model_drag_source_init));
static GtkListStore *nautilus_shortcuts_model_new (NautilusPlacesSidebar *sidebar);
208

209
G_DEFINE_TYPE (NautilusPlacesSidebar, nautilus_places_sidebar, GTK_TYPE_SCROLLED_WINDOW);
210

211
static GdkPixbuf *
212 213
get_eject_icon (NautilusPlacesSidebar *sidebar,
		gboolean highlighted)
214 215
{
	GdkPixbuf *eject;
216 217
	GtkIconInfo *icon_info;
	GIcon *icon;
218
	int icon_size;
219 220
	GtkIconTheme *icon_theme;
	GtkStyleContext *style;
221
	GtkStateFlags state;
222

223
	icon_theme = gtk_icon_theme_get_default ();
224
	icon_size = nautilus_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU);
225 226
	icon = g_themed_icon_new_with_default_fallbacks ("media-eject-symbolic");
	icon_info = gtk_icon_theme_lookup_by_gicon (icon_theme, icon, icon_size, 0);
227

228
	style = gtk_widget_get_style_context (GTK_WIDGET (sidebar));
229 230
	gtk_style_context_save (style);

231 232 233
	if (icon_info != NULL) {
		state = gtk_widget_get_state_flags (GTK_WIDGET (sidebar));
		gtk_style_context_add_class (style, GTK_STYLE_CLASS_IMAGE);
234

235 236 237
		if (highlighted) {
			state |= GTK_STATE_FLAG_PRELIGHT;
		}
238

239
		gtk_style_context_set_state (style, state);
240

241 242 243 244 245 246 247 248
		eject = gtk_icon_info_load_symbolic_for_context (icon_info,
								 style,
								 NULL,
								 NULL);

		gtk_icon_info_free (icon_info);
	} else {
		GtkIconSet *icon_set;
249

250 251 252 253 254 255
		gtk_style_context_set_state (style, GTK_STATE_FLAG_NORMAL);
		icon_set = gtk_style_context_lookup_icon_set (style, GTK_STOCK_MISSING_IMAGE);
		eject = gtk_icon_set_render_icon_pixbuf (icon_set, style, GTK_ICON_SIZE_MENU);
	}

	gtk_style_context_restore (style);
256
	g_object_unref (icon);
257

258 259 260
	return eject;
}

261 262 263 264 265 266
static gboolean
is_built_in_bookmark (NautilusFile *file)
{
	gboolean built_in;
	gint idx;

267 268 269 270
	if (nautilus_file_is_home (file)) {
		return TRUE;
	}

271 272
	if (nautilus_file_is_desktop_directory (file) &&
	    !g_settings_get_boolean (gnome_background_preferences, NAUTILUS_PREFERENCES_SHOW_DESKTOP)) {
273 274 275
		return FALSE;
	}

276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
	built_in = FALSE;

	for (idx = 0; idx < G_USER_N_DIRECTORIES; idx++) {
		/* PUBLIC_SHARE and TEMPLATES are not in our built-in list */
		if (nautilus_file_is_user_special_directory (file, idx)) {
			if (idx != G_USER_DIRECTORY_PUBLIC_SHARE &&  idx != G_USER_DIRECTORY_TEMPLATES) {
				built_in = TRUE;
			}

			break;
		}
	}

	return built_in;
}

static GtkTreeIter
add_heading (NautilusPlacesSidebar *sidebar,
	     SectionType section_type,
	     const gchar *title)
{
297
	GtkTreeIter iter;
298 299 300 301 302 303 304 305 306 307

	gtk_list_store_append (sidebar->store, &iter);
	gtk_list_store_set (sidebar->store, &iter,
			    PLACES_SIDEBAR_COLUMN_ROW_TYPE, PLACES_HEADING,
			    PLACES_SIDEBAR_COLUMN_SECTION_TYPE, section_type,	
			    PLACES_SIDEBAR_COLUMN_HEADING_TEXT, title,
			    PLACES_SIDEBAR_COLUMN_EJECT, FALSE,
			    PLACES_SIDEBAR_COLUMN_NO_EJECT, TRUE,
			    -1);

308
	return iter;
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
}

static void
check_heading_for_section (NautilusPlacesSidebar *sidebar,
			   SectionType section_type)
{
	switch (section_type) {
	case SECTION_DEVICES:
		if (!sidebar->devices_header_added) {
			add_heading (sidebar, SECTION_DEVICES,
				     _("Devices"));
			sidebar->devices_header_added = TRUE;
		}

		break;
	case SECTION_BOOKMARKS:
		if (!sidebar->bookmarks_header_added) {
			add_heading (sidebar, SECTION_BOOKMARKS,
				     _("Bookmarks"));
			sidebar->bookmarks_header_added = TRUE;
		}

		break;
	default:
		break;
	}
}

337
static void
338
add_place (NautilusPlacesSidebar *sidebar,
339
	   PlaceType place_type,
340
	   SectionType section_type,
341
	   const char *name,
Alexander Larsson's avatar
Alexander Larsson committed
342
	   GIcon *icon,
343
	   const char *uri,
Alexander Larsson's avatar
Alexander Larsson committed
344 345
	   GDrive *drive,
	   GVolume *volume,
346
	   GMount *mount,
347 348
	   const int index,
	   const char *tooltip)
349 350
{
	GdkPixbuf            *pixbuf;
351
	GtkTreeIter           iter;
352
	GdkPixbuf	     *eject;
Alexander Larsson's avatar
Alexander Larsson committed
353 354
	NautilusIconInfo *icon_info;
	int icon_size;
355
	gboolean show_eject, show_unmount;
356
	gboolean show_eject_button;
357

358 359
	check_heading_for_section (sidebar, section_type);

Alexander Larsson's avatar
Alexander Larsson committed
360 361 362 363 364
	icon_size = nautilus_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU);
	icon_info = nautilus_icon_info_lookup (icon, icon_size);

	pixbuf = nautilus_icon_info_get_pixbuf_at_size (icon_info, icon_size);
	g_object_unref (icon_info);
365

366 367 368 369 370 371
	check_unmount_and_eject (mount, volume, drive,
				 &show_unmount, &show_eject);

	if (show_unmount || show_eject) {
		g_assert (place_type != PLACES_BOOKMARK);
	}
372 373 374 375 376 377 378

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

379
	if (show_eject_button) {
380
		eject = get_eject_icon (sidebar, FALSE);
381 382 383 384
	} else {
		eject = NULL;
	}

385 386
	gtk_list_store_append (sidebar->store, &iter);
	gtk_list_store_set (sidebar->store, &iter,
387 388 389
			    PLACES_SIDEBAR_COLUMN_ICON, pixbuf,
			    PLACES_SIDEBAR_COLUMN_NAME, name,
			    PLACES_SIDEBAR_COLUMN_URI, uri,
390
			    PLACES_SIDEBAR_COLUMN_DRIVE, drive,
391
			    PLACES_SIDEBAR_COLUMN_VOLUME, volume,
392
			    PLACES_SIDEBAR_COLUMN_MOUNT, mount,
393
			    PLACES_SIDEBAR_COLUMN_ROW_TYPE, place_type,
394
			    PLACES_SIDEBAR_COLUMN_INDEX, index,
395 396
			    PLACES_SIDEBAR_COLUMN_EJECT, show_eject_button,
			    PLACES_SIDEBAR_COLUMN_NO_EJECT, !show_eject_button,
397
			    PLACES_SIDEBAR_COLUMN_BOOKMARK, place_type != PLACES_BOOKMARK,
398
			    PLACES_SIDEBAR_COLUMN_TOOLTIP, tooltip,
399
			    PLACES_SIDEBAR_COLUMN_EJECT_ICON, eject,
400
			    PLACES_SIDEBAR_COLUMN_SECTION_TYPE, section_type,
401
			    -1);
402

403 404 405
	if (pixbuf != NULL) {
		g_object_unref (pixbuf);
	}
406
}
407

408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
typedef struct {
	const gchar *location;
	const gchar *last_uri;
	NautilusPlacesSidebar *sidebar;
	GtkTreePath *path;
} RestoreLocationData;

static gboolean
restore_selection_foreach (GtkTreeModel *model,
			   GtkTreePath *path,
			   GtkTreeIter *iter,
			   gpointer user_data)
{
	RestoreLocationData *data = user_data;
	gchar *uri;

	gtk_tree_model_get (model, iter,
			    PLACES_SIDEBAR_COLUMN_URI, &uri,
			    -1);

	if (g_strcmp0 (uri, data->last_uri) == 0 ||
	    g_strcmp0 (uri, data->location) == 0) {
		data->path = gtk_tree_path_copy (path);
	}

	g_free (uri);

	return (data->path != NULL);
436 437
}

438
static void
439 440 441
sidebar_update_restore_selection (NautilusPlacesSidebar *sidebar,
				  const gchar *location,
				  const gchar *last_uri)
442
{
443 444
	RestoreLocationData data;
	GtkTreeSelection *selection;
445

446 447 448 449
	data.location = location;
	data.last_uri = last_uri;
	data.sidebar = sidebar;
	data.path = NULL;
450

451 452 453 454 455 456 457
	gtk_tree_model_foreach (GTK_TREE_MODEL (sidebar->store),
				restore_selection_foreach, &data);

	if (data.path != NULL) {
		selection = gtk_tree_view_get_selection (sidebar->tree_view);
		gtk_tree_selection_select_path (selection, data.path);
		gtk_tree_path_free (data.path);
458 459 460
	}
}

461 462 463
static void
update_places (NautilusPlacesSidebar *sidebar)
{
Alexander Larsson's avatar
Alexander Larsson committed
464 465
	NautilusBookmark *bookmark;
	GtkTreeSelection *selection;
466
	GtkTreeIter last_iter;
Cosimo Cecchi's avatar
Cosimo Cecchi committed
467
	GtkTreeModel *model;
Alexander Larsson's avatar
Alexander Larsson committed
468
	GVolumeMonitor *volume_monitor;
469 470
	GList *mounts, *l, *ll;
	GMount *mount;
Alexander Larsson's avatar
Alexander Larsson committed
471 472
	GList *drives;
	GDrive *drive;
473 474
	GList *volumes;
	GVolume *volume;
Alexander Larsson's avatar
Alexander Larsson committed
475
	int bookmark_count, index;
476
	char *location, *mount_uri, *name, *last_uri, *identifier;
477
	const gchar *path, *bookmark_name;
Alexander Larsson's avatar
Alexander Larsson committed
478 479
	GIcon *icon;
	GFile *root;
480
	NautilusWindowSlot *slot;
481
	char *tooltip;
482
	GList *network_mounts, *network_volumes;
483
	NautilusFile *file;
484

485 486
	DEBUG ("Updating places sidebar");

Cosimo Cecchi's avatar
Cosimo Cecchi committed
487 488 489
	model = NULL;
	last_uri = NULL;

490
	selection = gtk_tree_view_get_selection (sidebar->tree_view);
491 492 493 494 495
	if (gtk_tree_selection_get_selected (selection, &model, &last_iter)) {
		gtk_tree_model_get (model,
				    &last_iter,
				    PLACES_SIDEBAR_COLUMN_URI, &last_uri, -1);
	}
496
	gtk_list_store_clear (sidebar->store);
497

498 499 500
	sidebar->devices_header_added = FALSE;
	sidebar->bookmarks_header_added = FALSE;

501
	slot = nautilus_window_get_active_slot (sidebar->window);
502
	location = nautilus_window_slot_get_current_uri (slot);
503

504
	network_mounts = network_volumes = NULL;
Alexander Larsson's avatar
Alexander Larsson committed
505
	volume_monitor = sidebar->volume_monitor;
506 507

	/* first go through all connected drives */
Alexander Larsson's avatar
Alexander Larsson committed
508
	drives = g_volume_monitor_get_connected_drives (volume_monitor);
509

510 511
	for (l = drives; l != NULL; l = l->next) {
		drive = l->data;
512 513 514

		volumes = g_drive_get_volumes (drive);
		if (volumes != NULL) {
515 516
			for (ll = volumes; ll != NULL; ll = ll->next) {
				volume = ll->data;
517 518 519 520 521 522 523 524 525
				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);

526 527 528 529
				mount = g_volume_get_mount (volume);
				if (mount != NULL) {
					/* Show mounted volume in the sidebar */
					icon = g_mount_get_icon (mount);
530
					root = g_mount_get_default_location (mount);
531 532
					mount_uri = g_file_get_uri (root);
					name = g_mount_get_name (mount);
533
					tooltip = g_file_get_parse_name (root);
534

535 536 537 538
					add_place (sidebar, PLACES_MOUNTED_VOLUME,
						   SECTION_DEVICES,
						   name, icon, mount_uri,
						   drive, volume, mount, 0, tooltip);
539
					g_object_unref (root);
540 541
					g_object_unref (mount);
					g_object_unref (icon);
542
					g_free (tooltip);
543 544 545 546 547 548 549 550 551 552 553 554 555
					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_icon (volume);
					name = g_volume_get_name (volume);
556
					tooltip = g_strdup_printf (_("Mount and open %s"), name);
557

558 559 560 561
					add_place (sidebar, PLACES_MOUNTED_VOLUME,
						   SECTION_DEVICES,
						   name, icon, NULL,
						   drive, volume, NULL, 0, tooltip);
562 563
					g_object_unref (icon);
					g_free (name);
564
					g_free (tooltip);
565
				}
Alexander Larsson's avatar
Alexander Larsson committed
566
				g_object_unref (volume);
567
			}
568
			g_list_free (volumes);
569 570 571 572 573 574 575 576 577 578 579 580
		} 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_icon (drive);
				name = g_drive_get_name (drive);
581
				tooltip = g_strdup_printf (_("Mount and open %s"), name);
582

583 584 585 586
				add_place (sidebar, PLACES_BUILT_IN,
					   SECTION_DEVICES,
					   name, icon, NULL,
					   drive, NULL, NULL, 0, tooltip);
Alexander Larsson's avatar
Alexander Larsson committed
587
				g_object_unref (icon);
588
				g_free (tooltip);
589 590
				g_free (name);
			}
591
		}
Alexander Larsson's avatar
Alexander Larsson committed
592
		g_object_unref (drive);
593 594 595
	}
	g_list_free (drives);

596 597
	/* add all volumes that is not associated with a drive */
	volumes = g_volume_monitor_get_volumes (volume_monitor);
598 599
	for (l = volumes; l != NULL; l = l->next) {
		volume = l->data;
Alexander Larsson's avatar
Alexander Larsson committed
600 601
		drive = g_volume_get_drive (volume);
		if (drive != NULL) {
602 603 604 605
		    	g_object_unref (volume);
			g_object_unref (drive);
			continue;
		}
606 607 608 609 610 611 612 613 614 615

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

616 617 618
		mount = g_volume_get_mount (volume);
		if (mount != NULL) {
			icon = g_mount_get_icon (mount);
619
			root = g_mount_get_default_location (mount);
620
			mount_uri = g_file_get_uri (root);
621
			tooltip = g_file_get_parse_name (root);
622 623
			g_object_unref (root);
			name = g_mount_get_name (mount);
624 625 626 627
			add_place (sidebar, PLACES_MOUNTED_VOLUME,
				   SECTION_DEVICES,
				   name, icon, mount_uri,
				   NULL, volume, mount, 0, tooltip);
628 629 630
			g_object_unref (mount);
			g_object_unref (icon);
			g_free (name);
631
			g_free (tooltip);
632 633 634 635 636
			g_free (mount_uri);
		} else {
			/* see comment above in why we add an icon for an unmounted mountable volume */
			icon = g_volume_get_icon (volume);
			name = g_volume_get_name (volume);
637 638 639 640
			add_place (sidebar, PLACES_MOUNTED_VOLUME,
				   SECTION_DEVICES,
				   name, icon, NULL,
				   NULL, volume, NULL, 0, name);
641 642 643 644 645 646
			g_object_unref (icon);
			g_free (name);
		}
		g_object_unref (volume);
	}
	g_list_free (volumes);
647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665
	
	/* add bookmarks */
	bookmark_count = nautilus_bookmark_list_length (sidebar->bookmarks);

	for (index = 0; index < bookmark_count; ++index) {
		bookmark = nautilus_bookmark_list_item_at (sidebar->bookmarks, index);

		if (nautilus_bookmark_uri_known_not_to_exist (bookmark)) {
			continue;
		}

		root = nautilus_bookmark_get_location (bookmark);
		file = nautilus_file_get (root);

		if (is_built_in_bookmark (file)) {
			g_object_unref (root);
			nautilus_file_unref (file);
			continue;
		}
666
		nautilus_file_unref (file);
667

668
		bookmark_name = nautilus_bookmark_get_name (bookmark);
669 670 671 672
		icon = nautilus_bookmark_get_icon (bookmark);
		mount_uri = nautilus_bookmark_get_uri (bookmark);
		tooltip = g_file_get_parse_name (root);

673 674 675 676 677
		add_place (sidebar, PLACES_BOOKMARK,
			   SECTION_BOOKMARKS,
			   bookmark_name, icon, mount_uri,
			   NULL, NULL, NULL, index,
			   tooltip);
678 679 680 681 682 683
		g_object_unref (root);
		g_object_unref (icon);
		g_free (mount_uri);
		g_free (tooltip);
	}

684 685
	add_heading (sidebar, SECTION_COMPUTER,
		     _("Computer"));
686 687 688 689

	/* add built in bookmarks */

	/* home folder */
690 691
	mount_uri = nautilus_get_home_directory_uri ();
	icon = g_themed_icon_new (NAUTILUS_ICON_HOME);
692 693 694 695 696
	add_place (sidebar, PLACES_BUILT_IN,
		   SECTION_COMPUTER,
		   _("Home"), icon,
		   mount_uri, NULL, NULL, NULL, 0,
		   _("Open your personal folder"));
697 698
	g_object_unref (icon);
	g_free (mount_uri);
699

700
	if (g_settings_get_boolean (gnome_background_preferences, NAUTILUS_PREFERENCES_SHOW_DESKTOP)) {
701
		/* desktop */
702
		mount_uri = nautilus_get_desktop_directory_uri ();
703
		icon = g_themed_icon_new (NAUTILUS_ICON_DESKTOP);
704 705 706 707 708
		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"));
709 710 711
		g_object_unref (icon);
		g_free (mount_uri);
	}
712 713 714 715 716 717 718 719 720 721 722 723

	
	/* XDG directories */
	for (index = 0; index < G_USER_N_DIRECTORIES; index++) {

		if (index == G_USER_DIRECTORY_DESKTOP ||
		    index == G_USER_DIRECTORY_TEMPLATES ||
		    index == G_USER_DIRECTORY_PUBLIC_SHARE) {
			continue;
		}

		path = g_get_user_special_dir (index);
724

725 726 727 728 729
		/* 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 || g_strcmp0 (path, g_get_home_dir ()) == 0) {
730 731 732
			continue;
		}

733 734 735 736 737 738
		root = g_file_new_for_path (path);
		name = g_file_get_basename (root);
		icon = nautilus_user_special_directory_get_gicon (index);
		mount_uri = g_file_get_uri (root);
		tooltip = g_file_get_parse_name (root);

739 740 741 742 743
		add_place (sidebar, PLACES_XDG_DIR,
			   SECTION_COMPUTER,
			   name, icon, mount_uri,
			   NULL, NULL, NULL, 0,
			   tooltip);
744 745 746 747 748 749
		g_free (name);
		g_object_unref (root);
		g_object_unref (icon);
		g_free (mount_uri);
		g_free (tooltip);
	}
750 751 752

	/* add mounts that has no volume (/etc/mtab mounts, ftp, sftp,...) */
	mounts = g_volume_monitor_get_mounts (volume_monitor);
753

754 755
	for (l = mounts; l != NULL; l = l->next) {
		mount = l->data;
David Zeuthen's avatar
David Zeuthen committed
756 757 758 759
		if (g_mount_is_shadowed (mount)) {
			g_object_unref (mount);
			continue;
		}
760 761 762 763
		volume = g_mount_get_volume (mount);
		if (volume != NULL) {
		    	g_object_unref (volume);
			g_object_unref (mount);
764 765
			continue;
		}
766
		root = g_mount_get_default_location (mount);
767 768

		if (!g_file_is_native (root)) {
769
			network_mounts = g_list_prepend (network_mounts, mount);
770 771 772 773
			continue;
		}

		icon = g_mount_get_icon (mount);
Alexander Larsson's avatar
Alexander Larsson committed
774
		mount_uri = g_file_get_uri (root);
775
		name = g_mount_get_name (mount);
776
		tooltip = g_file_get_parse_name (root);
777 778 779 780
		add_place (sidebar, PLACES_MOUNTED_VOLUME,
			   SECTION_COMPUTER,
			   name, icon, mount_uri,
			   NULL, NULL, mount, 0, tooltip);
781
		g_object_unref (root);
782
		g_object_unref (mount);
Alexander Larsson's avatar
Alexander Larsson committed
783
		g_object_unref (icon);
784 785
		g_free (name);
		g_free (mount_uri);
786
		g_free (tooltip);
787
	}
788
	g_list_free (mounts);
789

790 791 792
	/* file system root */
 	mount_uri = "file:///"; /* No need to strdup */
	icon = g_themed_icon_new (NAUTILUS_ICON_FILESYSTEM);
793 794 795 796 797
	add_place (sidebar, PLACES_BUILT_IN,
		   SECTION_COMPUTER,
		   _("File System"), icon,
		   mount_uri, NULL, NULL, NULL, 0,
		   _("Open the contents of the File System"));
798 799
	g_object_unref (icon);

800
	mount_uri = "trash:///"; /* No need to strdup */
Alexander Larsson's avatar
Alexander Larsson committed
801
	icon = nautilus_trash_monitor_get_icon ();
802 803 804 805 806
	add_place (sidebar, PLACES_BUILT_IN,
		   SECTION_COMPUTER,
		   _("Trash"), icon, mount_uri,
		   NULL, NULL, NULL, 0,
		   _("Open the trash"));
Alexander Larsson's avatar
Alexander Larsson committed
807
	g_object_unref (icon);
808

809
	/* network */
810 811
	add_heading (sidebar, SECTION_NETWORK,
		     _("Network"));
812

813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837
	network_volumes = g_list_reverse (network_volumes);
	for (l = network_volumes; l != NULL; l = l->next) {
		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_icon (volume);
			name = g_volume_get_name (volume);
			tooltip = g_strdup_printf (_("Mount and open %s"), name);

			add_place (sidebar, PLACES_MOUNTED_VOLUME,
				   SECTION_NETWORK,
				   name, icon, NULL,
				   NULL, volume, NULL, 0, tooltip);
			g_object_unref (icon);
			g_free (name);
			g_free (tooltip);
		}
	}

	g_list_free_full (network_volumes, g_object_unref);

838 839 840 841 842 843 844
	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_icon (mount);
		mount_uri = g_file_get_uri (root);
		name = g_mount_get_name (mount);
845
		tooltip = g_file_get_parse_name (root);
846 847 848 849
		add_place (sidebar, PLACES_MOUNTED_VOLUME,
			   SECTION_NETWORK,
			   name, icon, mount_uri,
			   NULL, NULL, mount, 0, tooltip);
850
		g_object_unref (root);
Alexander Larsson's avatar
Alexander Larsson committed
851
		g_object_unref (icon);
852
		g_free (name);
853
		g_free (mount_uri);
854
		g_free (tooltip);
855
	}
856

857
	g_list_free_full (network_mounts, g_object_unref);
858 859 860 861

	/* network:// */
 	mount_uri = "network:///"; /* No need to strdup */
	icon = g_themed_icon_new (NAUTILUS_ICON_NETWORK);
862 863 864 865 866
	add_place (sidebar, PLACES_BUILT_IN,
		   SECTION_NETWORK,
		   _("Browse Network"), icon,
		   mount_uri, NULL, NULL, NULL, 0,
		   _("Browse the contents of the network"));
867
	g_object_unref (icon);
868

869 870
	/* restore selection */
	sidebar_update_restore_selection (sidebar, location, last_uri);
871

872
	g_free (location);
873
	g_free (last_uri);
874 875
}

876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906
static void
mount_added_callback (GVolumeMonitor *volume_monitor,
		      GMount *mount,
		      NautilusPlacesSidebar *sidebar)
{
	update_places (sidebar);
}

static void
mount_removed_callback (GVolumeMonitor *volume_monitor,
			GMount *mount,
			NautilusPlacesSidebar *sidebar)
{
	update_places (sidebar);
}

static void
mount_changed_callback (GVolumeMonitor *volume_monitor,
			GMount *mount,
			NautilusPlacesSidebar *sidebar)
{
	update_places (sidebar);
}

static void
volume_added_callback (GVolumeMonitor *volume_monitor,
		       GVolume *volume,
		       NautilusPlacesSidebar *sidebar)
{
	update_places (sidebar);
}
907 908

static void
909
volume_removed_callback (GVolumeMonitor *volume_monitor,
Alexander Larsson's avatar
Alexander Larsson committed
910
			 GVolume *volume,
911 912 913 914 915 916
			 NautilusPlacesSidebar *sidebar)
{
	update_places (sidebar);
}

static void
917 918 919
volume_changed_callback (GVolumeMonitor *volume_monitor,
			 GVolume *volume,
			 NautilusPlacesSidebar *sidebar)
920
{
921
	update_places (sidebar);
922 923 924
}

static void
Alexander Larsson's avatar
Alexander Larsson committed
925 926
drive_disconnected_callback (GVolumeMonitor *volume_monitor,
			     GDrive         *drive,
927 928 929 930 931 932
			     NautilusPlacesSidebar *sidebar)
{
	update_places (sidebar);
}

static void
Alexander Larsson's avatar
Alexander Larsson committed
933 934
drive_connected_callback (GVolumeMonitor *volume_monitor,
			  GDrive         *drive,
935
			  NautilusPlacesSidebar *sidebar)
936 937 938 939
{
	update_places (sidebar);
}

940 941 942 943 944 945 946 947
static void
drive_changed_callback (GVolumeMonitor *volume_monitor,
			GDrive         *drive,
			NautilusPlacesSidebar *sidebar)
{
	update_places (sidebar);
}

948
static gboolean
949 950 951 952
over_eject_button (NautilusPlacesSidebar *sidebar,
		   gint x,
		   gint y,
		   GtkTreePath **path)
953 954
{
	GtkTreeViewColumn *column;
955
	int width, x_offset, hseparator;
956
	int eject_button_size;
957 958 959
	gboolean show_eject;
	GtkTreeIter iter;
	GtkTreeModel *model;
960 961

	*path = NULL;
962
	model = gtk_tree_view_get_model (sidebar->tree_view);
963

964 965
	if (gtk_tree_view_get_path_at_pos (sidebar->tree_view,
					   x, y,
966
					   path, &column, NULL, NULL)) {
967 968 969 970 971 972 973 974 975 976

		gtk_tree_model_get_iter (model, &iter, *path);
		gtk_tree_model_get (model, &iter,
				    PLACES_SIDEBAR_COLUMN_EJECT, &show_eject,
				    -1);

		if (!show_eject) {
			goto out;
		}

977 978

		gtk_widget_style_get (GTK_WIDGET (sidebar->tree_view),
979
				      "horizontal-separator", &hseparator,
980 981
				      NULL);

982 983 984 985 986 987 988
		/* Reload cell attributes for this particular row */
		gtk_tree_view_column_cell_set_cell_data (column,
							 model, &iter, FALSE, FALSE);

		gtk_tree_view_column_cell_get_position (column,
							sidebar->eject_icon_cell_renderer,
							&x_offset, &width);
989

990
		eject_button_size = nautilus_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU);
991

992 993 994 995 996 997 998
		/* This is kinda weird, but we have to do it to workaround gtk+ expanding
		 * the eject cell renderer (even thought we told it not to) and we then
		 * had to set it right-aligned */
		x_offset += width - hseparator - EJECT_BUTTON_XPAD - eject_button_size;

		if (x - x_offset >= 0 &&
		    x - x_offset <= eject_button_size) {
999 1000 1001 1002
			return TRUE;
		}
	}

1003
 out:
1004 1005 1006 1007 1008
	if (*path != NULL) {
		gtk_tree_path_free (*path);
		*path = NULL;
	}

1009 1010
	return FALSE;
}
1011

1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022
static gboolean
clicked_eject_button (NautilusPlacesSidebar *sidebar,
		      GtkTreePath **path)
{
	GdkEvent *event = gtk_get_current_event ();
	GdkEventButton *button_event = (GdkEventButton *) event;

	if ((event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) &&
	     over_eject_button (sidebar, button_event->x, button_event->y, path)) {
		return TRUE;
	}
1023

1024 1025 1026
	return FALSE;
}

1027
static void
1028
desktop_setting_changed_callback (gpointer user_data)
1029 1030
{
	NautilusPlacesSidebar *sidebar;
1031

1032 1033 1034 1035 1036 1037
	sidebar = NAUTILUS_PLACES_SIDEBAR (user_data);

	update_places (sidebar);
}

static void
1038
loading_uri_callback (NautilusWindow *window,
1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053
		      char *location,
		      NautilusPlacesSidebar *sidebar)
{
	GtkTreeSelection *selection;
	GtkTreeIter 	 iter;
	gboolean 	 valid;
	char  		 *uri;

        if (strcmp (sidebar->uri, location) != 0) {
		g_free (sidebar->uri);
                sidebar->uri = g_strdup (location);
  
		/* set selection if any place matches location */
		selection = gtk_tree_view_get_selection (sidebar->tree_view);
		gtk_tree_selection_unselect_all (selection);
1054 1055
  		valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (sidebar->store),
						       &iter);
1056 1057

		while (valid) {
1058
			gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter, 
1059 1060 1061 1062 1063 1064 1065 1066 1067 1068
		 		       	    PLACES_SIDEBAR_COLUMN_URI, &uri,
					    -1);
			if (uri != NULL) {
				if (strcmp (uri, location) == 0) {
					g_free (uri);
					gtk_tree_selection_select_iter (selection, &iter);
					break;
				}
				g_free (uri);
			}
1069 1070
        	 	valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (sidebar->store),
							  &iter);
1071 1072 1073 1074
		}
    	}
}

1075 1076 1077 1078 1079 1080 1081 1082
/* Computes the appropriate row and position for dropping */
static gboolean
compute_drop_position (GtkTreeView *tree_view,
		       int                      x,
		       int                      y,
		       GtkTreePath            **path,
		       GtkTreeViewDropPosition *pos,
		       NautilusPlacesSidebar *sidebar)
1083 1084 1085 1086
{
	GtkTreeModel *model;
	GtkTreeIter iter;
	PlaceType place_type;
1087
	SectionType section_type;
1088

1089
	if (!gtk_tree_view_get_dest_row_at_pos (tree_view,
1090 1091
						x, y,
						path, pos)) {
1092 1093
		return FALSE;
	}
1094

1095
	model = gtk_tree_view_get_model (tree_view);
1096

1097 1098 1099 1100 1101
	gtk_tree_model_get_iter (model, &iter, *path);
	gtk_tree_model_get (model, &iter,
			    PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type,
			    PLACES_SIDEBAR_COLUMN_SECTION_TYPE, &section_type,
			    -1);
1102

1103 1104 1105 1106
	if (place_type == PLACES_HEADING && section_type != SECTION_BOOKMARKS) {
		/* never drop on headings, but special case the bookmarks heading,
		 * so we can drop bookmarks in between it and the first item.
		 */
1107

1108 1109 1110 1111
		gtk_tree_path_free (*path);
		*path = NULL;
		
		return FALSE;
1112 1113
	}

1114 1115 1116 1117
	if (section_type != SECTION_BOOKMARKS &&
	    sidebar->drag_data_received &&
	    sidebar->drag_data_info == GTK_TREE_MODEL_ROW) {
		/* don't allow dropping bookmarks into non-bookmark areas */
1118

1119 1120
		gtk_tree_path_free (*path);
		*path = NULL;
1121

1122
		return FALSE;
1123
	}
1124 1125

	if (section_type == SECTION_BOOKMARKS) {
1126
		*pos = GTK_TREE_VIEW_DROP_AFTER;
1127 1128
	} else {
		/* non-bookmark shortcuts can only be dragged into */
1129
		*pos = GTK_TREE_VIEW_DROP_INTO_OR_BEFORE;
1130 1131 1132 1133 1134
	}

	if (*pos != GTK_TREE_VIEW_DROP_BEFORE &&
	    sidebar->drag_data_received &&
	    sidebar->drag_data_info == GTK_TREE_MODEL_ROW) {
1135
		/* bookmark rows are never dragged into other bookmark rows */
1136 1137
		*pos = GTK_TREE_VIEW_DROP_AFTER;
	}
1138

1139
	return TRUE;
1140 1141
}

1142
static gboolean
1143 1144 1145 1146 1147
get_drag_data (GtkTreeView *tree_view,
	       GdkDragContext *context, 
	       unsigned int time)
{
	GdkAtom target;
Cosimo Cecchi's avatar
Cosimo Cecchi committed
1148

1149 1150 1151 1152
	target = gtk_drag_dest_find_target (GTK_WIDGET (tree_view), 
					    context, 
					    NULL);

1153 1154 1155 1156
	if (target == GDK_NONE) {
		return FALSE;
	}

1157 1158
	gtk_drag_get_data (GTK_WIDGET (tree_view),
			   context, target, time);
1159 1160

	return TRUE;
1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173
}

static void
free_drag_data (NautilusPlacesSidebar *sidebar)
{
	sidebar->drag_data_received = FALSE;

	if (sidebar->drag_list) {
		nautilus_drag_destroy_selection_list (sidebar->drag_list);
		sidebar->drag_list = NULL;
	}
}

1174 1175 1176
static gboolean
can_accept_file_as_bookmark (NautilusFile *file)
{
1177 1178
	return (nautilus_file_is_directory (file) &&
		!is_built_in_bookmark (file));
1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192
}

static gboolean
can_accept_items_as_bookmarks (const GList *items)
{
	int max;
	char *uri;
	NautilusFile *file;

	/* Iterate through selection checking if item will get accepted as a bookmark.
	 * If more than 100 items selected, return an over-optimistic result.
	 */
	for (max = 100; items != NULL && max >= 0; items = items->next, max--) {