2006-12-14 Francisco Javier F. Serrador <serrador@openshine.com>
[rhythmbox.git] / shell / rb-shell-clipboard.c
blob9e901f8f3b83c64c59cbc3dbc0beeed9105e8871
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * arch-tag: Implementation of song cut/paste handler object
5 * Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
6 * Copyright (C) 2003,2004 Colin Walters <walters@verbum.org>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include <config.h>
26 #include <glib/gi18n.h>
27 #include <gtk/gtk.h>
28 #include <gtk/gtkstock.h>
30 #include "rb-shell-clipboard.h"
31 #include "rb-playlist-manager.h"
32 #include "rb-play-queue-source.h"
33 #include "rb-sourcelist-model.h"
34 #include "rhythmdb.h"
35 #include "rb-debug.h"
37 static void rb_shell_clipboard_class_init (RBShellClipboardClass *klass);
38 static void rb_shell_clipboard_init (RBShellClipboard *shell_clipboard);
39 static GObject *rb_shell_clipboard_constructor (GType type, guint n_construct_properties,
40 GObjectConstructParam *construct_properties);
41 static void rb_shell_clipboard_finalize (GObject *object);
42 static void rb_shell_clipboard_set_property (GObject *object,
43 guint prop_id,
44 const GValue *value,
45 GParamSpec *pspec);
46 static void rb_shell_clipboard_get_property (GObject *object,
47 guint prop_id,
48 GValue *value,
49 GParamSpec *pspec);
50 static void rb_shell_clipboard_sync (RBShellClipboard *clipboard);
51 static void rb_shell_clipboard_cmd_select_all (GtkAction *action,
52 RBShellClipboard *clipboard);
53 static void rb_shell_clipboard_cmd_select_none (GtkAction *action,
54 RBShellClipboard *clipboard);
55 static void rb_shell_clipboard_cmd_cut (GtkAction *action,
56 RBShellClipboard *clipboard);
57 static void rb_shell_clipboard_cmd_copy (GtkAction *action,
58 RBShellClipboard *clipboard);
59 static void rb_shell_clipboard_cmd_paste (GtkAction *action,
60 RBShellClipboard *clipboard);
61 static void rb_shell_clipboard_cmd_delete (GtkAction *action,
62 RBShellClipboard *clipboard);
63 static void rb_shell_clipboard_cmd_queue_delete (GtkAction *action,
64 RBShellClipboard *clipboard);
65 static void rb_shell_clipboard_cmd_move_to_trash (GtkAction *action,
66 RBShellClipboard *clipboard);
67 static void rb_shell_clipboard_set (RBShellClipboard *clipboard,
68 GList *nodes);
69 static gboolean rb_shell_clipboard_idle_poll_deletions (RBShellClipboard *clipboard);
70 static void rb_shell_clipboard_playlist_added_cb (RBPlaylistManager *mgr,
71 RBPlaylistSource *source,
72 RBShellClipboard *clipboard);
73 static void rb_shell_clipboard_entry_deleted_cb (RhythmDB *db,
74 RhythmDBEntry *entry,
75 RBShellClipboard *clipboard);
76 static void rb_shell_clipboard_entryview_changed_cb (RBEntryView *view,
77 RBShellClipboard *clipboard);
78 static void rb_shell_clipboard_entries_changed_cb (RBEntryView *view,
79 gpointer stuff,
80 RBShellClipboard *clipboard);
81 static void rb_shell_clipboard_cmd_add_to_playlist_new (GtkAction *action,
82 RBShellClipboard *clipboard);
83 static void rb_shell_clipboard_cmd_add_song_to_queue (GtkAction *action,
84 RBShellClipboard *clipboard);
85 static void rb_shell_clipboard_cmd_song_info (GtkAction *action,
86 RBShellClipboard *clipboard);
87 static void rb_shell_clipboard_cmd_queue_song_info (GtkAction *action,
88 RBShellClipboard *clipboard);
89 static void rebuild_playlist_menu (RBShellClipboard *clipboard);
90 static gboolean rebuild_playlist_menu_idle (RBShellClipboard *clipboard);
92 struct RBShellClipboardPrivate
94 RhythmDB *db;
95 RBSource *source;
96 RBStaticPlaylistSource *queue_source;
97 RBPlaylistManager *playlist_manager;
99 GtkUIManager *ui_mgr;
100 GtkActionGroup *actiongroup;
101 guint playlist_menu_ui_id;
103 GHashTable *signal_hash;
105 GAsyncQueue *deleted_queue;
107 guint idle_deletion_id, idle_sync_id, idle_playlist_id;
109 GList *entries;
112 #define RB_SHELL_CLIPBOARD_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_SHELL_CLIPBOARD, RBShellClipboardPrivate))
114 enum
116 PROP_0,
117 PROP_SOURCE,
118 PROP_ACTION_GROUP,
119 PROP_DB,
120 PROP_QUEUE_SOURCE,
121 PROP_PLAYLIST_MANAGER,
122 PROP_UI_MANAGER,
125 static GtkActionEntry rb_shell_clipboard_actions [] =
127 { "EditSelectAll", NULL, N_("Select _All"), "<control>A",
128 N_("Select all songs"),
129 G_CALLBACK (rb_shell_clipboard_cmd_select_all) },
130 { "EditSelectNone", NULL, N_("D_eselect All"), "<shift><control>A",
131 N_("Deselect all songs"),
132 G_CALLBACK (rb_shell_clipboard_cmd_select_none) },
133 { "EditCut", GTK_STOCK_CUT, N_("Cu_t"), "<control>X",
134 N_("Cut selection"),
135 G_CALLBACK (rb_shell_clipboard_cmd_cut) },
136 { "EditCopy", GTK_STOCK_COPY, N_("_Copy"), "<control>C",
137 N_("Copy selection"),
138 G_CALLBACK (rb_shell_clipboard_cmd_copy) },
139 { "EditPaste", GTK_STOCK_PASTE, N_("_Paste"), "<control>V",
140 N_("Paste selection"),
141 G_CALLBACK (rb_shell_clipboard_cmd_paste) },
142 { "EditDelete", GTK_STOCK_REMOVE, N_("_Remove"), NULL,
143 N_("Remove selection"),
144 G_CALLBACK (rb_shell_clipboard_cmd_delete) },
145 { "EditMovetoTrash", GTK_STOCK_DELETE, N_("_Move to Trash"), NULL,
146 N_("Move selection to the trash"),
147 G_CALLBACK (rb_shell_clipboard_cmd_move_to_trash) },
149 { "EditPlaylistAdd", NULL, N_("Add to P_laylist") },
150 { "EditPlaylistAddNew", GTK_STOCK_ADD, N_("_New Playlist..."), NULL,
151 N_("Add the selected songs to a new playlist"),
152 G_CALLBACK (rb_shell_clipboard_cmd_add_to_playlist_new) },
153 { "AddToQueue", GTK_STOCK_ADD, N_("Add _to Play Queue"), NULL,
154 N_("Add the selected songs to the play queue"),
155 G_CALLBACK (rb_shell_clipboard_cmd_add_song_to_queue) },
156 { "QueueDelete", GTK_STOCK_REMOVE, N_("Remove"), NULL,
157 N_("Remove selection"),
158 G_CALLBACK (rb_shell_clipboard_cmd_queue_delete) },
160 { "MusicProperties", GTK_STOCK_PROPERTIES, N_("_Properties"), "<Alt>Return",
161 N_("Show information on the selected song"),
162 G_CALLBACK (rb_shell_clipboard_cmd_song_info) },
163 { "QueueMusicProperties", GTK_STOCK_PROPERTIES, N_("_Properties"), NULL,
164 N_("Show information on the selected song"),
165 G_CALLBACK (rb_shell_clipboard_cmd_queue_song_info) },
167 static guint rb_shell_clipboard_n_actions = G_N_ELEMENTS (rb_shell_clipboard_actions);
169 static const char *playlist_menu_paths[] = {
170 "/MenuBar/EditMenu/EditPlaylistAddMenu/EditPlaylistAddPlaceholder",
171 "/BrowserSourceViewPopup/BrowserSourcePopupPlaylistAdd/BrowserSourcePopupPlaylistAddPlaceholder",
172 "/PlaylistViewPopup/PlaylistPopupPlaylistAdd/PlaylistPopupPlaylistAddPlaceholder",
174 static guint num_playlist_menu_paths = G_N_ELEMENTS (playlist_menu_paths);
176 G_DEFINE_TYPE (RBShellClipboard, rb_shell_clipboard, G_TYPE_OBJECT)
178 static void
179 rb_shell_clipboard_class_init (RBShellClipboardClass *klass)
181 GObjectClass *object_class = G_OBJECT_CLASS (klass);
183 object_class->finalize = rb_shell_clipboard_finalize;
184 object_class->constructor = rb_shell_clipboard_constructor;
186 object_class->set_property = rb_shell_clipboard_set_property;
187 object_class->get_property = rb_shell_clipboard_get_property;
189 g_object_class_install_property (object_class,
190 PROP_SOURCE,
191 g_param_spec_object ("source",
192 "RBSource",
193 "RBSource object",
194 RB_TYPE_SOURCE,
195 G_PARAM_READWRITE));
196 g_object_class_install_property (object_class,
197 PROP_ACTION_GROUP,
198 g_param_spec_object ("action-group",
199 "GtkActionGroup",
200 "GtkActionGroup object",
201 GTK_TYPE_ACTION_GROUP,
202 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
203 g_object_class_install_property (object_class,
204 PROP_DB,
205 g_param_spec_object ("db",
206 "RhythmDB",
207 "RhythmDB database",
208 RHYTHMDB_TYPE,
209 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
210 g_object_class_install_property (object_class,
211 PROP_QUEUE_SOURCE,
212 g_param_spec_object ("queue-source",
213 "RBPlaylistSource",
214 "RBPlaylistSource object",
215 RB_TYPE_PLAYLIST_SOURCE,
216 G_PARAM_READWRITE));
217 g_object_class_install_property (object_class,
218 PROP_PLAYLIST_MANAGER,
219 g_param_spec_object ("playlist-manager",
220 "RBPlaylistManager",
221 "RBPlaylistManager object",
222 RB_TYPE_PLAYLIST_MANAGER,
223 G_PARAM_READWRITE));
224 g_object_class_install_property (object_class,
225 PROP_UI_MANAGER,
226 g_param_spec_object ("ui-manager",
227 "GtkUIManager",
228 "GtkUIManager object",
229 GTK_TYPE_UI_MANAGER,
230 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
232 g_type_class_add_private (klass, sizeof (RBShellClipboardPrivate));
235 static void
236 rb_shell_clipboard_init (RBShellClipboard *shell_clipboard)
238 shell_clipboard->priv = RB_SHELL_CLIPBOARD_GET_PRIVATE (shell_clipboard);
240 shell_clipboard->priv->signal_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal,
241 NULL, g_free);
243 shell_clipboard->priv->deleted_queue = g_async_queue_new ();
245 shell_clipboard->priv->idle_deletion_id = g_idle_add ((GSourceFunc) rb_shell_clipboard_idle_poll_deletions, shell_clipboard);
248 static void
249 unset_source_internal (RBShellClipboard *clipboard)
251 if (clipboard->priv->source != NULL) {
252 RBEntryView *songs = rb_source_get_entry_view (clipboard->priv->source);
254 if (songs) {
255 g_signal_handlers_disconnect_by_func (G_OBJECT (songs),
256 G_CALLBACK (rb_shell_clipboard_entryview_changed_cb),
257 clipboard);
258 g_signal_handlers_disconnect_by_func (G_OBJECT (songs),
259 G_CALLBACK (rb_shell_clipboard_entries_changed_cb),
260 clipboard);
263 clipboard->priv->source = NULL;
266 static void
267 rb_shell_clipboard_finalize (GObject *object)
269 RBShellClipboard *shell_clipboard;
271 g_return_if_fail (object != NULL);
272 g_return_if_fail (RB_IS_SHELL_CLIPBOARD (object));
274 shell_clipboard = RB_SHELL_CLIPBOARD (object);
276 g_return_if_fail (shell_clipboard->priv != NULL);
278 /* release references to the source */
279 unset_source_internal (shell_clipboard);
281 g_hash_table_destroy (shell_clipboard->priv->signal_hash);
283 g_list_foreach (shell_clipboard->priv->entries, (GFunc)rhythmdb_entry_unref, NULL);
284 g_list_free (shell_clipboard->priv->entries);
286 g_async_queue_unref (shell_clipboard->priv->deleted_queue);
288 if (shell_clipboard->priv->idle_sync_id)
289 g_source_remove (shell_clipboard->priv->idle_sync_id);
290 if (shell_clipboard->priv->idle_deletion_id)
291 g_source_remove (shell_clipboard->priv->idle_deletion_id);
292 if (shell_clipboard->priv->idle_playlist_id)
293 g_source_remove (shell_clipboard->priv->idle_playlist_id);
295 G_OBJECT_CLASS (rb_shell_clipboard_parent_class)->finalize (object);
298 static GObject *
299 rb_shell_clipboard_constructor (GType type, guint n_construct_properties,
300 GObjectConstructParam *construct_properties)
302 RBShellClipboard *clip;
303 RBShellClipboardClass *klass;
304 GObjectClass *parent_class;
306 klass = RB_SHELL_CLIPBOARD_CLASS (g_type_class_peek (RB_TYPE_SHELL_CLIPBOARD));
308 parent_class = G_OBJECT_CLASS (rb_shell_clipboard_parent_class);
309 clip = RB_SHELL_CLIPBOARD (parent_class->constructor (type,
310 n_construct_properties,
311 construct_properties));
313 g_signal_connect_object (G_OBJECT (clip->priv->db),
314 "entry_deleted",
315 G_CALLBACK (rb_shell_clipboard_entry_deleted_cb),
316 clip, 0);
318 return G_OBJECT (clip);
321 static void
322 rb_shell_clipboard_set_source_internal (RBShellClipboard *clipboard,
323 RBSource *source)
325 unset_source_internal (clipboard);
327 clipboard->priv->source = source;
328 rb_debug ("selected source %p", source);
330 rb_shell_clipboard_sync (clipboard);
332 if (clipboard->priv->source != NULL) {
333 RBEntryView *songs = rb_source_get_entry_view (clipboard->priv->source);
335 if (songs) {
336 g_signal_connect_object (G_OBJECT (songs),
337 "selection-changed",
338 G_CALLBACK (rb_shell_clipboard_entryview_changed_cb),
339 clipboard, 0);
340 g_signal_connect_object (G_OBJECT (songs),
341 "entry-added",
342 G_CALLBACK (rb_shell_clipboard_entries_changed_cb),
343 clipboard, 0);
344 g_signal_connect_object (G_OBJECT (songs),
345 "entry-deleted",
346 G_CALLBACK (rb_shell_clipboard_entries_changed_cb),
347 clipboard, 0);
348 g_signal_connect_object (G_OBJECT (songs),
349 "entries-replaced",
350 G_CALLBACK (rb_shell_clipboard_entryview_changed_cb),
351 clipboard, 0);
355 rebuild_playlist_menu (clipboard);
358 static void
359 rb_shell_clipboard_set_property (GObject *object,
360 guint prop_id,
361 const GValue *value,
362 GParamSpec *pspec)
364 RBShellClipboard *clipboard = RB_SHELL_CLIPBOARD (object);
366 switch (prop_id)
368 case PROP_SOURCE:
369 rb_shell_clipboard_set_source_internal (clipboard, g_value_get_object (value));
370 break;
371 case PROP_ACTION_GROUP:
372 clipboard->priv->actiongroup = g_value_get_object (value);
373 gtk_action_group_add_actions (clipboard->priv->actiongroup,
374 rb_shell_clipboard_actions,
375 rb_shell_clipboard_n_actions,
376 clipboard);
377 break;
378 case PROP_DB:
379 clipboard->priv->db = g_value_get_object (value);
380 break;
381 case PROP_UI_MANAGER:
382 clipboard->priv->ui_mgr = g_value_get_object (value);
383 break;
384 case PROP_PLAYLIST_MANAGER:
385 if (clipboard->priv->playlist_manager != NULL) {
386 g_signal_handlers_disconnect_by_func (clipboard->priv->playlist_manager,
387 G_CALLBACK (rb_shell_clipboard_playlist_added_cb),
388 clipboard);
391 clipboard->priv->playlist_manager = g_value_get_object (value);
392 if (clipboard->priv->playlist_manager != NULL) {
393 g_signal_connect_object (G_OBJECT (clipboard->priv->playlist_manager),
394 "playlist-added", G_CALLBACK (rb_shell_clipboard_playlist_added_cb),
395 clipboard, 0);
397 rebuild_playlist_menu (clipboard);
400 break;
401 case PROP_QUEUE_SOURCE:
402 if (clipboard->priv->queue_source != NULL) {
403 RBEntryView *sidebar;
404 g_object_get (clipboard->priv->queue_source, "sidebar", &sidebar, NULL);
405 g_signal_handlers_disconnect_by_func (sidebar,
406 G_CALLBACK (rb_shell_clipboard_entryview_changed_cb),
407 clipboard);
408 g_object_unref (sidebar);
411 clipboard->priv->queue_source = g_value_get_object (value);
412 if (clipboard->priv->queue_source != NULL) {
413 RBEntryView *sidebar;
414 g_object_get (clipboard->priv->queue_source, "sidebar", &sidebar, NULL);
415 g_signal_connect_object (G_OBJECT (sidebar), "selection-changed",
416 G_CALLBACK (rb_shell_clipboard_entryview_changed_cb),
417 clipboard, 0);
418 g_object_unref (sidebar);
420 break;
421 default:
422 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
423 break;
427 static void
428 rb_shell_clipboard_get_property (GObject *object,
429 guint prop_id,
430 GValue *value,
431 GParamSpec *pspec)
433 RBShellClipboard *clipboard = RB_SHELL_CLIPBOARD (object);
435 switch (prop_id)
437 case PROP_SOURCE:
438 g_value_set_object (value, clipboard->priv->source);
439 break;
440 case PROP_ACTION_GROUP:
441 g_value_set_object (value, clipboard->priv->actiongroup);
442 break;
443 case PROP_DB:
444 g_value_set_object (value, clipboard->priv->db);
445 break;
446 case PROP_UI_MANAGER:
447 g_value_set_object (value, clipboard->priv->ui_mgr);
448 break;
449 case PROP_PLAYLIST_MANAGER:
450 g_value_set_object (value, clipboard->priv->playlist_manager);
451 break;
452 case PROP_QUEUE_SOURCE:
453 g_value_set_object (value, clipboard->priv->queue_source);
454 break;
455 default:
456 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
457 break;
461 void
462 rb_shell_clipboard_set_source (RBShellClipboard *clipboard,
463 RBSource *source)
465 g_return_if_fail (RB_IS_SHELL_CLIPBOARD (clipboard));
466 if (source != NULL)
467 g_return_if_fail (RB_IS_SOURCE (source));
469 g_object_set (G_OBJECT (clipboard), "source", source, NULL);
472 RBShellClipboard *
473 rb_shell_clipboard_new (GtkActionGroup *actiongroup,
474 GtkUIManager *ui_mgr,
475 RhythmDB *db)
477 return g_object_new (RB_TYPE_SHELL_CLIPBOARD,
478 "action-group", actiongroup,
479 "ui-manager", ui_mgr,
480 "db", db,
481 NULL);
484 static gboolean
485 rb_shell_clipboard_sync_idle (RBShellClipboard *clipboard)
487 GDK_THREADS_ENTER ();
488 rb_shell_clipboard_sync (clipboard);
489 clipboard->priv->idle_sync_id = 0;
490 GDK_THREADS_LEAVE ();
492 return FALSE;
495 static void
496 rb_shell_clipboard_sync (RBShellClipboard *clipboard)
498 RBEntryView *view;
499 gboolean have_selection = FALSE;
500 gboolean have_sidebar_selection = FALSE;
501 gboolean can_cut = FALSE;
502 gboolean can_paste = FALSE;
503 gboolean can_delete = FALSE;
504 gboolean can_copy = FALSE;
505 gboolean can_add_to_queue = FALSE;
506 gboolean can_move_to_trash = FALSE;
507 gboolean can_select_all = FALSE;
508 gboolean can_show_properties = FALSE;
509 GtkAction *action;
510 RhythmDBEntryType entry_type;
512 if (!clipboard->priv->source)
513 return;
515 view = rb_source_get_entry_view (clipboard->priv->source);
516 if (view) {
517 have_selection = rb_entry_view_have_selection (view);
518 can_select_all = !rb_entry_view_have_complete_selection (view);
521 if (clipboard->priv->queue_source) {
522 RBEntryView *sidebar;
523 g_object_get (clipboard->priv->queue_source, "sidebar", &sidebar, NULL);
524 have_sidebar_selection = rb_entry_view_have_selection (sidebar);
525 g_object_unref (sidebar);
528 rb_debug ("syncing clipboard");
530 if (g_list_length (clipboard->priv->entries) > 0)
531 can_paste = rb_source_can_paste (clipboard->priv->source);
533 if (have_selection) {
534 can_cut = rb_source_can_cut (clipboard->priv->source);
535 can_delete = rb_source_can_delete (clipboard->priv->source);
536 can_copy = rb_source_can_copy (clipboard->priv->source);
537 can_move_to_trash = rb_source_can_move_to_trash (clipboard->priv->source);
538 can_show_properties = rb_source_can_show_properties (clipboard->priv->source);
540 if (clipboard->priv->queue_source)
541 can_add_to_queue = rb_source_can_add_to_queue (clipboard->priv->source);
544 action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditCut");
545 g_object_set (G_OBJECT (action), "sensitive", can_cut, NULL);
547 action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditDelete");
548 g_object_set (G_OBJECT (action), "sensitive", can_delete, NULL);
550 action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditMovetoTrash");
551 g_object_set (G_OBJECT (action), "sensitive", can_move_to_trash, NULL);
553 action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditCopy");
554 g_object_set (G_OBJECT (action), "sensitive", can_copy, NULL);
556 action = gtk_action_group_get_action (clipboard->priv->actiongroup,"EditPaste");
557 g_object_set (G_OBJECT (action), "sensitive", can_paste, NULL);
559 action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditPlaylistAdd");
560 g_object_set (G_OBJECT (action), "sensitive", can_copy, NULL);
562 action = gtk_action_group_get_action (clipboard->priv->actiongroup, "AddToQueue");
563 g_object_set (G_OBJECT (action), "sensitive", can_add_to_queue, NULL);
565 action = gtk_action_group_get_action (clipboard->priv->actiongroup, "MusicProperties");
566 g_object_set (G_OBJECT (action), "sensitive", can_show_properties, NULL);
568 action = gtk_action_group_get_action (clipboard->priv->actiongroup, "QueueMusicProperties");
569 g_object_set (G_OBJECT (action), "sensitive", have_sidebar_selection, NULL);
571 action = gtk_action_group_get_action (clipboard->priv->actiongroup, "QueueDelete");
572 g_object_set (G_OBJECT (action), "sensitive", have_sidebar_selection, NULL);
574 action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditSelectAll");
575 g_object_set (G_OBJECT (action), "sensitive", can_select_all, NULL);
577 action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditSelectNone");
578 g_object_set (G_OBJECT (action), "sensitive", have_selection, NULL);
580 /* disable the whole add-to-playlist menu if we can't add to a playlist
581 * FIXME: change this when we support non-library playilst adding
583 action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditPlaylistAdd");
584 g_object_get (clipboard->priv->source, "entry-type", &entry_type, NULL);
585 gtk_action_set_sensitive (action, (entry_type == RHYTHMDB_ENTRY_TYPE_SONG));
586 g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
589 static GtkWidget*
590 get_focussed_widget (RBShellClipboard *clipboard)
592 GtkWidget *window;
593 GtkWidget *widget;
595 /* FIXME: this should be better */
596 window = gtk_widget_get_toplevel (GTK_WIDGET (clipboard->priv->source));
597 widget = gtk_window_get_focus (GTK_WINDOW (window));
599 return widget;
602 static void
603 rb_shell_clipboard_cmd_select_all (GtkAction *action,
604 RBShellClipboard *clipboard)
606 RBEntryView *entryview;
607 GtkWidget *widget;
609 rb_debug ("select all");
610 widget = get_focussed_widget (clipboard);
611 if (GTK_IS_EDITABLE (widget)) {
612 gtk_editable_select_region (GTK_EDITABLE (widget), 0, -1);
613 } else {
614 /* select all tracks in the entry view */
615 entryview = rb_source_get_entry_view (clipboard->priv->source);
616 rb_entry_view_select_all (entryview);
620 static void
621 rb_shell_clipboard_cmd_select_none (GtkAction *action,
622 RBShellClipboard *clipboard)
624 RBEntryView *entryview;
625 GtkWidget *widget;
627 rb_debug ("select none");
628 widget = get_focussed_widget (clipboard);
629 if (GTK_IS_EDITABLE (widget)) {
630 gtk_editable_select_region (GTK_EDITABLE (widget), -1, -1);
631 } else {
632 entryview = rb_source_get_entry_view (clipboard->priv->source);
633 rb_entry_view_select_none (entryview);
637 static void
638 rb_shell_clipboard_cmd_cut (GtkAction *action,
639 RBShellClipboard *clipboard)
641 rb_debug ("cut");
642 rb_shell_clipboard_set (clipboard,
643 rb_source_cut (clipboard->priv->source));
646 static void
647 rb_shell_clipboard_cmd_copy (GtkAction *action,
648 RBShellClipboard *clipboard)
650 rb_debug ("copy");
651 rb_shell_clipboard_set (clipboard,
652 rb_source_copy (clipboard->priv->source));
655 static void
656 rb_shell_clipboard_cmd_paste (GtkAction *action,
657 RBShellClipboard *clipboard)
659 rb_debug ("paste");
660 rb_source_paste (clipboard->priv->source, clipboard->priv->entries);
663 static void
664 rb_shell_clipboard_cmd_delete (GtkAction *action,
665 RBShellClipboard *clipboard)
667 rb_debug ("delete");
668 rb_source_delete (clipboard->priv->source);
671 static void
672 rb_shell_clipboard_cmd_queue_delete (GtkAction *action,
673 RBShellClipboard *clipboard)
675 rb_debug ("delete");
676 rb_play_queue_source_sidebar_delete (RB_PLAY_QUEUE_SOURCE (clipboard->priv->queue_source));
679 static void
680 rb_shell_clipboard_cmd_move_to_trash (GtkAction *action,
681 RBShellClipboard *clipboard)
683 rb_debug ("movetotrash");
684 rb_source_move_to_trash (clipboard->priv->source);
687 static void
688 rb_shell_clipboard_set (RBShellClipboard *clipboard,
689 GList *entries)
691 if (clipboard->priv->entries != NULL) {
692 g_list_foreach (clipboard->priv->entries, (GFunc)rhythmdb_entry_unref, NULL);
693 g_list_free (clipboard->priv->entries);
696 clipboard->priv->entries = entries;
699 static gboolean
700 rb_shell_clipboard_process_deletions (RBShellClipboard *clipboard)
702 RhythmDBEntry *entry;
704 if (clipboard->priv->entries) {
705 GList *tem, *finished = NULL;
706 gboolean processed = FALSE;
708 while ((entry = g_async_queue_try_pop (clipboard->priv->deleted_queue)) != NULL) {
709 clipboard->priv->entries = g_list_remove (clipboard->priv->entries, entry);
710 finished = g_list_prepend (finished, entry);
711 processed = TRUE;
714 if (processed)
715 rb_shell_clipboard_sync (clipboard);
717 for (tem = finished; tem; tem = tem->next)
718 rhythmdb_entry_unref (tem->data);
719 g_list_free (finished);
721 return processed;
722 } else {
723 /* Fast path for when there's nothing in the clipboard */
724 while ((entry = g_async_queue_try_pop (clipboard->priv->deleted_queue)) != NULL)
725 rhythmdb_entry_unref (entry);
726 return FALSE;
730 static gboolean
731 rb_shell_clipboard_idle_poll_deletions (RBShellClipboard *clipboard)
733 gboolean did_sync;
735 GDK_THREADS_ENTER ();
737 did_sync = rb_shell_clipboard_process_deletions (clipboard);
739 if (did_sync)
740 clipboard->priv->idle_deletion_id =
741 g_idle_add_full (G_PRIORITY_LOW,
742 (GSourceFunc) rb_shell_clipboard_idle_poll_deletions,
743 clipboard, NULL);
744 else
745 clipboard->priv->idle_deletion_id =
746 g_timeout_add (300,
747 (GSourceFunc) rb_shell_clipboard_idle_poll_deletions,
748 clipboard);
750 GDK_THREADS_LEAVE ();
752 return FALSE;
755 static void
756 rb_shell_clipboard_entry_deleted_cb (RhythmDB *db,
757 RhythmDBEntry *entry,
758 RBShellClipboard *clipboard)
760 rhythmdb_entry_ref (entry);
761 g_async_queue_push (clipboard->priv->deleted_queue, entry);
764 static void
765 rb_shell_clipboard_entryview_changed_cb (RBEntryView *view,
766 RBShellClipboard *clipboard)
768 if (clipboard->priv->idle_sync_id == 0)
769 clipboard->priv->idle_sync_id = g_idle_add ((GSourceFunc) rb_shell_clipboard_sync_idle,
770 clipboard);
771 rb_debug ("entryview changed");
774 static void
775 rb_shell_clipboard_entries_changed_cb (RBEntryView *view,
776 gpointer stuff,
777 RBShellClipboard *clipboard)
779 rb_debug ("entryview changed");
780 if (clipboard->priv->idle_sync_id == 0)
781 clipboard->priv->idle_sync_id = g_idle_add ((GSourceFunc) rb_shell_clipboard_sync_idle,
782 clipboard);
785 static void
786 rb_shell_clipboard_cmd_add_to_playlist_new (GtkAction *action,
787 RBShellClipboard *clipboard)
789 GList *entries;
790 RBSource *playlist_source;
792 rb_debug ("add to new playlist");
794 entries = rb_source_copy (clipboard->priv->source);
795 playlist_source = rb_playlist_manager_new_playlist (clipboard->priv->playlist_manager,
796 NULL, FALSE);
797 rb_source_paste (playlist_source, entries);
799 g_list_foreach (entries, (GFunc)rhythmdb_entry_unref, NULL);
800 g_list_free (entries);
803 static void
804 rb_shell_clipboard_cmd_add_song_to_queue (GtkAction *action,
805 RBShellClipboard *clipboard)
807 rb_debug ("add to queue");
808 rb_source_add_to_queue (clipboard->priv->source,
809 RB_SOURCE (clipboard->priv->queue_source));
812 static void
813 rb_shell_clipboard_cmd_song_info (GtkAction *action,
814 RBShellClipboard *clipboard)
816 rb_debug ("song info");
818 rb_source_song_properties (clipboard->priv->source);
821 static void
822 rb_shell_clipboard_cmd_queue_song_info (GtkAction *action,
823 RBShellClipboard *clipboard)
825 rb_debug ("song info");
826 rb_play_queue_source_sidebar_song_info (RB_PLAY_QUEUE_SOURCE (clipboard->priv->queue_source));
829 static void
830 rb_shell_clipboard_playlist_add_cb (GtkAction *action,
831 RBShellClipboard *clipboard)
833 RBSource *playlist_source;
834 GList *entries;
836 rb_debug ("add to exisintg playlist");
837 playlist_source = g_object_get_data (G_OBJECT (action), "playlist-source");
839 entries = rb_source_copy (clipboard->priv->source);
840 rb_source_paste (playlist_source, entries);
842 g_list_foreach (entries, (GFunc)rhythmdb_entry_unref, NULL);
843 g_list_free (entries);
846 static char *
847 generate_action_name (RBStaticPlaylistSource *source,
848 RBShellClipboard *clipboard)
850 return g_strdup_printf ("AddToPlaylistClipboardAction%p", source);
853 static void
854 rb_shell_clipboard_playlist_deleted_cb (RBStaticPlaylistSource *source,
855 RBShellClipboard *clipboard)
857 char *action_name;
858 GtkAction *action;
860 /* first rebuild the menu */
861 rebuild_playlist_menu (clipboard);
863 /* then remove the 'add to playlist' action for the deleted playlist */
864 action_name = generate_action_name (source, clipboard);
865 action = gtk_action_group_get_action (clipboard->priv->actiongroup, action_name);
866 g_assert (action);
867 gtk_action_group_remove_action (clipboard->priv->actiongroup, action);
868 g_free (action_name);
871 static void
872 rb_shell_clipboard_playlist_renamed_cb (RBStaticPlaylistSource *source,
873 GParamSpec *spec,
874 RBShellClipboard *clipboard)
876 char *name, *action_name;
877 GtkAction *action;
879 g_object_get (source, "name", &name, NULL);
881 action_name = generate_action_name (source, clipboard);
882 action = gtk_action_group_get_action (clipboard->priv->actiongroup, action_name);
883 g_assert (action);
884 g_free (action_name);
886 g_object_set (action, "label", name, NULL);
887 g_free (name);
888 g_object_unref (action);
891 static void
892 rb_shell_clipboard_playlist_visible_cb (RBStaticPlaylistSource *source,
893 GParamSpec *spec,
894 RBShellClipboard *clipboard)
896 gboolean visible = FALSE;
897 char *action_name;
898 GtkAction *action;
900 g_object_get (source, "visibility", &visible, NULL);
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 g_free (action_name);
907 gtk_action_set_visible (action, visible);
908 g_object_unref (G_OBJECT (action));
911 static gboolean
912 add_playlist_to_menu (GtkTreeModel *model,
913 GtkTreePath *path,
914 GtkTreeIter *iter,
915 RBShellClipboard *clipboard)
917 RhythmDBEntryType entry_type;
918 RhythmDBEntryType source_entry_type;
919 RBSource *source = NULL;
920 char *action_name;
921 GtkAction *action;
922 int i;
924 gtk_tree_model_get (GTK_TREE_MODEL (model), iter,
925 RB_SOURCELIST_MODEL_COLUMN_SOURCE, &source, -1);
927 if (source == NULL) {
928 return FALSE;
931 if (!RB_IS_STATIC_PLAYLIST_SOURCE (source)) {
932 g_object_unref (source);
933 return FALSE;
936 /* FIXME: allow add-to-playlist for iPods and the like,
937 * based on the currently selected source
939 entry_type = RHYTHMDB_ENTRY_TYPE_SONG;
940 g_object_get (source, "entry-type", &source_entry_type, NULL);
941 if (source_entry_type != entry_type) {
942 g_object_unref (source);
943 g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, source_entry_type);
944 return FALSE;
947 action_name = generate_action_name (RB_STATIC_PLAYLIST_SOURCE (source), clipboard);
948 action = gtk_action_group_get_action (clipboard->priv->actiongroup, action_name);
949 if (action == NULL) {
950 char *name;
952 g_object_get (source, "name", &name, NULL);
953 action = gtk_action_new (action_name, name, NULL, NULL);
954 gtk_action_group_add_action (clipboard->priv->actiongroup, action);
955 g_free (name);
957 g_object_set_data (G_OBJECT (action), "playlist-source", source);
958 g_signal_connect_object (G_OBJECT (action),
959 "activate", G_CALLBACK (rb_shell_clipboard_playlist_add_cb),
960 clipboard, 0);
962 g_signal_connect_object (source,
963 "deleted", G_CALLBACK (rb_shell_clipboard_playlist_deleted_cb),
964 clipboard, 0);
965 g_signal_connect_object (source,
966 "notify::name", G_CALLBACK (rb_shell_clipboard_playlist_renamed_cb),
967 clipboard, 0);
968 g_signal_connect_object (source,
969 "notify::visibility", G_CALLBACK (rb_shell_clipboard_playlist_visible_cb),
970 clipboard, 0);
973 for (i = 0; i < num_playlist_menu_paths; i++) {
974 gtk_ui_manager_add_ui (clipboard->priv->ui_mgr, clipboard->priv->playlist_menu_ui_id,
975 playlist_menu_paths[i],
976 action_name, action_name,
977 GTK_UI_MANAGER_AUTO, FALSE);
980 g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, source_entry_type);
981 g_free (action_name);
982 g_object_unref (source);
984 return FALSE;
987 static void
988 rebuild_playlist_menu (RBShellClipboard *clipboard)
990 GtkTreeModel *model = NULL;
991 GObject *sourcelist = NULL;
993 rb_debug ("rebuilding add-to-playlist menu");
995 if (clipboard->priv->playlist_menu_ui_id != 0) {
996 gtk_ui_manager_remove_ui (clipboard->priv->ui_mgr,
997 clipboard->priv->playlist_menu_ui_id);
998 } else {
999 clipboard->priv->playlist_menu_ui_id =
1000 gtk_ui_manager_new_merge_id (clipboard->priv->ui_mgr);
1003 if (clipboard->priv->playlist_manager != NULL) {
1004 g_object_get (clipboard->priv->playlist_manager, "sourcelist", &sourcelist, NULL);
1007 if (sourcelist != NULL) {
1008 g_object_get (sourcelist, "model", &model, NULL);
1009 g_object_unref (sourcelist);
1012 if (model != NULL) {
1013 gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc)add_playlist_to_menu, clipboard);
1014 g_object_unref (model);
1018 static gboolean
1019 rebuild_playlist_menu_idle (RBShellClipboard *clipboard)
1021 GDK_THREADS_ENTER ();
1022 rebuild_playlist_menu (clipboard);
1023 clipboard->priv->idle_playlist_id = 0;
1024 GDK_THREADS_LEAVE ();
1025 return FALSE;
1028 static void
1029 rb_shell_clipboard_playlist_added_cb (RBPlaylistManager *mgr,
1030 RBPlaylistSource *source,
1031 RBShellClipboard *clipboard)
1033 if (!RB_IS_STATIC_PLAYLIST_SOURCE (source))
1034 return;
1036 if (clipboard->priv->idle_playlist_id == 0) {
1037 clipboard->priv->idle_playlist_id =
1038 g_idle_add ((GSourceFunc)rebuild_playlist_menu_idle, clipboard);