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 Colin Walters <walters@gnome.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-static-playlist-source
32 * @short_description: Manually defined playlist class
33 *
34 * Static playlists are not defined by a query, but instead by manually selected
35 * and ordered tracks.
36 *
37 * This class is used for static playlists built from the user's library, and is
38 * also a base class for the play queue and for playlists on devices and network
39 * shares.
40 *
41 * It has some ability to track locations that are not yet present in the database
42 * and to add them to the playlist once they are added.
43 */
44
45 #include "config.h"
46
47 #include <string.h>
48
49 #include <libxml/tree.h>
50 #include <glib/gi18n.h>
51 #include <gtk/gtk.h>
52
53 #include "rb-static-playlist-source.h"
54 #include "rb-library-browser.h"
55 #include "rb-util.h"
56 #include "rb-debug.h"
57 #include "rb-stock-icons.h"
58 #include "rb-file-helpers.h"
59 #include "rb-playlist-xml.h"
60 #include "rb-source-search-basic.h"
61 #include "rb-source-toolbar.h"
62
63 static void rb_static_playlist_source_constructed (GObject *object);
64 static void rb_static_playlist_source_dispose (GObject *object);
65 static void rb_static_playlist_source_finalize (GObject *object);
66 static void rb_static_playlist_source_set_property (GObject *object,
67 guint prop_id,
68 const GValue *value,
69 GParamSpec *pspec);
70 static void rb_static_playlist_source_get_property (GObject *object,
71 guint prop_id,
72 GValue *value,
73 GParamSpec *pspec);
74
75 /* source methods */
76 static GList * impl_cut (RBSource *source);
77 static RBTrackTransferBatch *impl_paste (RBSource *asource, GList *entries);
78 static void impl_delete (RBSource *source);
79 static void impl_search (RBSource *asource, RBSourceSearch *search, const char *cur_text, const char *new_text);
80 static void impl_reset_filters (RBSource *asource);
81 static gboolean impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data);
82 static guint impl_want_uri (RBSource *source, const char *uri);
83
84 static GPtrArray *construct_query_from_selection (RBStaticPlaylistSource *source);
85
86 /* playlist methods */
87 static void impl_save_contents_to_xml (RBPlaylistSource *source,
88 xmlNodePtr node);
89
90 /* browser stuff */
91 static GList *impl_get_property_views (RBSource *source);
92 void rb_static_playlist_source_browser_views_activated_cb (GtkWidget *widget,
93 RBStaticPlaylistSource *source);
94 static void rb_static_playlist_source_browser_changed_cb (RBLibraryBrowser *entry,
95 GParamSpec *pspec,
96 RBStaticPlaylistSource *source);
97
98 static void rb_static_playlist_source_do_query (RBStaticPlaylistSource *source);
99
100 static void rb_static_playlist_source_add_id_list (RBStaticPlaylistSource *source,
101 GList *list);
102 static void rb_static_playlist_source_add_uri_list (RBStaticPlaylistSource *source,
103 GList *list);
104 static void rb_static_playlist_source_row_inserted (GtkTreeModel *model,
105 GtkTreePath *path,
106 GtkTreeIter *iter,
107 RBStaticPlaylistSource *source);
108 static gboolean rb_static_playlist_source_filter_entry_drop (RhythmDBQueryModel *model,
109 RhythmDBEntry *entry,
110 RBStaticPlaylistSource *source);
111 static void rb_static_playlist_source_non_entry_dropped (GtkTreeModel *model,
112 const char *uri,
113 int position,
114 RBStaticPlaylistSource *source);
115 static void rb_static_playlist_source_rows_reordered (GtkTreeModel *model,
116 GtkTreePath *path,
117 GtkTreeIter *iter,
118 gint *order_map,
119 RBStaticPlaylistSource *source);
120
121 static GtkRadioActionEntry rb_static_playlist_source_radio_actions [] =
122 {
123 { "StaticPlaylistSearchAll", NULL, N_("Search all fields"), NULL, NULL, RHYTHMDB_PROP_SEARCH_MATCH },
124 { "StaticPlaylistSearchArtists", NULL, N_("Search artists"), NULL, NULL, RHYTHMDB_PROP_ARTIST_FOLDED },
125 { "StaticPlaylistSearchAlbums", NULL, N_("Search albums"), NULL, NULL, RHYTHMDB_PROP_ALBUM_FOLDED },
126 { "StaticPlaylistSearchTitles", NULL, N_("Search titles"), NULL, NULL, RHYTHMDB_PROP_TITLE_FOLDED }
127 };
128
129 enum
130 {
131 PROP_0,
132 PROP_BASE_QUERY_MODEL,
133 PROP_SHOW_BROWSER
134 };
135
136 G_DEFINE_TYPE (RBStaticPlaylistSource, rb_static_playlist_source, RB_TYPE_PLAYLIST_SOURCE)
137 #define RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), \
138 RB_TYPE_STATIC_PLAYLIST_SOURCE, \
139 RBStaticPlaylistSourcePrivate))
140
141 typedef struct
142 {
143 RhythmDBQueryModel *base_model;
144 RhythmDBQueryModel *filter_model;
145
146 RBSourceToolbar *toolbar;
147 RBLibraryBrowser *browser;
148
149 RBSourceSearch *default_search;
150 RhythmDBQuery *search_query;
151
152 gboolean dispose_has_run;
153 } RBStaticPlaylistSourcePrivate;
154
155 static gpointer playlist_pixbuf = NULL;
156
157 static void
158 rb_static_playlist_source_class_init (RBStaticPlaylistSourceClass *klass)
159 {
160 GObjectClass *object_class = G_OBJECT_CLASS (klass);
161 RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
162 RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
163 RBPlaylistSourceClass *playlist_class = RB_PLAYLIST_SOURCE_CLASS (klass);
164
165 object_class->constructed = rb_static_playlist_source_constructed;
166 object_class->dispose = rb_static_playlist_source_dispose;
167 object_class->finalize = rb_static_playlist_source_finalize;
168 object_class->set_property = rb_static_playlist_source_set_property;
169 object_class->get_property = rb_static_playlist_source_get_property;
170
171 page_class->receive_drag = impl_receive_drag;
172
173 source_class->impl_can_cut = (RBSourceFeatureFunc) rb_true_function;
174 source_class->impl_can_paste = (RBSourceFeatureFunc) rb_true_function;
175 source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
176 source_class->impl_cut = impl_cut;
177 source_class->impl_paste = impl_paste;
178 source_class->impl_delete = impl_delete;
179 source_class->impl_search = impl_search;
180 source_class->impl_reset_filters = impl_reset_filters;
181 source_class->impl_get_property_views = impl_get_property_views;
182 source_class->impl_want_uri = impl_want_uri;
183
184 playlist_class->impl_save_contents_to_xml = impl_save_contents_to_xml;
185
186 g_object_class_override_property (object_class,
187 PROP_BASE_QUERY_MODEL,
188 "base-query-model");
189 g_object_class_override_property (object_class,
190 PROP_SHOW_BROWSER,
191 "show-browser");
192
193 g_type_class_add_private (klass, sizeof (RBStaticPlaylistSourcePrivate));
194 }
195
196 void
197 rb_static_playlist_source_create_actions (RBShell *shell)
198 {
199 RBStaticPlaylistSourceClass *klass;
200 GtkUIManager *uimanager;
201
202 klass = RB_STATIC_PLAYLIST_SOURCE_CLASS (g_type_class_ref (RB_TYPE_STATIC_PLAYLIST_SOURCE));
203
204 klass->action_group = gtk_action_group_new ("StaticPlaylistActions");
205 gtk_action_group_set_translation_domain (klass->action_group, GETTEXT_PACKAGE);
206
207 g_object_get (shell, "ui-manager", &uimanager, NULL);
208 gtk_ui_manager_insert_action_group (uimanager, klass->action_group, 0);
209 g_object_unref (uimanager);
210
211 gtk_action_group_add_radio_actions (klass->action_group,
212 rb_static_playlist_source_radio_actions,
213 G_N_ELEMENTS (rb_static_playlist_source_radio_actions),
214 0,
215 NULL,
216 NULL);
217 rb_source_search_basic_create_for_actions (klass->action_group,
218 rb_static_playlist_source_radio_actions,
219 G_N_ELEMENTS (rb_static_playlist_source_radio_actions));
220
221 g_type_class_unref (klass);
222 }
223
224 static void
225 set_playlist_pixbuf (RBStaticPlaylistSource *source)
226 {
227 if (playlist_pixbuf == NULL) {
228 gint size;
229 gtk_icon_size_lookup (RB_SOURCE_ICON_SIZE, &size, NULL);
230 playlist_pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
231 RB_STOCK_PLAYLIST,
232 size,
233 0, NULL);
234 if (playlist_pixbuf) {
235 g_object_add_weak_pointer (playlist_pixbuf,
236 (gpointer *) &playlist_pixbuf);
237
238 g_object_set (source, "pixbuf", playlist_pixbuf, NULL);
239
240 /* drop the initial reference to the icon */
241 g_object_unref (playlist_pixbuf);
242 }
243 } else {
244 g_object_set (source, "pixbuf", playlist_pixbuf, NULL);
245 }
246 }
247
248 static void
249 rb_static_playlist_source_init (RBStaticPlaylistSource *source)
250 {
251 }
252
253 static void
254 rb_static_playlist_source_dispose (GObject *object)
255 {
256 RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (object);
257
258 if (priv->dispose_has_run) {
259 /* If dispose did already run, return. */
260 rb_debug ("Dispose has already run for static playlist source %p", object);
261 return;
262 }
263 /* Make sure dispose does not run twice. */
264 priv->dispose_has_run = TRUE;
265
266 rb_debug ("Disposing static playlist source %p", object);
267
268 if (priv->base_model != NULL) {
269 g_object_unref (priv->base_model);
270 priv->base_model = NULL;
271 }
272
273 if (priv->filter_model != NULL) {
274 g_object_unref (priv->filter_model);
275 priv->filter_model = NULL;
276 }
277
278 if (priv->default_search != NULL) {
279 g_object_unref (priv->default_search);
280 priv->default_search = NULL;
281 }
282
283 G_OBJECT_CLASS (rb_static_playlist_source_parent_class)->dispose (object);
284 }
285
286 static void
287 rb_static_playlist_source_finalize (GObject *object)
288 {
289 RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (object);
290
291 rb_debug ("Finalizing static playlist source %p", object);
292
293 if (priv->search_query != NULL) {
294 rhythmdb_query_free (priv->search_query);
295 priv->search_query = NULL;
296 }
297
298 G_OBJECT_CLASS (rb_static_playlist_source_parent_class)->finalize (object);
299 }
300
301 static void
302 rb_static_playlist_source_constructed (GObject *object)
303 {
304 RBStaticPlaylistSource *source;
305 RBStaticPlaylistSourcePrivate *priv;
306 RBPlaylistSource *psource;
307 RBEntryView *songs;
308 RBShell *shell;
309 RhythmDBEntryType *entry_type;
310 GtkUIManager *ui_manager;
311 GtkWidget *grid;
312 GtkWidget *paned;
313
314 RB_CHAIN_GOBJECT_METHOD (rb_static_playlist_source_parent_class, constructed, object);
315
316 source = RB_STATIC_PLAYLIST_SOURCE (object);
317 priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
318 psource = RB_PLAYLIST_SOURCE (source);
319
320 set_playlist_pixbuf (source);
321
322 priv->base_model = rb_playlist_source_get_query_model (RB_PLAYLIST_SOURCE (psource));
323 g_object_set (priv->base_model, "show-hidden", TRUE, NULL);
324 g_object_ref (priv->base_model);
325 g_signal_connect_object (priv->base_model,
326 "filter-entry-drop",
327 G_CALLBACK (rb_static_playlist_source_filter_entry_drop),
328 source, 0);
329
330 paned = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
331 gtk_widget_set_hexpand (paned, TRUE);
332 gtk_widget_set_vexpand (paned, TRUE);
333
334 priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH);
335
336 g_object_get (source, "shell", &shell, NULL);
337 g_object_get (shell, "ui-manager", &ui_manager, NULL);
338 g_object_unref (shell);
339
340 g_object_get (source, "entry-type", &entry_type, NULL);
341 priv->browser = rb_library_browser_new (rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source)),
342 entry_type);
343 if (entry_type != NULL) {
344 g_object_unref (entry_type);
345 }
346
347 gtk_paned_pack1 (GTK_PANED (paned), GTK_WIDGET (priv->browser), TRUE, FALSE);
348 g_signal_connect_object (priv->browser, "notify::output-model",
349 G_CALLBACK (rb_static_playlist_source_browser_changed_cb),
350 source, 0);
351
352 rb_library_browser_set_model (priv->browser, priv->base_model, FALSE);
353 rb_static_playlist_source_do_query (source);
354
355 /* reparent the entry view */
356 songs = rb_source_get_entry_view (RB_SOURCE (source));
357 g_object_ref (songs);
358 gtk_container_remove (GTK_CONTAINER (source), GTK_WIDGET (songs));
359 gtk_paned_pack2 (GTK_PANED (paned), GTK_WIDGET (songs), TRUE, FALSE);
360
361 /* set up search box / toolbar */
362 priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), ui_manager);
363 rb_source_toolbar_add_search_entry (priv->toolbar, "/StaticPlaylistSourceSearchMenu", NULL);
364 g_object_unref (ui_manager);
365
366 /* put it all together */
367 grid = gtk_grid_new ();
368 gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
369 gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
370 gtk_widget_set_margin_top (GTK_WIDGET (grid), 6);
371 gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (priv->toolbar), 0, 0, 1, 1);
372 gtk_grid_attach (GTK_GRID (grid), paned, 0, 1, 1, 1);
373 gtk_container_add (GTK_CONTAINER (source), grid);
374
375 rb_source_bind_settings (RB_SOURCE (source), GTK_WIDGET (songs), paned, GTK_WIDGET (priv->browser));
376 g_object_unref (songs);
377
378 /* watch these to find out when things are dropped into the entry view */
379 g_signal_connect_object (priv->base_model, "row-inserted",
380 G_CALLBACK (rb_static_playlist_source_row_inserted),
381 source, 0);
382 g_signal_connect_object (priv->base_model, "non-entry-dropped",
383 G_CALLBACK (rb_static_playlist_source_non_entry_dropped),
384 source, 0);
385 g_signal_connect_object (priv->base_model, "rows-reordered",
386 G_CALLBACK (rb_static_playlist_source_rows_reordered),
387 source, 0);
388
389 gtk_widget_show_all (GTK_WIDGET (source));
390 }
391
392 /**
393 * rb_static_playlist_source_new:
394 * @shell: the #RBShell
395 * @name: the playlist name
396 * @settings_name: the settings name for the playlist (GSettings path friendly)
397 * @local: if %TRUE, the playlist is local to the library
398 * @entry_type: type of database entries that can be added to the playlist.
399 *
400 * Creates a new static playlist source.
401 *
402 * Return value: new playlist.
403 */
404 RBSource *
405 rb_static_playlist_source_new (RBShell *shell, const char *name, const char *settings_name, gboolean local, RhythmDBEntryType *entry_type)
406 {
407 GSettings *settings;
408
409 if (name == NULL)
410 name = "";
411
412 if (settings_name != NULL) {
413 char *path;
414 path = g_strdup_printf ("/org/gnome/rhythmbox/playlist/%s/", settings_name);
415 settings = g_settings_new_with_path ("org.gnome.rhythmbox.source", path);
416 g_free (path);
417 } else {
418 settings = NULL;
419 }
420
421 return RB_SOURCE (g_object_new (RB_TYPE_STATIC_PLAYLIST_SOURCE,
422 "name", name,
423 "settings", settings,
424 "shell", shell,
425 "is-local", local,
426 "entry-type", entry_type,
427 "toolbar-path", "/StaticPlaylistSourceToolBar",
428 NULL));
429 }
430
431 static void
432 rb_static_playlist_source_set_property (GObject *object,
433 guint prop_id,
434 const GValue *value,
435 GParamSpec *pspec)
436 {
437 RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (object);
438
439 switch (prop_id) {
440 case PROP_SHOW_BROWSER:
441 if (g_value_get_boolean (value))
442 gtk_widget_show (GTK_WIDGET (priv->browser));
443 else
444 gtk_widget_hide (GTK_WIDGET (priv->browser));
445 break;
446 default:
447 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
448 break;
449 }
450 }
451
452 static void
453 rb_static_playlist_source_get_property (GObject *object,
454 guint prop_id,
455 GValue *value,
456 GParamSpec *pspec)
457 {
458 RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (object);
459
460 switch (prop_id) {
461 case PROP_BASE_QUERY_MODEL:
462 g_value_set_object (value, priv->base_model);
463 break;
464 case PROP_SHOW_BROWSER:
465 g_value_set_boolean (value, gtk_widget_get_visible (GTK_WIDGET (priv->browser)));
466 break;
467 default:
468 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
469 break;
470 }
471 }
472
473 /**
474 * rb_static_playlist_source_load_from_xml:
475 * @source: an #RBStaticPlaylistSource
476 * @node: XML node to load from
477 *
478 * Loads the playlist contents from the specified XML document node.
479 */
480 void
481 rb_static_playlist_source_load_from_xml (RBStaticPlaylistSource *source, xmlNodePtr node)
482 {
483 xmlNodePtr child;
484
485 for (child = node->children; child; child = child->next) {
486 xmlChar *location;
487
488 if (xmlNodeIsText (child))
489 continue;
490
491 if (xmlStrcmp (child->name, RB_PLAYLIST_LOCATION))
492 continue;
493
494 location = xmlNodeGetContent (child);
495 rb_static_playlist_source_add_location (source,
496 (char *) location, -1);
497 xmlFree (location);
498 }
499 }
500
501 /**
502 * rb_static_playlist_source_new_from_xml:
503 * @shell: the #RBShell
504 * @node: XML node containing playlist entries
505 *
506 * Constructs a new playlist from the given XML document node.
507 *
508 * Return value: playlist read from XML
509 */
510 RBSource *
511 rb_static_playlist_source_new_from_xml (RBShell *shell, xmlNodePtr node)
512 {
513 RBSource *psource = rb_static_playlist_source_new (shell,
514 NULL,
515 NULL,
516 TRUE,
517 RHYTHMDB_ENTRY_TYPE_SONG);
518 RBStaticPlaylistSource *source = RB_STATIC_PLAYLIST_SOURCE (psource);
519
520 rb_static_playlist_source_load_from_xml (source, node);
521
522 return RB_SOURCE (source);
523 }
524
525 static GList *
526 impl_cut (RBSource *asource)
527 {
528 RBStaticPlaylistSource *source = RB_STATIC_PLAYLIST_SOURCE (asource);
529 RBEntryView *songs = rb_source_get_entry_view (asource);
530 GList *sel = rb_entry_view_get_selected_entries (songs);
531 GList *tem;
532
533 for (tem = sel; tem; tem = tem->next)
534 rb_static_playlist_source_remove_entry (source, (RhythmDBEntry *) tem->data);
535
536 return sel;
537 }
538
539 static RBTrackTransferBatch *
540 impl_paste (RBSource *asource, GList *entries)
541 {
542 RBStaticPlaylistSource *source = RB_STATIC_PLAYLIST_SOURCE (asource);
543
544 for (; entries; entries = g_list_next (entries))
545 rb_static_playlist_source_add_entry (source, entries->data, -1);
546
547 return NULL;
548 }
549
550 static void
551 impl_delete (RBSource *asource)
552 {
553 RBEntryView *songs = rb_source_get_entry_view (asource);
554 RBStaticPlaylistSource *source = RB_STATIC_PLAYLIST_SOURCE (asource);
555 GList *sel, *tem;
556
557 sel = rb_entry_view_get_selected_entries (songs);
558 for (tem = sel; tem != NULL; tem = tem->next) {
559 rb_static_playlist_source_remove_entry (source, (RhythmDBEntry *) tem->data);
560 }
561 g_list_foreach (sel, (GFunc)rhythmdb_entry_unref, NULL);
562 g_list_free (sel);
563 }
564
565 static void
566 impl_reset_filters (RBSource *source)
567 {
568 RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
569 gboolean changed = FALSE;
570
571 if (rb_library_browser_reset (priv->browser))
572 changed = TRUE;
573
574 if (priv->search_query != NULL) {
575 changed = TRUE;
576 rhythmdb_query_free (priv->search_query);
577 priv->search_query = NULL;
578 }
579
580 rb_source_toolbar_clear_search_entry (priv->toolbar);
581
582 if (changed) {
583 rb_static_playlist_source_do_query (RB_STATIC_PLAYLIST_SOURCE (source));
584 rb_source_notify_filter_changed (source);
585 }
586 }
587
588 static void
589 impl_search (RBSource *source, RBSourceSearch *search, const char *cur_text, const char *new_text)
590 {
591 RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
592 RhythmDB *db;
593
594 if (search == NULL) {
595 search = priv->default_search;
596 }
597
598 /* replace our search query */
599 if (priv->search_query != NULL) {
600 rhythmdb_query_free (priv->search_query);
601 priv->search_query = NULL;
602 }
603 db = rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source));
604 priv->search_query = rb_source_search_create_query (search, db, new_text);
605
606 rb_static_playlist_source_do_query (RB_STATIC_PLAYLIST_SOURCE (source));
607 }
608
609 static GList *
610 impl_get_property_views (RBSource *source)
611 {
612 RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
613 GList *ret;
614
615 ret = rb_library_browser_get_property_views (priv->browser);
616 return ret;
617 }
618
619 static GPtrArray *
620 construct_query_from_selection (RBStaticPlaylistSource *source)
621 {
622 RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
623 RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
624 RhythmDB *db = rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (psource));
625 GPtrArray *query = NULL;
626
627 query = g_ptr_array_new();
628
629 if (priv->search_query != NULL) {
630 rhythmdb_query_append (db,
631 query,
632 RHYTHMDB_QUERY_SUBQUERY, priv->search_query,
633 RHYTHMDB_QUERY_END);
634 }
635
636 return query;
637 }
638
639 static void
640 rb_static_playlist_source_do_query (RBStaticPlaylistSource *source)
641 {
642 RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
643 RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
644 RhythmDB *db = rb_playlist_source_get_db (psource);
645 GPtrArray *query;
646
647 if (priv->filter_model != NULL) {
648 g_object_unref (priv->filter_model);
649 }
650 priv->filter_model = rhythmdb_query_model_new_empty (db);
651 g_object_set (priv->filter_model, "base-model", priv->base_model, NULL);
652
653 query = construct_query_from_selection (source);
654 g_object_set (priv->filter_model, "query", query, NULL);
655 rhythmdb_query_free (query);
656
657 rhythmdb_query_model_reapply_query (priv->filter_model, TRUE);
658 rb_library_browser_set_model (priv->browser, priv->filter_model, FALSE);
659 }
660
661 static void
662 rb_static_playlist_source_browser_changed_cb (RBLibraryBrowser *browser,
663 GParamSpec *pspec,
664 RBStaticPlaylistSource *source)
665 {
666 RBEntryView *songs = rb_source_get_entry_view (RB_SOURCE (source));
667 RhythmDBQueryModel *query_model;
668
669 g_object_get (browser, "output-model", &query_model, NULL);
670 rb_entry_view_set_model (songs, query_model);
671 rb_playlist_source_set_query_model (RB_PLAYLIST_SOURCE (source), query_model);
672 g_object_unref (query_model);
673
674 rb_source_notify_filter_changed (RB_SOURCE (source));
675 }
676
677 static gboolean
678 impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data)
679 {
680 GdkAtom type;
681 GList *list;
682 RBStaticPlaylistSource *source = RB_STATIC_PLAYLIST_SOURCE (page);
683
684 type = gtk_selection_data_get_data_type (data);
685
686 if (type == gdk_atom_intern ("text/uri-list", TRUE) ||
687 type == gdk_atom_intern ("application/x-rhythmbox-entry", TRUE)) {
688 list = rb_uri_list_parse ((char *)gtk_selection_data_get_data (data));
689 if (list == NULL)
690 return FALSE;
691
692 if (type == gdk_atom_intern ("text/uri-list", TRUE))
693 rb_static_playlist_source_add_uri_list (source, list);
694 else
695 rb_static_playlist_source_add_id_list (source, list);
696 rb_list_deep_free (list);
697 }
698
699 return TRUE;
700 }
701
702 static void
703 impl_save_contents_to_xml (RBPlaylistSource *source,
704 xmlNodePtr node)
705 {
706 RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
707 GtkTreeIter iter;
708
709 xmlSetProp (node, RB_PLAYLIST_TYPE, RB_PLAYLIST_STATIC);
710
711 if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->base_model), &iter))
712 return;
713
714 do {
715 xmlNodePtr child_node = xmlNewChild (node, NULL, RB_PLAYLIST_LOCATION, NULL);
716 RhythmDBEntry *entry;
717 xmlChar *encoded;
718 const char *location;
719
720 gtk_tree_model_get (GTK_TREE_MODEL (priv->base_model), &iter, 0, &entry, -1);
721
722 location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
723 encoded = xmlEncodeEntitiesReentrant (NULL, BAD_CAST location);
724
725 xmlNodeSetContent (child_node, encoded);
726
727 g_free (encoded);
728 rhythmdb_entry_unref (entry);
729 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->base_model), &iter));
730 }
731
732 static void
733 rb_static_playlist_source_add_id_list (RBStaticPlaylistSource *source,
734 GList *list)
735 {
736 RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
737 GList *i;
738 gint id;
739
740 g_return_if_fail (list != NULL);
741
742 for (i = list; i != NULL; i = i->next) {
743 RhythmDBEntry *entry;
744
745 id = strtoul ((const char *)i->data, NULL, 0);
746 if (id == 0)
747 continue;
748
749 entry = rhythmdb_entry_lookup_by_id (rb_playlist_source_get_db (psource), id);
750 if (entry == NULL) {
751 rb_debug ("received id %d, but can't find the entry", id);
752 continue;
753 }
754
755 rb_static_playlist_source_add_entry (source, entry, -1);
756 }
757 }
758
759 static void
760 rb_static_playlist_source_add_uri_list (RBStaticPlaylistSource *source,
761 GList *list)
762 {
763 GList *i, *uri_list = NULL;
764 RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
765 RhythmDBEntry *entry;
766
767 g_return_if_fail (list != NULL);
768
769 for (i = list; i != NULL; i = g_list_next (i)) {
770 char *uri = (char *) i->data;
771 uri_list = g_list_prepend (uri_list, rb_canonicalise_uri (uri));
772 }
773
774 uri_list = g_list_reverse (uri_list);
775 if (uri_list == NULL)
776 return;
777
778 for (i = uri_list; i != NULL; i = i->next) {
779 char *uri = i->data;
780 if (uri != NULL) {
781 entry = rhythmdb_entry_lookup_by_location (rb_playlist_source_get_db (psource), uri);
782 if (entry == NULL)
783 rhythmdb_add_uri (rb_playlist_source_get_db (psource), uri);
784
785 rb_static_playlist_source_add_location (source, uri, -1);
786 }
787
788 g_free (uri);
789 }
790 g_list_free (uri_list);
791 }
792
793 static void
794 rb_static_playlist_source_add_location_internal (RBStaticPlaylistSource *source,
795 const char *location,
796 gint index)
797 {
798 RhythmDB *db;
799 RhythmDBEntry *entry;
800 RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
801 if (rb_playlist_source_location_in_map (psource, location))
802 return;
803
804 db = rb_playlist_source_get_db (psource);
805 entry = rhythmdb_entry_lookup_by_location (db, location);
806 if (entry) {
807 RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
808
809 if (_rb_source_check_entry_type (RB_SOURCE (source), entry)) {
810 rhythmdb_entry_ref (entry);
811 rhythmdb_query_model_add_entry (priv->base_model, entry, index);
812 rhythmdb_entry_unref (entry);
813 }
814 }
815
816 rb_playlist_source_add_to_map (psource, location);
817
818 rb_playlist_source_mark_dirty (psource);
819 }
820
821 static gboolean
822 _add_location_cb (GFile *file,
823 gboolean dir,
824 RBStaticPlaylistSource *source)
825 {
826 if (!dir) {
827 char *uri;
828
829 uri = g_file_get_uri (file);
830 rb_static_playlist_source_add_location_internal (source, uri, -1);
831 g_free (uri);
832 }
833 return TRUE;
834 }
835
836 /**
837 * rb_static_playlist_source_add_location:
838 * @source: an #RBStaticPlaylistSource
839 * @location: location (URI) to add to the playlist
840 * @index: position at which to add the location (-1 to add at the end)
841 *
842 * If the location matches an entry in the database, the entry is added
843 * to the playlist. Otherwise, if it identifies a directory, the contents
844 * of that directory are added.
845 */
846 void
847 rb_static_playlist_source_add_location (RBStaticPlaylistSource *source,
848 const char *location,
849 gint index)
850 {
851 RhythmDB *db;
852 RhythmDBEntry *entry;
853
854 db = rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source));
855 entry = rhythmdb_entry_lookup_by_location (db, location);
856
857 /* if there is an entry, it won't be a directory */
858 if (entry == NULL && rb_uri_is_directory (location))
859 rb_uri_handle_recursively (location,
860 NULL,
861 (RBUriRecurseFunc) _add_location_cb,
862 source);
863 else
864 rb_static_playlist_source_add_location_internal (source, location, index);
865
866 }
867
868 /**
869 * rb_static_playlist_source_add_locations:
870 * @source: an #RBStaticPlaylistSource
871 * @locations: (element-type utf8) (transfer none): URI strings to add
872 *
873 * Adds the locations specified in @locations to the playlist.
874 * See @rb_static_playlist_source_add_location for details.
875 */
876 void
877 rb_static_playlist_source_add_locations (RBStaticPlaylistSource *source,
878 GList *locations)
879 {
880 GList *l;
881
882 for (l = locations; l; l = l->next) {
883 const gchar *uri = (const gchar *)l->data;
884 rb_static_playlist_source_add_location (source, uri, -1);
885 }
886 }
887
888 /**
889 * rb_static_playlist_source_remove_location:
890 * @source: an #RBStaticPlaylistSource
891 * @location: location to remove
892 *
893 * Removes the specified location from the playlist. This affects both
894 * the location map and the query model, whether an entry exists for the
895 * location or not.
896 */
897 void
898 rb_static_playlist_source_remove_location (RBStaticPlaylistSource *source,
899 const char *location)
900 {
901 RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
902 RhythmDB *db;
903 RhythmDBEntry *entry;
904
905 g_return_if_fail (rb_playlist_source_location_in_map (psource, location));
906
907 db = rb_playlist_source_get_db (psource);
908 entry = rhythmdb_entry_lookup_by_location (db, location);
909
910 if (entry != NULL) {
911 RhythmDBQueryModel *model = rb_playlist_source_get_query_model (psource);
912
913 /* if this fails, the model and the playlist are out of sync */
914 g_assert (rhythmdb_query_model_remove_entry (model, entry));
915 rb_playlist_source_mark_dirty (psource);
916 }
917 }
918
919 /**
920 * rb_static_playlist_source_add_entry:
921 * @source: an #RBStaticPlaylistSource
922 * @entry: entry to add to the playlist
923 * @index: position at which to add it (-1 to add at the end)
924 *
925 * Adds the specified entry to the playlist.
926 */
927 void
928 rb_static_playlist_source_add_entry (RBStaticPlaylistSource *source,
929 RhythmDBEntry *entry,
930 gint index)
931 {
932 const char *location;
933
934 location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
935 rb_static_playlist_source_add_location_internal (source, location, index);
936 }
937
938 /**
939 * rb_static_playlist_source_remove_entry:
940 * @source: an #RBStaticPlaylistSource
941 * @entry: the entry to remove
942 *
943 * Removes the specified entry from the playlist.
944 */
945 void
946 rb_static_playlist_source_remove_entry (RBStaticPlaylistSource *source,
947 RhythmDBEntry *entry)
948 {
949 const char *location;
950
951 location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
952 rb_static_playlist_source_remove_location (source, location);
953 }
954
955 /**
956 * rb_static_playlist_source_move_entry:
957 * @source: an #RBStaticPlaylistSource
958 * @entry: the entry to move
959 * @index: new location for the entry
960 *
961 * Moves an entry within the playlist.
962 */
963 void
964 rb_static_playlist_source_move_entry (RBStaticPlaylistSource *source,
965 RhythmDBEntry *entry,
966 gint index)
967 {
968 RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
969 RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
970
971 rhythmdb_query_model_move_entry (priv->base_model, entry, index);
972
973 rb_playlist_source_mark_dirty (psource);
974 }
975
976 static void
977 rb_static_playlist_source_non_entry_dropped (GtkTreeModel *model,
978 const char *uri,
979 int position,
980 RBStaticPlaylistSource *source)
981 {
982 g_assert (g_utf8_strlen (uri, -1) > 0);
983
984 rhythmdb_add_uri (rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source)), uri);
985 rb_static_playlist_source_add_location (source, uri, position);
986 }
987
988 static void
989 rb_static_playlist_source_row_inserted (GtkTreeModel *model,
990 GtkTreePath *path,
991 GtkTreeIter *iter,
992 RBStaticPlaylistSource *source)
993 {
994 RhythmDBEntry *entry;
995
996 gtk_tree_model_get (model, iter, 0, &entry, -1);
997
998 rb_static_playlist_source_add_entry (source, entry, -1);
999
1000 rhythmdb_entry_unref (entry);
1001 }
1002
1003 static void
1004 rb_static_playlist_source_rows_reordered (GtkTreeModel *model,
1005 GtkTreePath *path,
1006 GtkTreeIter *iter,
1007 gint *order_map,
1008 RBStaticPlaylistSource *source)
1009 {
1010 rb_playlist_source_mark_dirty (RB_PLAYLIST_SOURCE (source));
1011 }
1012
1013 static gboolean
1014 rb_static_playlist_source_filter_entry_drop (RhythmDBQueryModel *model,
1015 RhythmDBEntry *entry,
1016 RBStaticPlaylistSource *source)
1017 {
1018 if (_rb_source_check_entry_type (RB_SOURCE (source), entry)) {
1019 rb_debug ("allowing drop of entry %s", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
1020 return TRUE;
1021 }
1022 rb_debug ("preventing drop of entry %s", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
1023 return FALSE;
1024 }
1025
1026 static guint
1027 impl_want_uri (RBSource *source, const char *uri)
1028 {
1029 /* take anything local, on smb, or sftp */
1030 if (rb_uri_is_local (uri) ||
1031 g_str_has_prefix (uri, "smb://") ||
1032 g_str_has_prefix (uri, "sftp://"))
1033 return 25; /* less than what the library returns */
1034
1035 return 0;
1036 }