hythmbox-2.98/shell/rb-shell-clipboard.c

No issues found

   1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
   2  *
   3  *  Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
   4  *  Copyright (C) 2003,2004 Colin Walters <walters@verbum.org>
   5  *
   6  *  This program is free software; you can redistribute it and/or modify
   7  *  it under the terms of the GNU General Public License as published by
   8  *  the Free Software Foundation; either version 2 of the License, or
   9  *  (at your option) any later version.
  10  *
  11  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
  12  *  GStreamer plugins to be used and distributed together with GStreamer
  13  *  and Rhythmbox. This permission is above and beyond the permissions granted
  14  *  by the GPL license by which Rhythmbox is covered. If you modify this code
  15  *  you may extend this exception to your version of the code, but you are not
  16  *  obligated to do so. If you do not wish to do so, delete this exception
  17  *  statement from your version.
  18  *
  19  *  This program is distributed in the hope that it will be useful,
  20  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  21  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  22  *  GNU General Public License for more details.
  23  *
  24  *  You should have received a copy of the GNU General Public License
  25  *  along with this program; if not, write to the Free Software
  26  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
  27  *
  28  */
  29 
  30 /**
  31  * SECTION:rb-shell-clipboard
  32  * @short_description: cut and paste handling
  33  *
  34  * The clipboard is primarily responsible for handling cut and paste actions,
  35  * as well as various other actions that relate to a selected set of entries
  36  * from a source, such as move to trash, delete, and add to playlist.
  37  *
  38  * It updates the sensitivity of the various actions it handles when the selected
  39  * source changes, and when the track list selection changes.  The actual action
  40  * handlers are fairly simple, mostly calling #RBSource methods.
  41  *
  42  * For the 'add to playlist' action, the clipboard builds a menu containing
  43  * the available playlists that entries can be added to from the current selected
  44  * source.
  45  */
  46 
  47 #include <config.h>
  48 
  49 #include <glib/gi18n.h>
  50 #include <gtk/gtk.h>
  51 
  52 #include "rb-shell-clipboard.h"
  53 #include "rb-playlist-manager.h"
  54 #include "rb-play-queue-source.h"
  55 #include "rb-display-page-model.h"
  56 #include "rhythmdb.h"
  57 #include "rb-debug.h"
  58 #include "rb-stock-icons.h"
  59 
  60 static void rb_shell_clipboard_class_init (RBShellClipboardClass *klass);
  61 static void rb_shell_clipboard_init (RBShellClipboard *shell_clipboard);
  62 static void rb_shell_clipboard_dispose (GObject *object);
  63 static void rb_shell_clipboard_finalize (GObject *object);
  64 static void rb_shell_clipboard_set_property (GObject *object,
  65 					     guint prop_id,
  66 					     const GValue *value,
  67 					     GParamSpec *pspec);
  68 static void rb_shell_clipboard_get_property (GObject *object,
  69 				   	     guint prop_id,
  70 					     GValue *value,
  71 					     GParamSpec *pspec);
  72 static void rb_shell_clipboard_sync (RBShellClipboard *clipboard);
  73 static void rb_shell_clipboard_cmd_select_all (GtkAction *action,
  74 					       RBShellClipboard *clipboard);
  75 static void rb_shell_clipboard_cmd_select_none (GtkAction *action,
  76 						RBShellClipboard *clipboard);
  77 static void rb_shell_clipboard_cmd_cut (GtkAction *action,
  78 			                RBShellClipboard *clipboard);
  79 static void rb_shell_clipboard_cmd_copy (GtkAction *action,
  80 			                 RBShellClipboard *clipboard);
  81 static void rb_shell_clipboard_cmd_paste (GtkAction *action,
  82 			                  RBShellClipboard *clipboard);
  83 static void rb_shell_clipboard_cmd_delete (GtkAction *action,
  84 					   RBShellClipboard *clipboard);
  85 static void rb_shell_clipboard_cmd_queue_delete (GtkAction *action,
  86 						 RBShellClipboard *clipboard);
  87 static void rb_shell_clipboard_cmd_move_to_trash (GtkAction *action,
  88 						  RBShellClipboard *clipboard);
  89 static void rb_shell_clipboard_set (RBShellClipboard *clipboard,
  90 			            GList *nodes);
  91 static void rb_shell_clipboard_playlist_added_cb (RBPlaylistManager *mgr,
  92 						  RBPlaylistSource *source,
  93 						  RBShellClipboard *clipboard);
  94 static void rb_shell_clipboard_entry_deleted_cb (RhythmDB *db,
  95 						 RhythmDBEntry *entry,
  96 						 RBShellClipboard *clipboard);
  97 static void rb_shell_clipboard_entryview_changed_cb (RBEntryView *view,
  98 						     RBShellClipboard *clipboard);
  99 static void rb_shell_clipboard_entries_changed_cb (RBEntryView *view,
 100 						   gpointer stuff,
 101 						   RBShellClipboard *clipboard);
 102 static void rb_shell_clipboard_cmd_add_to_playlist_new (GtkAction *action,
 103 							RBShellClipboard *clipboard);
 104 static void rb_shell_clipboard_cmd_add_song_to_queue (GtkAction *action,
 105 						      RBShellClipboard *clipboard);
 106 static void rb_shell_clipboard_cmd_song_info (GtkAction *action,
 107 					      RBShellClipboard *clipboard);
 108 static void rb_shell_clipboard_cmd_queue_song_info (GtkAction *action,
 109 						    RBShellClipboard *clipboard);
 110 static void rebuild_playlist_menu (RBShellClipboard *clipboard);
 111 static gboolean rebuild_playlist_menu_idle (RBShellClipboard *clipboard);
 112 
 113 struct RBShellClipboardPrivate
 114 {
 115 	RhythmDB *db;
 116 	RBSource *source;
 117 	RBStaticPlaylistSource *queue_source;
 118 	RBPlaylistManager *playlist_manager;
 119 
 120 	GtkUIManager *ui_mgr;
 121 	GtkActionGroup *actiongroup;
 122 	guint playlist_menu_ui_id;
 123 
 124 	guint delete_action_ui_id;
 125 	GtkAction *delete_action;
 126 
 127 	GHashTable *signal_hash;
 128 
 129 	GAsyncQueue *deleted_queue;
 130 
 131 	guint idle_sync_id, idle_playlist_id;
 132 
 133 	GList *entries;
 134 };
 135 
 136 #define RB_SHELL_CLIPBOARD_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_SHELL_CLIPBOARD, RBShellClipboardPrivate))
 137 
 138 enum
 139 {
 140 	PROP_0,
 141 	PROP_SOURCE,
 142 	PROP_ACTION_GROUP,
 143 	PROP_DB,
 144 	PROP_QUEUE_SOURCE,
 145 	PROP_PLAYLIST_MANAGER,
 146 	PROP_UI_MANAGER,
 147 };
 148 
 149 static GtkActionEntry rb_shell_clipboard_actions [] =
 150 {
 151 	{ "EditSelectAll", NULL, N_("Select _All"), "<control>A",
 152 	  N_("Select all songs"),
 153 	  G_CALLBACK (rb_shell_clipboard_cmd_select_all) },
 154 	{ "EditSelectNone", NULL, N_("D_eselect All"), "<shift><control>A",
 155 	  N_("Deselect all songs"),
 156 	  G_CALLBACK (rb_shell_clipboard_cmd_select_none) },
 157 	{ "EditCut", GTK_STOCK_CUT, N_("Cu_t"), "<control>X",
 158 	  N_("Cut selection"),
 159 	  G_CALLBACK (rb_shell_clipboard_cmd_cut) },
 160 	{ "EditCopy", GTK_STOCK_COPY, N_("_Copy"), "<control>C",
 161 	  N_("Copy selection"),
 162 	  G_CALLBACK (rb_shell_clipboard_cmd_copy) },
 163 	{ "EditPaste", GTK_STOCK_PASTE, N_("_Paste"), "<control>V",
 164 	  N_("Paste selection"),
 165 	  G_CALLBACK (rb_shell_clipboard_cmd_paste) },
 166 	{ "EditDelete", GTK_STOCK_DELETE, N_("_Delete"), NULL,
 167 	  N_("Delete each selected item"),
 168 	  G_CALLBACK (rb_shell_clipboard_cmd_delete) },
 169 	{ "EditRemove", GTK_STOCK_REMOVE, N_("_Remove"), NULL,
 170 	  N_("Remove each selected item from the library"),
 171 	  G_CALLBACK (rb_shell_clipboard_cmd_delete) },
 172 	{ "EditMovetoTrash", "user-trash", N_("_Move to Trash"), NULL,
 173 	  N_("Move each selected item to the trash"),
 174 	  G_CALLBACK (rb_shell_clipboard_cmd_move_to_trash) },
 175 
 176 	{ "EditPlaylistAdd", NULL, N_("Add to P_laylist") },
 177 	{ "EditPlaylistAddNew", RB_STOCK_PLAYLIST_NEW, N_("_New Playlist"), NULL,
 178 	  N_("Add each selected song to a new playlist"),
 179 	  G_CALLBACK (rb_shell_clipboard_cmd_add_to_playlist_new) },
 180 	{ "AddToQueue", GTK_STOCK_ADD, N_("Add _to Play Queue"), NULL,
 181 	  N_("Add each selected song to the play queue"),
 182 	  G_CALLBACK (rb_shell_clipboard_cmd_add_song_to_queue) },
 183 	{ "QueueDelete", GTK_STOCK_REMOVE, N_("Remove"), NULL,
 184 	  N_("Remove each selected item from the play queue"),
 185 	  G_CALLBACK (rb_shell_clipboard_cmd_queue_delete) },
 186 
 187 	{ "MusicProperties", GTK_STOCK_PROPERTIES, N_("Pr_operties"), "<Alt>Return",
 188 	  N_("Show information on each selected song"),
 189 	  G_CALLBACK (rb_shell_clipboard_cmd_song_info) },
 190 	{ "QueueMusicProperties", GTK_STOCK_PROPERTIES, N_("_Properties"), NULL,
 191 	  N_("Show information on each selected song"),
 192 	  G_CALLBACK (rb_shell_clipboard_cmd_queue_song_info) },
 193 };
 194 static guint rb_shell_clipboard_n_actions = G_N_ELEMENTS (rb_shell_clipboard_actions);
 195 
 196 static const char *delete_action_paths[] = {
 197 	"/MenuBar/EditMenu/DeleteActionPlaceholder",
 198 	"/BrowserSourceViewPopup/DeleteActionPlaceholder",
 199 };
 200 
 201 static const char *playlist_menu_paths[] = {
 202 	"/MenuBar/EditMenu/EditPlaylistAddMenu/EditPlaylistAddPlaceholder",
 203 	"/BrowserSourceViewPopup/BrowserSourcePopupPlaylistAdd/BrowserSourcePopupPlaylistAddPlaceholder",
 204 	"/PlaylistViewPopup/PlaylistPopupPlaylistAdd/PlaylistPopupPlaylistAddPlaceholder",
 205 };
 206 static guint num_playlist_menu_paths = G_N_ELEMENTS (playlist_menu_paths);
 207 
 208 G_DEFINE_TYPE (RBShellClipboard, rb_shell_clipboard, G_TYPE_OBJECT)
 209 
 210 static void
 211 rb_shell_clipboard_class_init (RBShellClipboardClass *klass)
 212 {
 213 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 214 
 215 	object_class->dispose = rb_shell_clipboard_dispose;
 216 	object_class->finalize = rb_shell_clipboard_finalize;
 217 
 218 	object_class->set_property = rb_shell_clipboard_set_property;
 219 	object_class->get_property = rb_shell_clipboard_get_property;
 220 
 221 	g_object_class_install_property (object_class,
 222 					 PROP_SOURCE,
 223 					 g_param_spec_object ("source",
 224 							      "RBSource",
 225 							      "RBSource object",
 226 							      RB_TYPE_SOURCE,
 227 							      G_PARAM_READWRITE));
 228 	g_object_class_install_property (object_class,
 229 					 PROP_ACTION_GROUP,
 230 					 g_param_spec_object ("action-group",
 231 							      "GtkActionGroup",
 232 							      "GtkActionGroup object",
 233 							      GTK_TYPE_ACTION_GROUP,
 234 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 235 	g_object_class_install_property (object_class,
 236 					 PROP_DB,
 237 					 g_param_spec_object ("db",
 238 							      "RhythmDB",
 239 							      "RhythmDB database",
 240 							      RHYTHMDB_TYPE,
 241 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 242 	g_object_class_install_property (object_class,
 243 					 PROP_QUEUE_SOURCE,
 244 					 g_param_spec_object ("queue-source",
 245 						 	      "RBPlaylistSource",
 246 							      "RBPlaylistSource object",
 247 							      RB_TYPE_PLAYLIST_SOURCE,
 248 							      G_PARAM_READWRITE));
 249 	g_object_class_install_property (object_class,
 250 					 PROP_PLAYLIST_MANAGER,
 251 					 g_param_spec_object ("playlist-manager",
 252 						 	      "RBPlaylistManager",
 253 							      "RBPlaylistManager object",
 254 							      RB_TYPE_PLAYLIST_MANAGER,
 255 							      G_PARAM_READWRITE));
 256 	g_object_class_install_property (object_class,
 257 					 PROP_UI_MANAGER,
 258 					 g_param_spec_object ("ui-manager",
 259 						 	      "GtkUIManager",
 260 							      "GtkUIManager object",
 261 							      GTK_TYPE_UI_MANAGER,
 262 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 263 
 264 	g_type_class_add_private (klass, sizeof (RBShellClipboardPrivate));
 265 }
 266 
 267 static void
 268 rb_shell_clipboard_init (RBShellClipboard *shell_clipboard)
 269 {
 270 	shell_clipboard->priv = RB_SHELL_CLIPBOARD_GET_PRIVATE (shell_clipboard);
 271 
 272 	shell_clipboard->priv->signal_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal,
 273 								    NULL, g_free);
 274 
 275 	shell_clipboard->priv->deleted_queue = g_async_queue_new ();
 276 }
 277 
 278 static void
 279 unset_source_internal (RBShellClipboard *clipboard)
 280 {
 281 	if (clipboard->priv->source != NULL) {
 282 		RBEntryView *songs = rb_source_get_entry_view (clipboard->priv->source);
 283 
 284 		if (songs) {
 285 			g_signal_handlers_disconnect_by_func (G_OBJECT (songs),
 286 							      G_CALLBACK (rb_shell_clipboard_entryview_changed_cb),
 287 							      clipboard);
 288 			g_signal_handlers_disconnect_by_func (G_OBJECT (songs),
 289 							      G_CALLBACK (rb_shell_clipboard_entries_changed_cb),
 290 							      clipboard);
 291 		}
 292 
 293 		gtk_ui_manager_remove_ui (clipboard->priv->ui_mgr,
 294 					  clipboard->priv->delete_action_ui_id);
 295 	}
 296 	clipboard->priv->source = NULL;
 297 }
 298 
 299 static void
 300 rb_shell_clipboard_dispose (GObject *object)
 301 {
 302 	RBShellClipboard *shell_clipboard;
 303 
 304 	g_return_if_fail (object != NULL);
 305 	g_return_if_fail (RB_IS_SHELL_CLIPBOARD (object));
 306 
 307 	shell_clipboard = RB_SHELL_CLIPBOARD (object);
 308 
 309 	g_return_if_fail (shell_clipboard->priv != NULL);
 310 
 311 	unset_source_internal (shell_clipboard);
 312 
 313 	if (shell_clipboard->priv->idle_sync_id != 0) {
 314 		g_source_remove (shell_clipboard->priv->idle_sync_id);
 315 		shell_clipboard->priv->idle_sync_id = 0;
 316 	}
 317 	if (shell_clipboard->priv->idle_playlist_id != 0) {
 318 		g_source_remove (shell_clipboard->priv->idle_playlist_id);
 319 		shell_clipboard->priv->idle_playlist_id = 0;
 320 	}
 321 
 322 	G_OBJECT_CLASS (rb_shell_clipboard_parent_class)->dispose (object);
 323 }
 324 
 325 static void
 326 rb_shell_clipboard_finalize (GObject *object)
 327 {
 328 	RBShellClipboard *shell_clipboard;
 329 
 330 	g_return_if_fail (object != NULL);
 331 	g_return_if_fail (RB_IS_SHELL_CLIPBOARD (object));
 332 
 333 	shell_clipboard = RB_SHELL_CLIPBOARD (object);
 334 
 335 	g_return_if_fail (shell_clipboard->priv != NULL);
 336 
 337 	g_hash_table_destroy (shell_clipboard->priv->signal_hash);
 338 
 339 	g_list_foreach (shell_clipboard->priv->entries, (GFunc)rhythmdb_entry_unref, NULL);
 340 	g_list_free (shell_clipboard->priv->entries);
 341 
 342 	g_async_queue_unref (shell_clipboard->priv->deleted_queue);
 343 
 344 	G_OBJECT_CLASS (rb_shell_clipboard_parent_class)->finalize (object);
 345 }
 346 
 347 static void
 348 rb_shell_clipboard_set_source_internal (RBShellClipboard *clipboard,
 349 					RBSource *source)
 350 {
 351 	unset_source_internal (clipboard);
 352 
 353 	clipboard->priv->source = source;
 354 	rb_debug ("selected source %p", source);
 355 
 356 	rb_shell_clipboard_sync (clipboard);
 357 
 358 	if (clipboard->priv->source != NULL) {
 359 		RBEntryView *songs = rb_source_get_entry_view (clipboard->priv->source);
 360 		char *delete_action;
 361 
 362 		if (songs) {
 363 			g_signal_connect_object (G_OBJECT (songs),
 364 						 "selection-changed",
 365 						 G_CALLBACK (rb_shell_clipboard_entryview_changed_cb),
 366 						 clipboard, 0);
 367 			g_signal_connect_object (G_OBJECT (songs),
 368 						 "entry-added",
 369 						 G_CALLBACK (rb_shell_clipboard_entries_changed_cb),
 370 						 clipboard, 0);
 371 			g_signal_connect_object (G_OBJECT (songs),
 372 						 "entry-deleted",
 373 						 G_CALLBACK (rb_shell_clipboard_entries_changed_cb),
 374 						 clipboard, 0);
 375 			g_signal_connect_object (G_OBJECT (songs),
 376 						 "entries-replaced",
 377 						 G_CALLBACK (rb_shell_clipboard_entryview_changed_cb),
 378 						 clipboard, 0);
 379 		}
 380 
 381 		delete_action = rb_source_get_delete_action (source);
 382 		if (delete_action != NULL) {
 383 			char *path;
 384 			int i;
 385 			for (i = 0; i < G_N_ELEMENTS (delete_action_paths); i++) {
 386 				gtk_ui_manager_add_ui (clipboard->priv->ui_mgr,
 387 						       clipboard->priv->delete_action_ui_id,
 388 						       delete_action_paths[i],
 389 						       delete_action,
 390 						       delete_action,
 391 						       GTK_UI_MANAGER_AUTO,
 392 						       FALSE);
 393 			}
 394 			gtk_ui_manager_ensure_update (clipboard->priv->ui_mgr);
 395 
 396 			/* locate action too */
 397 			path = g_strdup_printf ("%s/%s", delete_action_paths[0], delete_action);
 398 			clipboard->priv->delete_action = gtk_ui_manager_get_action (clipboard->priv->ui_mgr, path);
 399 			g_free (path);
 400 		}
 401 		g_free (delete_action);
 402 	}
 403 
 404 	rebuild_playlist_menu (clipboard);
 405 }
 406 
 407 static void
 408 rb_shell_clipboard_set_property (GObject *object,
 409 			         guint prop_id,
 410 			         const GValue *value,
 411 			         GParamSpec *pspec)
 412 {
 413 	RBShellClipboard *clipboard = RB_SHELL_CLIPBOARD (object);
 414 
 415 	switch (prop_id)
 416 	{
 417 	case PROP_SOURCE:
 418 		rb_shell_clipboard_set_source_internal (clipboard, g_value_get_object (value));
 419 		break;
 420 	case PROP_ACTION_GROUP:
 421 		clipboard->priv->actiongroup = g_value_get_object (value);
 422 		gtk_action_group_add_actions (clipboard->priv->actiongroup,
 423 					      rb_shell_clipboard_actions,
 424 					      rb_shell_clipboard_n_actions,
 425 					      clipboard);
 426 		break;
 427 	case PROP_DB:
 428 		clipboard->priv->db = g_value_get_object (value);
 429 		g_signal_connect_object (clipboard->priv->db,
 430 					 "entry_deleted",
 431 					 G_CALLBACK (rb_shell_clipboard_entry_deleted_cb),
 432 					 clipboard, 0);
 433 		break;
 434 	case PROP_UI_MANAGER:
 435 		clipboard->priv->ui_mgr = g_value_get_object (value);
 436 		clipboard->priv->delete_action_ui_id =
 437 			gtk_ui_manager_new_merge_id (clipboard->priv->ui_mgr);
 438 
 439 		break;
 440 	case PROP_PLAYLIST_MANAGER:
 441 		if (clipboard->priv->playlist_manager != NULL) {
 442 			g_signal_handlers_disconnect_by_func (clipboard->priv->playlist_manager,
 443 							      G_CALLBACK (rb_shell_clipboard_playlist_added_cb),
 444 							      clipboard);
 445 		}
 446 
 447 		clipboard->priv->playlist_manager = g_value_get_object (value);
 448 		if (clipboard->priv->playlist_manager != NULL) {
 449 			g_signal_connect_object (G_OBJECT (clipboard->priv->playlist_manager),
 450 						 "playlist-added", G_CALLBACK (rb_shell_clipboard_playlist_added_cb),
 451 						 clipboard, 0);
 452 
 453 			rebuild_playlist_menu (clipboard);
 454 		}
 455 
 456 		break;
 457 	case PROP_QUEUE_SOURCE:
 458 		if (clipboard->priv->queue_source != NULL) {
 459 			RBEntryView *sidebar;
 460 			g_object_get (clipboard->priv->queue_source, "sidebar", &sidebar, NULL);
 461 			g_signal_handlers_disconnect_by_func (sidebar,
 462 							      G_CALLBACK (rb_shell_clipboard_entryview_changed_cb),
 463 							      clipboard);
 464 			g_object_unref (sidebar);
 465 		}
 466 
 467 		clipboard->priv->queue_source = g_value_get_object (value);
 468 		if (clipboard->priv->queue_source != NULL) {
 469 			RBEntryView *sidebar;
 470 			g_object_get (clipboard->priv->queue_source, "sidebar", &sidebar, NULL);
 471 			g_signal_connect_object (G_OBJECT (sidebar), "selection-changed",
 472 						 G_CALLBACK (rb_shell_clipboard_entryview_changed_cb),
 473 						 clipboard, 0);
 474 			g_object_unref (sidebar);
 475 		}
 476 		break;
 477 	default:
 478 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 479 		break;
 480 	}
 481 }
 482 
 483 static void
 484 rb_shell_clipboard_get_property (GObject *object,
 485 			         guint prop_id,
 486 			         GValue *value,
 487 			         GParamSpec *pspec)
 488 {
 489 	RBShellClipboard *clipboard = RB_SHELL_CLIPBOARD (object);
 490 
 491 	switch (prop_id)
 492 	{
 493 	case PROP_SOURCE:
 494 		g_value_set_object (value, clipboard->priv->source);
 495 		break;
 496 	case PROP_ACTION_GROUP:
 497 		g_value_set_object (value, clipboard->priv->actiongroup);
 498 		break;
 499 	case PROP_DB:
 500 		g_value_set_object (value, clipboard->priv->db);
 501 		break;
 502 	case PROP_UI_MANAGER:
 503 		g_value_set_object (value, clipboard->priv->ui_mgr);
 504 		break;
 505 	case PROP_PLAYLIST_MANAGER:
 506 		g_value_set_object (value, clipboard->priv->playlist_manager);
 507 		break;
 508 	case PROP_QUEUE_SOURCE:
 509 		g_value_set_object (value, clipboard->priv->queue_source);
 510 		break;
 511 	default:
 512 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 513 		break;
 514 	}
 515 }
 516 
 517 /**
 518  * rb_shell_clipboard_set_source:
 519  * @clipboard: the #RBShellClipboard
 520  * @source: the new selected #RBSource
 521  *
 522  * Updates the clipboard to reflect a newly selected source.
 523  */
 524 void
 525 rb_shell_clipboard_set_source (RBShellClipboard *clipboard,
 526 			       RBSource *source)
 527 {
 528 	g_return_if_fail (RB_IS_SHELL_CLIPBOARD (clipboard));
 529 	if (source != NULL)
 530 		g_return_if_fail (RB_IS_SOURCE (source));
 531 
 532 	g_object_set (G_OBJECT (clipboard), "source", source, NULL);
 533 }
 534 
 535 /**
 536  * rb_shell_clipboard_new:
 537  * @actiongroup: the #GtkActionGroup to use
 538  * @ui_mgr: the #GtkUIManager instance
 539  * @db: the #RhythmDB instance
 540  *
 541  * Creates the #RBShellClipboard instance
 542  *
 543  * Return value: the #RBShellClipboard
 544  */
 545 RBShellClipboard *
 546 rb_shell_clipboard_new (GtkActionGroup *actiongroup,
 547 			GtkUIManager *ui_mgr,
 548 			RhythmDB *db)
 549 {
 550 	return g_object_new (RB_TYPE_SHELL_CLIPBOARD,
 551 			     "action-group", actiongroup,
 552 			     "ui-manager", ui_mgr,
 553 			     "db", db,
 554 			     NULL);
 555 }
 556 
 557 static gboolean
 558 rb_shell_clipboard_sync_idle (RBShellClipboard *clipboard)
 559 {
 560 	GDK_THREADS_ENTER ();
 561 	rb_shell_clipboard_sync (clipboard);
 562 	clipboard->priv->idle_sync_id = 0;
 563 	GDK_THREADS_LEAVE ();
 564 
 565 	return FALSE;
 566 }
 567 
 568 static void
 569 rb_shell_clipboard_sync (RBShellClipboard *clipboard)
 570 {
 571 	RBEntryView *view;
 572 	gboolean have_selection = FALSE;
 573 	gboolean have_sidebar_selection = FALSE;
 574 	gboolean can_cut = FALSE;
 575 	gboolean can_paste = FALSE;
 576 	gboolean can_delete = FALSE;
 577 	gboolean can_copy = FALSE;
 578 	gboolean can_add_to_queue = FALSE;
 579 	gboolean can_move_to_trash = FALSE;
 580 	gboolean can_select_all = FALSE;
 581 	gboolean can_show_properties = FALSE;
 582 	GtkAction *action;
 583 	RhythmDBEntryType *entry_type;
 584 
 585 	if (clipboard->priv->source) {
 586 		view = rb_source_get_entry_view (clipboard->priv->source);
 587 		if (view) {
 588 			have_selection = rb_entry_view_have_selection (view);
 589 			can_select_all = !rb_entry_view_have_complete_selection (view);
 590 		}
 591 	}
 592 
 593 	if (clipboard->priv->queue_source) {
 594 		RBEntryView *sidebar;
 595 		g_object_get (clipboard->priv->queue_source, "sidebar", &sidebar, NULL);
 596 		have_sidebar_selection = rb_entry_view_have_selection (sidebar);
 597 		g_object_unref (sidebar);
 598 	}
 599 
 600 	rb_debug ("syncing clipboard");
 601 
 602 	if (clipboard->priv->source != NULL && g_list_length (clipboard->priv->entries) > 0)
 603 		can_paste = rb_source_can_paste (clipboard->priv->source);
 604 
 605 	if (have_selection) {
 606 		can_cut = rb_source_can_cut (clipboard->priv->source);
 607 		can_delete = rb_source_can_delete (clipboard->priv->source);
 608 		can_copy = rb_source_can_copy (clipboard->priv->source);
 609 		can_move_to_trash = rb_source_can_move_to_trash (clipboard->priv->source);
 610 		can_show_properties = rb_source_can_show_properties (clipboard->priv->source);
 611 
 612 		if (clipboard->priv->queue_source)
 613 			can_add_to_queue = rb_source_can_add_to_queue (clipboard->priv->source);
 614 	}
 615 
 616 	action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditCut");
 617 	g_object_set (action, "sensitive", can_cut, NULL);
 618 
 619 	if (clipboard->priv->delete_action != NULL) {
 620 		g_object_set (clipboard->priv->delete_action, "sensitive", can_delete, NULL);
 621 	}
 622 
 623 	action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditMovetoTrash");
 624 	g_object_set (action, "sensitive", can_move_to_trash, NULL);
 625 
 626 	action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditCopy");
 627 	g_object_set (action, "sensitive", can_copy, NULL);
 628 
 629 	action = gtk_action_group_get_action (clipboard->priv->actiongroup,"EditPaste");
 630 	g_object_set (action, "sensitive", can_paste, NULL);
 631 
 632 	action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditPlaylistAdd");
 633 	g_object_set (action, "sensitive", can_copy, NULL);
 634 
 635 	action = gtk_action_group_get_action (clipboard->priv->actiongroup, "AddToQueue");
 636 	g_object_set (action, "sensitive", can_add_to_queue, NULL);
 637 
 638 	action = gtk_action_group_get_action (clipboard->priv->actiongroup, "MusicProperties");
 639 	g_object_set (action, "sensitive", can_show_properties, NULL);
 640 
 641 	action = gtk_action_group_get_action (clipboard->priv->actiongroup, "QueueMusicProperties");
 642 	g_object_set (action, "sensitive", have_sidebar_selection, NULL);
 643 
 644 	action = gtk_action_group_get_action (clipboard->priv->actiongroup, "QueueDelete");
 645 	g_object_set (action, "sensitive", have_sidebar_selection, NULL);
 646 
 647 	action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditSelectAll");
 648 	g_object_set (action, "sensitive", can_select_all, NULL);
 649 
 650 	action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditSelectNone");
 651 	g_object_set (action, "sensitive", have_selection, NULL);
 652 
 653 	/* disable the whole add-to-playlist menu if the source's entry type doesn't have playlists */
 654 	action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditPlaylistAdd");
 655 	if (clipboard->priv->source != NULL) {
 656 		g_object_get (clipboard->priv->source, "entry-type", &entry_type, NULL);
 657 		if (entry_type != NULL) {
 658 			gboolean has_playlists;
 659 			g_object_get (entry_type, "has-playlists", &has_playlists, NULL);
 660 			gtk_action_set_sensitive (action, has_playlists);
 661 			g_object_unref (entry_type);
 662 		} else {
 663 			gtk_action_set_sensitive (action, FALSE);
 664 		}
 665 	} else {
 666 		gtk_action_set_sensitive (action, FALSE);
 667 	}
 668 }
 669 
 670 static GtkWidget*
 671 get_focussed_widget (RBShellClipboard *clipboard)
 672 {
 673 	GtkWidget *window;
 674 	GtkWidget *widget;
 675 
 676 	/* FIXME: this should be better */
 677 	window = gtk_widget_get_toplevel (GTK_WIDGET (clipboard->priv->source));
 678 	widget = gtk_window_get_focus (GTK_WINDOW (window));
 679 
 680 	return widget;
 681 }
 682 
 683 static void
 684 rb_shell_clipboard_cmd_select_all (GtkAction *action,
 685 				   RBShellClipboard *clipboard)
 686 {
 687 	RBEntryView *entryview;
 688 	GtkWidget *widget;
 689 
 690 	rb_debug ("select all");
 691 	widget = get_focussed_widget (clipboard);
 692 	if (GTK_IS_EDITABLE (widget)) {
 693 		gtk_editable_select_region (GTK_EDITABLE (widget), 0, -1);
 694 	} else {
 695 		/* select all tracks in the entry view */
 696 		entryview = rb_source_get_entry_view (clipboard->priv->source);
 697 		if (entryview != NULL) {
 698 			rb_entry_view_select_all (entryview);
 699 		}
 700 	}
 701 }
 702 
 703 static void
 704 rb_shell_clipboard_cmd_select_none (GtkAction *action,
 705 				    RBShellClipboard *clipboard)
 706 {
 707 	RBEntryView *entryview;
 708 	GtkWidget *widget;
 709 
 710 	rb_debug ("select none");
 711 	widget = get_focussed_widget (clipboard);
 712 	if (GTK_IS_EDITABLE (widget)) {
 713 		gtk_editable_select_region (GTK_EDITABLE (widget), -1, -1);
 714 	} else {
 715 		entryview = rb_source_get_entry_view (clipboard->priv->source);
 716 		if (entryview != NULL) {
 717 			rb_entry_view_select_none (entryview);
 718 		}
 719 	}
 720 }
 721 
 722 static void
 723 rb_shell_clipboard_cmd_cut (GtkAction *action,
 724 			    RBShellClipboard *clipboard)
 725 {
 726 	rb_debug ("cut");
 727 	rb_shell_clipboard_set (clipboard,
 728 				rb_source_cut (clipboard->priv->source));
 729 }
 730 
 731 static void
 732 rb_shell_clipboard_cmd_copy (GtkAction *action,
 733 			     RBShellClipboard *clipboard)
 734 {
 735 	rb_debug ("copy");
 736 	rb_shell_clipboard_set (clipboard,
 737 				rb_source_copy (clipboard->priv->source));
 738 }
 739 
 740 static void
 741 rb_shell_clipboard_cmd_paste (GtkAction *action,
 742 			      RBShellClipboard *clipboard)
 743 {
 744 	rb_debug ("paste");
 745 	rb_source_paste (clipboard->priv->source, clipboard->priv->entries);
 746 }
 747 
 748 static void
 749 rb_shell_clipboard_cmd_delete (GtkAction *action,
 750 	                       RBShellClipboard *clipboard)
 751 {
 752 	rb_debug ("delete");
 753 	rb_source_delete (clipboard->priv->source);
 754 }
 755 
 756 static void
 757 rb_shell_clipboard_cmd_queue_delete (GtkAction *action,
 758 				     RBShellClipboard *clipboard)
 759 {
 760 	rb_debug ("delete");
 761 	rb_play_queue_source_sidebar_delete (RB_PLAY_QUEUE_SOURCE (clipboard->priv->queue_source));
 762 }
 763 
 764 static void
 765 rb_shell_clipboard_cmd_move_to_trash (GtkAction *action,
 766 				      RBShellClipboard *clipboard)
 767 {
 768 	rb_debug ("movetotrash");
 769 	rb_source_move_to_trash (clipboard->priv->source);
 770 }
 771 
 772 /* NOTE: takes ownership of the entries */
 773 static void
 774 rb_shell_clipboard_set (RBShellClipboard *clipboard,
 775 			GList *entries)
 776 {
 777 	if (clipboard->priv->entries != NULL) {
 778 		g_list_foreach (clipboard->priv->entries, (GFunc)rhythmdb_entry_unref, NULL);
 779 		g_list_free (clipboard->priv->entries);
 780 	}
 781 
 782 	clipboard->priv->entries = entries;
 783 }
 784 
 785 static void
 786 rb_shell_clipboard_entry_deleted_cb (RhythmDB *db,
 787 				     RhythmDBEntry *entry,
 788 				     RBShellClipboard *clipboard)
 789 {
 790 	GList *l;
 791 
 792 	GDK_THREADS_ENTER ();
 793 	l = g_list_find (clipboard->priv->entries, entry);
 794 	if (l != NULL) {
 795 		clipboard->priv->entries = g_list_delete_link (clipboard->priv->entries, l);
 796 		rhythmdb_entry_unref (entry);
 797 		rb_shell_clipboard_sync (clipboard);
 798 	}
 799 	GDK_THREADS_LEAVE ();
 800 }
 801 
 802 static void
 803 rb_shell_clipboard_entryview_changed_cb (RBEntryView *view,
 804 					 RBShellClipboard *clipboard)
 805 {
 806 	if (clipboard->priv->idle_sync_id == 0)
 807 		clipboard->priv->idle_sync_id = g_idle_add ((GSourceFunc) rb_shell_clipboard_sync_idle,
 808 							    clipboard);
 809 	rb_debug ("entryview changed");
 810 }
 811 
 812 static void
 813 rb_shell_clipboard_entries_changed_cb (RBEntryView *view,
 814 				       gpointer stuff,
 815 				       RBShellClipboard *clipboard)
 816 {
 817 	rb_debug ("entryview changed");
 818 	if (clipboard->priv->idle_sync_id == 0)
 819 		clipboard->priv->idle_sync_id = g_idle_add ((GSourceFunc) rb_shell_clipboard_sync_idle,
 820 							    clipboard);
 821 }
 822 
 823 static void
 824 rb_shell_clipboard_cmd_add_to_playlist_new (GtkAction *action,
 825 					    RBShellClipboard *clipboard)
 826 {
 827 	GList *entries;
 828 	RBSource *playlist_source;
 829 
 830 	rb_debug ("add to new playlist");
 831 
 832 	entries = rb_source_copy (clipboard->priv->source);
 833 	playlist_source = rb_playlist_manager_new_playlist (clipboard->priv->playlist_manager,
 834 							    NULL, FALSE);
 835 	rb_source_paste (playlist_source, entries);
 836 
 837 	g_list_foreach (entries, (GFunc)rhythmdb_entry_unref, NULL);
 838 	g_list_free (entries);
 839 }
 840 
 841 static void
 842 rb_shell_clipboard_cmd_add_song_to_queue (GtkAction *action,
 843 					  RBShellClipboard *clipboard)
 844 {
 845 	rb_debug ("add to queue");
 846 	rb_source_add_to_queue (clipboard->priv->source,
 847 				RB_SOURCE (clipboard->priv->queue_source));
 848 }
 849 
 850 static void
 851 rb_shell_clipboard_cmd_song_info (GtkAction *action,
 852 				  RBShellClipboard *clipboard)
 853 {
 854 	rb_debug ("song info");
 855 
 856 	rb_source_song_properties (clipboard->priv->source);
 857 }
 858 
 859 static void
 860 rb_shell_clipboard_cmd_queue_song_info (GtkAction *action,
 861 					RBShellClipboard *clipboard)
 862 {
 863 	rb_debug ("song info");
 864 	rb_play_queue_source_sidebar_song_info (RB_PLAY_QUEUE_SOURCE (clipboard->priv->queue_source));
 865 }
 866 
 867 static void
 868 rb_shell_clipboard_playlist_add_cb (GtkAction *action,
 869 				    RBShellClipboard *clipboard)
 870 {
 871 	RBSource *playlist_source;
 872 	GList *entries;
 873 
 874 	rb_debug ("add to exisintg playlist");
 875 	playlist_source = g_object_get_data (G_OBJECT (action), "playlist-source");
 876 
 877 	entries = rb_source_copy (clipboard->priv->source);
 878 	rb_source_paste (playlist_source, entries);
 879 
 880 	g_list_foreach (entries, (GFunc)rhythmdb_entry_unref, NULL);
 881 	g_list_free (entries);
 882 }
 883 
 884 static char *
 885 generate_action_name (RBStaticPlaylistSource *source,
 886 		      RBShellClipboard *clipboard)
 887 {
 888 	return g_strdup_printf ("AddToPlaylistClipboardAction%p", source);
 889 }
 890 
 891 static void
 892 rb_shell_clipboard_playlist_deleted_cb (RBStaticPlaylistSource *source,
 893 					RBShellClipboard *clipboard)
 894 {
 895 	char *action_name;
 896 	GtkAction *action;
 897 
 898 	/* first rebuild the menu */
 899 	rebuild_playlist_menu (clipboard);
 900 
 901 	/* then remove the 'add to playlist' action for the deleted playlist */
 902 	action_name = generate_action_name (source, clipboard);
 903 	action = gtk_action_group_get_action (clipboard->priv->actiongroup, action_name);
 904 	g_assert (action);
 905 	gtk_action_group_remove_action (clipboard->priv->actiongroup, action);
 906 	g_free (action_name);
 907 }
 908 
 909 static void
 910 rb_shell_clipboard_playlist_renamed_cb (RBStaticPlaylistSource *source,
 911 					GParamSpec *spec,
 912 					RBShellClipboard *clipboard)
 913 {
 914 	char *name, *action_name;
 915 	GtkAction *action;
 916 
 917 	g_object_get (source, "name", &name, NULL);
 918 
 919 	action_name = generate_action_name (source, clipboard);
 920 	action = gtk_action_group_get_action (clipboard->priv->actiongroup, action_name);
 921 	g_assert (action);
 922 	g_free (action_name);
 923 
 924 	g_object_set (action, "label", name, NULL);
 925 	g_free (name);
 926 }
 927 
 928 static void
 929 rb_shell_clipboard_playlist_visible_cb (RBStaticPlaylistSource *source,
 930 					GParamSpec *spec,
 931 					RBShellClipboard *clipboard)
 932 {
 933 	gboolean visible = FALSE;
 934 	char *action_name;
 935 	GtkAction *action;
 936 
 937 	g_object_get (source, "visibility", &visible, NULL);
 938 
 939 	action_name = generate_action_name (source, clipboard);
 940 	action = gtk_action_group_get_action (clipboard->priv->actiongroup, action_name);
 941 	g_assert (action);
 942 	g_free (action_name);
 943 
 944 	gtk_action_set_visible (action, visible);
 945 	g_object_unref (G_OBJECT (action));
 946 }
 947 
 948 static gboolean
 949 add_playlist_to_menu (GtkTreeModel *model,
 950 		      GtkTreePath *path,
 951 		      GtkTreeIter *iter,
 952 		      RBShellClipboard *clipboard)
 953 {
 954 	RhythmDBEntryType *entry_type;
 955 	RhythmDBEntryType *source_entry_type;
 956 	RBDisplayPage *page = NULL;
 957 	char *action_name;
 958 	GtkAction *action;
 959 	int i;
 960 
 961 	gtk_tree_model_get (GTK_TREE_MODEL (model), iter,
 962 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page, -1);
 963 
 964 	if (page == NULL) {
 965 		return FALSE;
 966 	}
 967 
 968 	if (RB_IS_STATIC_PLAYLIST_SOURCE (page) == FALSE) {
 969 		g_object_unref (page);
 970 		return FALSE;
 971 	}
 972 
 973 	/* FIXME this isn't quite right; we'd want to be able to add
 974 	 * songs from the library to playlists on devices (transferring
 975 	 * the song to the device first), surely?
 976 	 */
 977 	g_object_get (clipboard->priv->source, "entry-type", &entry_type, NULL);
 978 	g_object_get (page, "entry-type", &source_entry_type, NULL);
 979 	if (source_entry_type != entry_type || source_entry_type == NULL) {
 980 		g_object_unref (page);
 981 		if (entry_type)
 982 			g_object_unref (entry_type);
 983 		if (source_entry_type)
 984 			g_object_unref (source_entry_type);
 985 		return FALSE;
 986 	}
 987 
 988 	action_name = generate_action_name (RB_STATIC_PLAYLIST_SOURCE (page), clipboard);
 989 	action = gtk_action_group_get_action (clipboard->priv->actiongroup, action_name);
 990 	if (action == NULL) {
 991 		char *name;
 992 
 993 		g_object_get (page, "name", &name, NULL);
 994 		action = gtk_action_new (action_name, name, NULL, NULL);
 995 		gtk_action_group_add_action (clipboard->priv->actiongroup, action);
 996 		g_free (name);
 997 
 998 		g_object_set_data (G_OBJECT (action), "playlist-source", page);
 999 		g_signal_connect_object (action,
1000 					 "activate", G_CALLBACK (rb_shell_clipboard_playlist_add_cb),
1001 					 clipboard, 0);
1002 
1003 		g_signal_connect_object (page,
1004 					 "deleted", G_CALLBACK (rb_shell_clipboard_playlist_deleted_cb),
1005 					 clipboard, 0);
1006 		g_signal_connect_object (page,
1007 					 "notify::name", G_CALLBACK (rb_shell_clipboard_playlist_renamed_cb),
1008 					 clipboard, 0);
1009 		g_signal_connect_object (page,
1010 					 "notify::visibility", G_CALLBACK (rb_shell_clipboard_playlist_visible_cb),
1011 					 clipboard, 0);
1012 	}
1013 
1014 	for (i = 0; i < num_playlist_menu_paths; i++) {
1015 		gtk_ui_manager_add_ui (clipboard->priv->ui_mgr, clipboard->priv->playlist_menu_ui_id,
1016 				       playlist_menu_paths[i],
1017 				       action_name, action_name,
1018 				       GTK_UI_MANAGER_AUTO, FALSE);
1019 	}
1020 
1021 	g_object_unref (source_entry_type);
1022 	g_object_unref (entry_type);
1023 	g_free (action_name);
1024 	g_object_unref (page);
1025 
1026 	return FALSE;
1027 }
1028 
1029 static void
1030 rebuild_playlist_menu (RBShellClipboard *clipboard)
1031 {
1032 	GtkTreeModel *model = NULL;
1033 
1034 	if (clipboard->priv->source == NULL)
1035 		return;
1036 
1037 	rb_debug ("rebuilding add-to-playlist menu");
1038 
1039 	if (clipboard->priv->playlist_menu_ui_id != 0) {
1040 		gtk_ui_manager_remove_ui (clipboard->priv->ui_mgr,
1041 					  clipboard->priv->playlist_menu_ui_id);
1042 	} else {
1043 		clipboard->priv->playlist_menu_ui_id =
1044 			gtk_ui_manager_new_merge_id (clipboard->priv->ui_mgr);
1045 	}
1046 
1047 	if (clipboard->priv->playlist_manager != NULL) {
1048 		g_object_get (clipboard->priv->playlist_manager, "display-page-model", &model, NULL);
1049 	}
1050 
1051 	if (model != NULL) {
1052 		gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc)add_playlist_to_menu, clipboard);
1053 		g_object_unref (model);
1054 	}
1055 }
1056 
1057 static gboolean
1058 rebuild_playlist_menu_idle (RBShellClipboard *clipboard)
1059 {
1060 	GDK_THREADS_ENTER ();
1061 	rebuild_playlist_menu (clipboard);
1062 	clipboard->priv->idle_playlist_id = 0;
1063 	GDK_THREADS_LEAVE ();
1064 	return FALSE;
1065 }
1066 
1067 static void
1068 rb_shell_clipboard_playlist_added_cb (RBPlaylistManager *mgr,
1069 				      RBPlaylistSource *source,
1070 				      RBShellClipboard *clipboard)
1071 {
1072 	if (!RB_IS_STATIC_PLAYLIST_SOURCE (source))
1073 		return;
1074 
1075 	if (clipboard->priv->idle_playlist_id == 0) {
1076 		clipboard->priv->idle_playlist_id =
1077 			g_idle_add ((GSourceFunc)rebuild_playlist_menu_idle, clipboard);
1078 	}
1079 }