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 }