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 #include "config.h"
31
32 #include <time.h>
33 #include <string.h>
34
35 #include <glib/gi18n.h>
36 #include <gtk/gtk.h>
37
38 #include "rb-source.h"
39 #include "rb-cut-and-paste-code.h"
40 #include "rb-debug.h"
41 #include "rb-dialog.h"
42 #include "rb-shell.h"
43 #include "rb-source.h"
44 #include "rb-util.h"
45 #include "rb-static-playlist-source.h"
46 #include "rb-play-order.h"
47
48 static void rb_source_class_init (RBSourceClass *klass);
49 static void rb_source_init (RBSource *source);
50 static void rb_source_dispose (GObject *object);
51 static void rb_source_finalize (GObject *object);
52 static void rb_source_set_property (GObject *object,
53 guint prop_id,
54 const GValue *value,
55 GParamSpec *pspec);
56 static void rb_source_get_property (GObject *object,
57 guint prop_id,
58 GValue *value,
59 GParamSpec *pspec);
60
61 static void default_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress);
62 static void default_activate (RBDisplayPage *page);
63 static GList *default_get_property_views (RBSource *source);
64 static gboolean default_can_rename (RBSource *source);
65 static GList *default_copy (RBSource *source);
66 static void default_reset_filters (RBSource *source);
67 static gboolean default_try_playlist (RBSource *source);
68 static RBSourceEOFType default_handle_eos (RBSource *source);
69 static RBEntryView *default_get_entry_view (RBSource *source);
70 static void default_add_to_queue (RBSource *source, RBSource *queue);
71 static void default_move_to_trash (RBSource *source);
72 static char *default_get_delete_action (RBSource *source);
73
74 static void rb_source_post_entry_deleted_cb (GtkTreeModel *model,
75 RhythmDBEntry *entry,
76 RBSource *source);
77 static void rb_source_row_inserted_cb (GtkTreeModel *model,
78 GtkTreePath *path,
79 GtkTreeIter *iter,
80 RBSource *source);
81
82 G_DEFINE_ABSTRACT_TYPE (RBSource, rb_source, RB_TYPE_DISPLAY_PAGE)
83
84 /**
85 * SECTION:rb-source
86 * @short_description: base class for sources
87 *
88 * This class provides methods for requesting information
89 * about the UI capabilities of the source, and defines the
90 * expectations that apply to all sources - that they will
91 * provide #RBEntryView and #RhythmDBQueryModel objects, mostly.
92 *
93 * Many of the methods on this class come in can_do_x and do_x
94 * pairs. When can_do_x always returns FALSE, the class does not
95 * need to implement the do_x method.
96 *
97 * Useful subclasses include #RBBrowserSource, which includes a #RBLibraryBrowser
98 * and takes care of constructing an #RBEntryView too; #RBRemovableMediaSource,
99 * which takes care of many aspects of implementing a source that represents a
100 * removable device; and #RBPlaylistSource, which provides functionality for
101 * playlist-like sources.
102 */
103
104 struct _RBSourcePrivate
105 {
106 RhythmDBQueryModel *query_model;
107 guint hidden_when_empty : 1;
108 guint update_visibility_id;
109 guint update_status_id;
110 RhythmDBEntryType *entry_type;
111 RBSourceLoadStatus load_status;
112
113 GSettings *settings;
114
115 char *toolbar_path;
116 };
117
118 enum
119 {
120 PROP_0,
121 PROP_QUERY_MODEL,
122 PROP_HIDDEN_WHEN_EMPTY,
123 PROP_ENTRY_TYPE,
124 PROP_BASE_QUERY_MODEL,
125 PROP_PLAY_ORDER,
126 PROP_SETTINGS,
127 PROP_SHOW_BROWSER,
128 PROP_LOAD_STATUS,
129 PROP_TOOLBAR_PATH
130 };
131
132 enum
133 {
134 FILTER_CHANGED,
135 LAST_SIGNAL
136 };
137
138 static guint rb_source_signals[LAST_SIGNAL] = { 0 };
139
140 static void
141 rb_source_class_init (RBSourceClass *klass)
142 {
143 GObjectClass *object_class = G_OBJECT_CLASS (klass);
144 RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
145
146 object_class->dispose = rb_source_dispose;
147 object_class->finalize = rb_source_finalize;
148 object_class->set_property = rb_source_set_property;
149 object_class->get_property = rb_source_get_property;
150
151 page_class->activate = default_activate;
152 page_class->get_status = default_get_status;
153
154 klass->impl_get_property_views = default_get_property_views;
155 klass->impl_can_rename = default_can_rename;
156 klass->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
157 klass->impl_can_paste = (RBSourceFeatureFunc) rb_false_function;
158 klass->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;
159 klass->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
160 klass->impl_can_add_to_queue = (RBSourceFeatureFunc) rb_false_function;
161 klass->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
162 klass->impl_can_pause = (RBSourceFeatureFunc) rb_true_function;
163 klass->impl_get_entry_view = default_get_entry_view;
164 klass->impl_copy = default_copy;
165 klass->impl_reset_filters = default_reset_filters;
166 klass->impl_handle_eos = default_handle_eos;
167 klass->impl_try_playlist = default_try_playlist;
168 klass->impl_add_to_queue = default_add_to_queue;
169 klass->impl_get_delete_action = default_get_delete_action;
170 klass->impl_move_to_trash = default_move_to_trash;
171
172 /**
173 * RBSource:hidden-when-empty:
174 *
175 * If TRUE, the source will not be displayed in the source list
176 * when it contains no entries.
177 */
178 g_object_class_install_property (object_class,
179 PROP_HIDDEN_WHEN_EMPTY,
180 g_param_spec_boolean ("hidden-when-empty",
181 "hidden-when-empty",
182 "Whether the source should be displayed in the source list when it is empty",
183 FALSE,
184 G_PARAM_READWRITE));
185
186 /**
187 * RBSource:query-model:
188 *
189 * The current query model for the source. This is used in
190 * various places, including the play order, to find the
191 * set of entries within the source.
192 */
193 g_object_class_install_property (object_class,
194 PROP_QUERY_MODEL,
195 g_param_spec_object ("query-model",
196 "RhythmDBQueryModel",
197 "RhythmDBQueryModel object",
198 RHYTHMDB_TYPE_QUERY_MODEL,
199 G_PARAM_READWRITE));
200 /**
201 * RBSource:entry-type:
202 *
203 * Entry type for entries in this source.
204 */
205 g_object_class_install_property (object_class,
206 PROP_ENTRY_TYPE,
207 g_param_spec_object ("entry-type",
208 "Entry type",
209 "Type of the entries which should be displayed by this source",
210 RHYTHMDB_TYPE_ENTRY_TYPE,
211 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
212 /**
213 * RBSource:base-query-model:
214 *
215 * The unfiltered query model for the source, containing all entries in the source.
216 * Source classes should override this if they perform filtering based on the search
217 * box or a browser.
218 */
219 g_object_class_install_property (object_class,
220 PROP_BASE_QUERY_MODEL,
221 g_param_spec_object ("base-query-model",
222 "RhythmDBQueryModel",
223 "RhythmDBQueryModel object (unfiltered)",
224 RHYTHMDB_TYPE_QUERY_MODEL,
225 G_PARAM_READABLE));
226 /**
227 * RBSource:play-order:
228 *
229 * If the source provides its own play order, it can override this property.
230 */
231 g_object_class_install_property (object_class,
232 PROP_PLAY_ORDER,
233 g_param_spec_object ("play-order",
234 "play order",
235 "optional play order specific to the source",
236 RB_TYPE_PLAY_ORDER,
237 G_PARAM_READABLE));
238
239 /**
240 * RBSource:load-status:
241 *
242 * Indicates whether the source is not loaded, is currently loading data, or is
243 * fully loaded.
244 */
245 g_object_class_install_property (object_class,
246 PROP_LOAD_STATUS,
247 g_param_spec_enum ("load-status",
248 "load-status",
249 "load status",
250 RB_TYPE_SOURCE_LOAD_STATUS,
251 RB_SOURCE_LOAD_STATUS_LOADED,
252 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
253
254 /**
255 * RBSource:settings:
256 *
257 * The #GSettings instance storing settings for the source. The instance must
258 * have a schema of org.gnome.Rhythmbox.Source.
259 */
260 g_object_class_install_property (object_class,
261 PROP_SETTINGS,
262 g_param_spec_object ("settings",
263 "settings",
264 "GSettings instance",
265 G_TYPE_SETTINGS,
266 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
267 /**
268 * RBSource:show-browser:
269 *
270 * Whether the browser widget for the source (if any) should be displayed.
271 * This should be overridden in sources that include a browser widget.
272 */
273 g_object_class_install_property (object_class,
274 PROP_SHOW_BROWSER,
275 g_param_spec_boolean ("show-browser",
276 "show browser",
277 "whether the browser widget should be shown",
278 TRUE,
279 G_PARAM_READWRITE));
280 /**
281 * RBSource:toolbar-path:
282 *
283 * UI manager path for a toolbar to display at the top of the source.
284 * The #RBSource class doesn't actually display the toolbar anywhere.
285 * Adding the toolbar to a container is the responsibility of a subclass
286 * such as #RBBrowserSource.
287 */
288 g_object_class_install_property (object_class,
289 PROP_TOOLBAR_PATH,
290 g_param_spec_string ("toolbar-path",
291 "toolbar path",
292 "toolbar UI path",
293 NULL,
294 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
295
296 /**
297 * RBSource::filter-changed:
298 * @source: the #RBSource
299 *
300 * Fires when the user changes the filter, either by changing the
301 * contents of the search box or by selecting a different browser
302 * entry.
303 */
304 rb_source_signals[FILTER_CHANGED] =
305 g_signal_new ("filter_changed",
306 RB_TYPE_SOURCE,
307 G_SIGNAL_RUN_LAST,
308 G_STRUCT_OFFSET (RBSourceClass, filter_changed),
309 NULL, NULL,
310 g_cclosure_marshal_VOID__VOID,
311 G_TYPE_NONE,
312 0);
313
314 g_type_class_add_private (object_class, sizeof (RBSourcePrivate));
315 }
316
317 static void
318 rb_source_init (RBSource *source)
319 {
320 source->priv = G_TYPE_INSTANCE_GET_PRIVATE (source, RB_TYPE_SOURCE, RBSourcePrivate);
321 }
322
323 static void
324 rb_source_dispose (GObject *object)
325 {
326 RBSource *source;
327
328 g_return_if_fail (object != NULL);
329 g_return_if_fail (RB_IS_SOURCE (object));
330
331 source = RB_SOURCE (object);
332
333 if (source->priv->update_visibility_id != 0) {
334 g_source_remove (source->priv->update_visibility_id);
335 source->priv->update_visibility_id = 0;
336 }
337 if (source->priv->update_status_id != 0) {
338 g_source_remove (source->priv->update_status_id);
339 source->priv->update_status_id = 0;
340 }
341 if (source->priv->settings != NULL) {
342 g_object_unref (source->priv->settings);
343 source->priv->settings = NULL;
344 }
345
346 G_OBJECT_CLASS (rb_source_parent_class)->dispose (object);
347 }
348
349 static void
350 rb_source_finalize (GObject *object)
351 {
352 RBSource *source;
353
354 g_return_if_fail (object != NULL);
355 g_return_if_fail (RB_IS_SOURCE (object));
356
357 source = RB_SOURCE (object);
358
359 if (source->priv->query_model != NULL) {
360 rb_debug ("Unreffing model %p count: %d",
361 source->priv->query_model,
362 G_OBJECT (source->priv->query_model)->ref_count);
363 g_object_unref (source->priv->query_model);
364 }
365
366 g_free (source->priv->toolbar_path);
367
368 G_OBJECT_CLASS (rb_source_parent_class)->finalize (object);
369 }
370
371 static gboolean
372 update_visibility_idle (RBSource *source)
373 {
374 gint count;
375
376 GDK_THREADS_ENTER ();
377
378 count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (source->priv->query_model), NULL);
379 g_object_set (source, "visibility", (count > 0), NULL);
380
381 source->priv->update_visibility_id = 0;
382 GDK_THREADS_LEAVE ();
383 return FALSE;
384 }
385
386 static void
387 queue_update_visibility (RBSource *source)
388 {
389 if (source->priv->update_visibility_id != 0) {
390 g_source_remove (source->priv->update_visibility_id);
391 }
392 source->priv->update_visibility_id = g_idle_add ((GSourceFunc) update_visibility_idle, source);
393 }
394
395 /**
396 * rb_source_set_hidden_when_empty:
397 * @source: a #RBSource
398 * @hidden: if TRUE, automatically hide the source
399 *
400 * Enables or disables automatic hiding of the source when
401 * there are no entries in it.
402 */
403 void
404 rb_source_set_hidden_when_empty (RBSource *source,
405 gboolean hidden)
406 {
407 g_return_if_fail (RB_IS_SOURCE (source));
408
409 if (source->priv->hidden_when_empty != hidden) {
410 source->priv->hidden_when_empty = hidden;
411 queue_update_visibility (source);
412 }
413 }
414
415 static void
416 rb_source_set_query_model_internal (RBSource *source,
417 RhythmDBQueryModel *model)
418 {
419 if (source->priv->query_model == model) {
420 return;
421 }
422
423 if (source->priv->query_model != NULL) {
424 g_signal_handlers_disconnect_by_func (source->priv->query_model,
425 G_CALLBACK (rb_source_post_entry_deleted_cb),
426 source);
427 g_signal_handlers_disconnect_by_func (source->priv->query_model,
428 G_CALLBACK (rb_source_row_inserted_cb),
429 source);
430 g_object_unref (source->priv->query_model);
431 }
432
433 source->priv->query_model = model;
434 if (source->priv->query_model != NULL) {
435 g_object_ref (source->priv->query_model);
436 g_signal_connect_object (model, "post-entry-delete",
437 G_CALLBACK (rb_source_post_entry_deleted_cb),
438 source, 0);
439 g_signal_connect_object (model, "row_inserted",
440 G_CALLBACK (rb_source_row_inserted_cb),
441 source, 0);
442 }
443
444 rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
445 }
446
447 static void
448 rb_source_set_property (GObject *object,
449 guint prop_id,
450 const GValue *value,
451 GParamSpec *pspec)
452 {
453 RBSource *source = RB_SOURCE (object);
454
455 switch (prop_id) {
456 case PROP_HIDDEN_WHEN_EMPTY:
457 rb_source_set_hidden_when_empty (source, g_value_get_boolean (value));
458 break;
459 case PROP_QUERY_MODEL:
460 rb_source_set_query_model_internal (source, g_value_get_object (value));
461 break;
462 case PROP_ENTRY_TYPE:
463 source->priv->entry_type = g_value_get_object (value);
464 break;
465 case PROP_SETTINGS:
466 source->priv->settings = g_value_dup_object (value);
467 break;
468 case PROP_SHOW_BROWSER:
469 /* not connected to anything here */
470 break;
471 case PROP_LOAD_STATUS:
472 source->priv->load_status = g_value_get_enum (value);
473 break;
474 case PROP_TOOLBAR_PATH:
475 source->priv->toolbar_path = g_value_dup_string (value);
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_source_get_property (GObject *object,
485 guint prop_id,
486 GValue *value,
487 GParamSpec *pspec)
488 {
489 RBSource *source = RB_SOURCE (object);
490
491 switch (prop_id) {
492 case PROP_QUERY_MODEL:
493 g_value_set_object (value, source->priv->query_model);
494 break;
495 case PROP_ENTRY_TYPE:
496 g_value_set_object (value, source->priv->entry_type);
497 break;
498 case PROP_BASE_QUERY_MODEL:
499 /* unless the subclass overrides it, just assume the
500 * current query model is the base model.
501 */
502 g_value_set_object (value, source->priv->query_model);
503 break;
504 case PROP_PLAY_ORDER:
505 g_value_set_object (value, NULL); /* ? */
506 break;
507 case PROP_SETTINGS:
508 g_value_set_object (value, source->priv->settings);
509 break;
510 case PROP_SHOW_BROWSER:
511 g_value_set_boolean (value, FALSE);
512 break;
513 case PROP_LOAD_STATUS:
514 g_value_set_enum (value, source->priv->load_status);
515 break;
516 case PROP_TOOLBAR_PATH:
517 g_value_set_string (value, source->priv->toolbar_path);
518 break;
519 default:
520 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
521 break;
522 }
523 }
524
525 static void
526 default_activate (RBDisplayPage *page)
527 {
528 RBShell *shell;
529
530 g_object_get (page, "shell", &shell, NULL);
531 rb_shell_activate_source (shell,
532 RB_SOURCE (page),
533 RB_SHELL_ACTIVATION_ALWAYS_PLAY,
534 NULL);
535 }
536
537 static void
538 default_get_status (RBDisplayPage *page,
539 char **text,
540 char **progress_text,
541 float *progress)
542 {
543 RBSource *source = RB_SOURCE (page);
544 /* hack to get these strings marked for translation */
545 if (0) {
546 ngettext ("%d song", "%d songs", 0);
547 }
548
549 if (source->priv->query_model) {
550 *text = rhythmdb_query_model_compute_status_normal (source->priv->query_model,
551 "%d song",
552 "%d songs");
553 if (rhythmdb_query_model_has_pending_changes (source->priv->query_model)) {
554 *progress = -1.0f;
555 }
556 } else {
557 *text = g_strdup ("");
558 }
559 }
560
561 /**
562 * rb_source_notify_filter_changed:
563 * @source: a #RBSource
564 *
565 * Source implementations call this when their filter state changes
566 */
567 void
568 rb_source_notify_filter_changed (RBSource *source)
569 {
570 g_signal_emit (G_OBJECT (source), rb_source_signals[FILTER_CHANGED], 0);
571 }
572
573 /**
574 * rb_source_update_play_statistics:
575 * @source: a #RBSource
576 * @db: the #RhythmDB instance
577 * @entry: the #RhythmDBEntry to update
578 *
579 * Updates play count and play time statistics for a database entry.
580 * Sources containing entries that do not normally reach EOS should
581 * call this for an entry when it is no longer being played.
582 */
583 void
584 rb_source_update_play_statistics (RBSource *source,
585 RhythmDB *db,
586 RhythmDBEntry *entry)
587 {
588 time_t now;
589 gulong current_count;
590 GValue value = { 0, };
591
592 g_value_init (&value, G_TYPE_ULONG);
593
594 current_count = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_PLAY_COUNT);
595
596 g_value_set_ulong (&value, current_count + 1);
597
598 /* Increment current play count */
599 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAY_COUNT, &value);
600 g_value_unset (&value);
601
602 /* Reset the last played time */
603 time (&now);
604
605 g_value_init (&value, G_TYPE_ULONG);
606 g_value_set_ulong (&value, now);
607 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LAST_PLAYED, &value);
608 g_value_unset (&value);
609
610 rhythmdb_commit (db);
611 }
612
613 /**
614 * rb_source_get_entry_view:
615 * @source: a #RBSource
616 *
617 * Returns the entry view widget for the source.
618 *
619 * Return value: (transfer none): the #RBEntryView instance for the source
620 */
621 RBEntryView *
622 rb_source_get_entry_view (RBSource *source)
623 {
624 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
625
626 return klass->impl_get_entry_view (source);
627 }
628
629 static GList *
630 default_get_property_views (RBSource *source)
631 {
632 return NULL;
633 }
634
635 /**
636 * rb_source_get_property_views:
637 * @source: a #RBSource
638 *
639 * Returns a list containing the #RBPropertyView instances for the
640 * source, if any.
641 *
642 * Return value: (element-type RB.PropertyView) (transfer container): list of property views
643 */
644 GList *
645 rb_source_get_property_views (RBSource *source)
646 {
647 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
648
649 return klass->impl_get_property_views (source);
650 }
651
652 static gboolean
653 default_can_rename (RBSource *source)
654 {
655 return FALSE;
656 }
657
658 static gboolean
659 is_party_mode (RBSource *source)
660 {
661 gboolean result = FALSE;
662 RBShell *shell;
663
664 g_object_get (source, "shell", &shell, NULL);
665 result = rb_shell_get_party_mode (shell);
666 g_object_unref (shell);
667
668 return result;
669 }
670
671 /**
672 * rb_source_can_rename:
673 * @source: a #RBSource.
674 *
675 * Determines whether the source can be renamed.
676 *
677 * Return value: TRUE if this source can be renamed
678 */
679 gboolean
680 rb_source_can_rename (RBSource *source)
681 {
682 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
683
684 if (is_party_mode (source)) {
685 return FALSE;
686 } else {
687 return klass->impl_can_rename (source);
688 }
689 }
690
691 /**
692 * rb_source_search:
693 * @source: a #RBSource
694 * @search: (allow-none): the active #RBSourceSearch instance
695 * @cur_text: (allow-none): the current search text
696 * @new_text: the new search text
697 *
698 * Updates the source with new search text. The source
699 * should recreate the database query that feeds into the
700 * browser (if any).
701 */
702 void
703 rb_source_search (RBSource *source,
704 RBSourceSearch *search,
705 const char *cur_text,
706 const char *new_text)
707 {
708 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
709 g_assert (new_text != NULL);
710
711 if (klass->impl_search != NULL)
712 klass->impl_search (source, search, cur_text, new_text);
713 }
714
715
716 /**
717 * rb_source_can_cut:
718 * @source: a #RBSource
719 *
720 * Determines whether the source supporst the typical cut
721 * (as in cut-and-paste) operation.
722 *
723 * Return value: TRUE if cutting is supported
724 */
725 gboolean
726 rb_source_can_cut (RBSource *source)
727 {
728 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
729
730 return klass->impl_can_cut (source);
731 }
732
733 /**
734 * rb_source_can_paste:
735 * @source: a #RBSource
736 *
737 * Determines whether the source supports paste operations.
738 *
739 * Return value: TRUE if the pasting is supported
740 */
741 gboolean
742 rb_source_can_paste (RBSource *source)
743 {
744 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
745
746 return klass->impl_can_paste (source);
747 }
748
749 /**
750 * rb_source_can_delete:
751 * @source: a #RBSource
752 *
753 * Determines whether the source allows the user to delete
754 * a selected set of entries.
755 *
756 * Return value: TRUE if deletion is supported
757 */
758 gboolean
759 rb_source_can_delete (RBSource *source)
760 {
761 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
762 if (is_party_mode (source)) {
763 return FALSE;
764 } else {
765 return klass->impl_can_delete (source);
766 }
767 }
768
769 /**
770 * rb_source_can_move_to_trash:
771 * @source: a #RBSource
772 *
773 * Determines whether the source allows the user to trash
774 * the files backing a selected set of entries.
775 *
776 * Return value: TRUE if trashing is supported
777 */
778 gboolean
779 rb_source_can_move_to_trash (RBSource *source)
780 {
781 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
782 if (is_party_mode (source)) {
783 return FALSE;
784 } else {
785 return klass->impl_can_move_to_trash (source);
786 }
787 }
788
789 /**
790 * rb_source_can_copy:
791 * @source: a #RBSource
792 *
793 * Determines whether the source supports the copy part
794 * of a copy-and-paste operation.
795 *
796 * Return value: TRUE if copying is supported
797 */
798 gboolean
799 rb_source_can_copy (RBSource *source)
800 {
801 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
802
803 return klass->impl_can_copy (source);
804 }
805
806 /**
807 * rb_source_cut:
808 * @source: a #RBSource
809 *
810 * Removes the currently selected entries from the source and
811 * returns them so they can be pasted into another source.
812 *
813 * Return value: (element-type RB.RhythmDBEntry) (transfer full): entries cut
814 * from the source.
815 */
816 GList *
817 rb_source_cut (RBSource *source)
818 {
819 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
820
821 return klass->impl_cut (source);
822 }
823
824 static GList *
825 default_copy (RBSource *source)
826 {
827 RBEntryView *entry_view;
828 entry_view = rb_source_get_entry_view (source);
829 if (entry_view == NULL)
830 return NULL;
831
832 return rb_entry_view_get_selected_entries (entry_view);
833 }
834
835 /**
836 * rb_source_copy:
837 * @source: a #RBSource
838 *
839 * Copies the selected entries to the clipboard.
840 *
841 * Return value: (element-type RB.RhythmDBEntry) (transfer full): a list containing
842 * the currently selected entries from the source.
843 */
844 GList *
845 rb_source_copy (RBSource *source)
846 {
847 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
848
849 return klass->impl_copy (source);
850 }
851
852 /**
853 * rb_source_paste:
854 * @source: a #RBSource
855 * @entries: (element-type RB.RhythmDBEntry): a list of #RhythmDBEntry objects to paste in
856 *
857 * Adds a list of entries previously cut or copied from another
858 * source. If the entries are not of the type used by the source,
859 * the entries will be copied and possibly converted into an acceptable format.
860 * This can be used for transfers to and from devices and network shares.
861 *
862 * If the transfer is performed using an #RBTrackTransferBatch, the batch object
863 * is returned so the caller can monitor the transfer progress. The caller does not
864 * own a reference on the batch object.
865 *
866 * Return value: (transfer none): the #RBTrackTransferBatch used to perform the transfer (if any)
867 */
868 RBTrackTransferBatch *
869 rb_source_paste (RBSource *source, GList *entries)
870 {
871 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
872
873 return klass->impl_paste (source, entries);
874 }
875
876 /**
877 * rb_source_can_add_to_queue:
878 * @source: a #RBSource
879 *
880 * Determines whether the source can add the selected entries to
881 * the play queue.
882 *
883 * Return value: TRUE if adding to the play queue is supported
884 */
885 gboolean
886 rb_source_can_add_to_queue (RBSource *source)
887 {
888 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
889 return klass->impl_can_add_to_queue (source);
890 }
891
892 static void
893 default_add_to_queue (RBSource *source,
894 RBSource *queue)
895 {
896 RBEntryView *songs;
897 GList *selection;
898 GList *iter;
899
900 songs = rb_source_get_entry_view (source);
901 if (songs == NULL)
902 return;
903
904 selection = rb_entry_view_get_selected_entries (songs);
905 if (selection == NULL)
906 return;
907
908 for (iter = selection; iter; iter = iter->next) {
909 rb_static_playlist_source_add_entry (RB_STATIC_PLAYLIST_SOURCE (queue),
910 (RhythmDBEntry *)iter->data, -1);
911 }
912
913 g_list_foreach (selection, (GFunc)rhythmdb_entry_unref, NULL);
914 g_list_free (selection);
915 }
916
917 /**
918 * rb_source_add_to_queue:
919 * @source: a #RBSource
920 * @queue: the #RBSource for the play queue
921 *
922 * Adds the currently selected entries to the end of the
923 * play queue.
924 */
925 void
926 rb_source_add_to_queue (RBSource *source,
927 RBSource *queue)
928 {
929 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
930 klass->impl_add_to_queue (source, queue);
931 }
932
933 /**
934 * rb_source_delete:
935 * @source: a #RBSource
936 *
937 * Deletes the currently selected entries from the source.
938 */
939 void
940 rb_source_delete (RBSource *source)
941 {
942 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
943
944 klass->impl_delete (source);
945 }
946
947 static void
948 default_move_to_trash (RBSource *source)
949 {
950 GList *sel, *tem;
951 RBEntryView *entry_view;
952 RhythmDB *db;
953
954 g_object_get (source->priv->query_model, "db", &db, NULL);
955
956 sel = NULL;
957 entry_view = rb_source_get_entry_view (source);
958 if (entry_view != NULL) {
959 sel = rb_entry_view_get_selected_entries (entry_view);
960 }
961
962 for (tem = sel; tem != NULL; tem = tem->next) {
963 rhythmdb_entry_move_to_trash (db, (RhythmDBEntry *)tem->data);
964 rhythmdb_commit (db);
965 }
966
967 g_list_foreach (sel, (GFunc)rhythmdb_entry_unref, NULL);
968 g_list_free (sel);
969 g_object_unref (db);
970 }
971
972 /**
973 * rb_source_move_to_trash:
974 * @source: a #RBSource
975 *
976 * Trashes the files backing the currently selected set of entries.
977 * In general, this should use #rhythmdb_entry_move_to_trash to
978 * perform the actual trash operation.
979 */
980 void
981 rb_source_move_to_trash (RBSource *source)
982 {
983 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
984
985 klass->impl_move_to_trash (source);
986 }
987
988 static void
989 default_reset_filters (RBSource *source)
990 {
991 rb_debug ("no implementation of reset_filters for this source");
992 }
993
994 /**
995 * rb_source_reset_filters:
996 * @source: a #RBSource
997 *
998 * Clears all filters (browser selections, etc.) in this source.
999 */
1000 void
1001 rb_source_reset_filters (RBSource *source)
1002 {
1003 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1004
1005 klass->impl_reset_filters (source);
1006 }
1007
1008 /**
1009 * rb_source_can_show_properties:
1010 * @source: a #RBSource
1011 *
1012 * Determines whether the source can display a properties
1013 * window for the currently selected entry (or set of entries)
1014 *
1015 * Return value: TRUE if showing properties is supported
1016 */
1017 gboolean
1018 rb_source_can_show_properties (RBSource *source)
1019 {
1020 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1021
1022 return (klass->impl_song_properties != NULL);
1023 }
1024
1025 /**
1026 * rb_source_song_properties:
1027 * @source: a #RBSource
1028 *
1029 * Displays a properties window for the currently selected entries.
1030 */
1031 void
1032 rb_source_song_properties (RBSource *source)
1033 {
1034 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1035
1036 g_assert (klass->impl_song_properties);
1037 klass->impl_song_properties (source);
1038 }
1039
1040 /**
1041 * rb_source_try_playlist:
1042 * @source: a #RBSource
1043 *
1044 * Determines whether playback URIs for entries in the source should
1045 * be parsed as playlists rather than just played.
1046 *
1047 * Return value: TRUE to attempt playlist parsing
1048 */
1049 gboolean
1050 rb_source_try_playlist (RBSource *source)
1051 {
1052 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1053
1054 return klass->impl_try_playlist (source);
1055 }
1056
1057 /**
1058 * rb_source_want_uri:
1059 * @source: a #RBSource
1060 * @uri: a URI for the source to consider
1061 *
1062 * Returns an indication of how much the source wants to handle
1063 * the specified URI. 100 is the highest usual value, and should
1064 * only be used when the URI can only be associated with this source.
1065 * 0 should be used when the URI does not match the source at all.
1066 *
1067 * Return value: value from 0 to 100 indicating how much the
1068 * source wants this URI.
1069 */
1070 guint
1071 rb_source_want_uri (RBSource *source, const char *uri)
1072 {
1073 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1074 if (klass->impl_want_uri)
1075 return klass->impl_want_uri (source, uri);
1076 return 0;
1077 }
1078
1079 /**
1080 * rb_source_uri_is_source:
1081 * @source: a #RBSource
1082 * @uri: a URI for the source to consider
1083 *
1084 * Checks if the URI matches the source itself. A source
1085 * should return TRUE here if the URI points to the device that
1086 * the source represents, for example.
1087 *
1088 * Return value: TRUE if the URI identifies the source itself.
1089 */
1090 gboolean
1091 rb_source_uri_is_source (RBSource *source, const char *uri)
1092 {
1093 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1094 if (klass->impl_uri_is_source)
1095 return klass->impl_uri_is_source (source, uri);
1096 return FALSE;
1097 }
1098
1099 /**
1100 * rb_source_add_uri:
1101 * @source: a #RBSource
1102 * @uri: a URI to add
1103 * @title: theoretically, the title of the entity the URI points to
1104 * @genre: theoretically, the genre of the entity the URI points to
1105 * @callback: a callback function to call when complete
1106 * @data: data to pass to the callback
1107 * @destroy_data: function to call to destroy the callback data
1108 *
1109 * Adds an entry corresponding to the URI to the source. The
1110 * @title and @genre parameters are not really used.
1111 */
1112 void
1113 rb_source_add_uri (RBSource *source,
1114 const char *uri,
1115 const char *title,
1116 const char *genre,
1117 RBSourceAddCallback callback,
1118 gpointer data,
1119 GDestroyNotify destroy_data)
1120 {
1121 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1122 if (klass->impl_add_uri)
1123 klass->impl_add_uri (source, uri, title, genre, callback, data, destroy_data);
1124 }
1125
1126 /**
1127 * rb_source_can_pause:
1128 * @source: a #RBSource
1129 *
1130 * Determines whether playback of entries from the source can
1131 * be paused.
1132 *
1133 * Return value: TRUE if pausing is supported
1134 */
1135 gboolean
1136 rb_source_can_pause (RBSource *source)
1137 {
1138 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1139
1140 return klass->impl_can_pause (source);
1141 }
1142
1143 static gboolean
1144 default_try_playlist (RBSource *source)
1145 {
1146 return FALSE;
1147 }
1148
1149 static RBSourceEOFType
1150 default_handle_eos (RBSource *source)
1151 {
1152 return RB_SOURCE_EOF_NEXT;
1153 }
1154
1155 /**
1156 * rb_source_handle_eos:
1157 * @source: a #RBSource
1158 *
1159 * Determines how EOS events should be handled when playing entries
1160 * from the source.
1161 *
1162 * Return value: EOS event handling type
1163 */
1164 RBSourceEOFType
1165 rb_source_handle_eos (RBSource *source)
1166 {
1167 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1168
1169 return klass->impl_handle_eos (source);
1170 }
1171
1172 static RBEntryView*
1173 default_get_entry_view (RBSource *source)
1174 {
1175 return NULL;
1176 }
1177
1178 static char *
1179 default_get_delete_action (RBSource *source)
1180 {
1181 return g_strdup ("EditRemove");
1182 }
1183
1184 /**
1185 * rb_source_get_delete_action:
1186 * @source: a #RBSource
1187 *
1188 * Returns the name of the UI action to use for 'delete'.
1189 * This allows the source to customise the visible action name
1190 * and description to better describe what deletion actually does.
1191 *
1192 * Return value: allocated string holding UI action name
1193 */
1194 char *
1195 rb_source_get_delete_action (RBSource *source)
1196 {
1197 RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1198 return klass->impl_get_delete_action (source);
1199 }
1200
1201 static gboolean
1202 _update_status_idle (RBSource *source)
1203 {
1204 rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
1205
1206 if (source->priv->hidden_when_empty)
1207 update_visibility_idle (source);
1208
1209 source->priv->update_status_id = 0;
1210 return FALSE;
1211 }
1212
1213 static void
1214 rb_source_row_inserted_cb (GtkTreeModel *model,
1215 GtkTreePath *path,
1216 GtkTreeIter *iter,
1217 RBSource *source)
1218 {
1219 if (source->priv->update_status_id == 0)
1220 source->priv->update_status_id = g_idle_add ((GSourceFunc)_update_status_idle, source);
1221 }
1222
1223 static void
1224 rb_source_post_entry_deleted_cb (GtkTreeModel *model,
1225 RhythmDBEntry *entry,
1226 RBSource *source)
1227 {
1228 if (source->priv->update_status_id == 0)
1229 source->priv->update_status_id = g_idle_add ((GSourceFunc)_update_status_idle, source);
1230 }
1231
1232 static void
1233 rb_source_gather_hash_keys (char *key,
1234 gpointer unused,
1235 GList **data)
1236 {
1237 *data = g_list_prepend (*data, key);
1238 }
1239
1240 /**
1241 * rb_source_gather_selected_properties:
1242 * @source: a #RBSource
1243 * @prop: property for which to gather selection
1244 *
1245 * Returns a list containing the values of the specified
1246 * property from the selected entries in the source.
1247 * This is used to implement the 'browse this artist' (etc.)
1248 * actions.
1249 *
1250 * Return value: (element-type utf8) (transfer full): list of property values
1251 */
1252 GList *
1253 rb_source_gather_selected_properties (RBSource *source,
1254 RhythmDBPropType prop)
1255 {
1256 RBEntryView *entryview;
1257 GList *selected, *tem;
1258 GHashTable *selected_set;
1259
1260 entryview = rb_source_get_entry_view (source);
1261 if (entryview == NULL)
1262 return NULL;
1263
1264 selected_set = g_hash_table_new (g_str_hash, g_str_equal);
1265 selected = rb_entry_view_get_selected_entries (entryview);
1266
1267 for (tem = selected; tem; tem = tem->next) {
1268 RhythmDBEntry *entry = tem->data;
1269 char *val = g_strdup (rhythmdb_entry_get_string (entry, prop));
1270 g_hash_table_insert (selected_set, val, NULL);
1271 }
1272
1273 g_list_foreach (selected, (GFunc)rhythmdb_entry_unref, NULL);
1274 g_list_free (selected);
1275
1276 tem = NULL;
1277 g_hash_table_foreach (selected_set, (GHFunc) rb_source_gather_hash_keys,
1278 &tem);
1279 g_hash_table_destroy (selected_set);
1280 return tem;
1281 }
1282
1283 /**
1284 * _rb_source_check_entry_type:
1285 * @source: a #RBSource
1286 * @entry: a #RhythmDBEntry
1287 *
1288 * Checks if a database entry matches the entry type for the source.
1289 *
1290 * Return value: %TRUE if the entry matches the source's entry type.
1291 */
1292 gboolean
1293 _rb_source_check_entry_type (RBSource *source, RhythmDBEntry *entry)
1294 {
1295 RhythmDBEntryType *entry_type;
1296 gboolean ret = TRUE;
1297
1298 g_object_get (source, "entry-type", &entry_type, NULL);
1299 if (entry_type != NULL) {
1300 if (rhythmdb_entry_get_entry_type (entry) != entry_type) {
1301 ret = FALSE;
1302 }
1303 g_object_unref (entry_type);
1304 }
1305 return ret;
1306 }
1307
1308 /**
1309 * _rb_source_set_import_status:
1310 * @source: an #RBSource
1311 * @job: a #RhythmDBImportJob
1312 * @progress_text: used to return progress text
1313 * @progress: used to return progress fraction
1314 *
1315 * Used in implementations of the get_status method to provide source
1316 * status information based on a #RhythmDBImportJob.
1317 */
1318 void
1319 _rb_source_set_import_status (RBSource *source, RhythmDBImportJob *job, char **progress_text, float *progress)
1320 {
1321 int total;
1322 int imported;
1323
1324 total = rhythmdb_import_job_get_total (job);
1325 imported = rhythmdb_import_job_get_imported (job);
1326
1327 g_free (*progress_text);
1328 *progress_text = g_strdup_printf (_("Importing (%d/%d)"), imported, total);
1329 *progress = ((float)imported / (float)total);
1330 }
1331
1332 static gboolean
1333 sort_order_get_mapping (GValue *value, GVariant *variant, gpointer data)
1334 {
1335 const char *column;
1336 gboolean sort_type;
1337 char *str;
1338
1339 g_variant_get (variant, "(&sb)", &column, &sort_type);
1340 str = g_strdup_printf ("%s,%s", column, sort_type ? "ascending" : "descending");
1341 g_value_take_string (value, str);
1342 return TRUE;
1343 }
1344
1345 static GVariant *
1346 sort_order_set_mapping (const GValue *value, const GVariantType *expected_type, gpointer data)
1347 {
1348 gboolean sort_type;
1349 GVariant *var;
1350 char **strs;
1351
1352 strs = g_strsplit (g_value_get_string (value), ",", 0);
1353 if (!strcmp ("ascending", strs[1])) {
1354 sort_type = TRUE;
1355 } else if (!strcmp ("descending", strs[1])) {
1356 sort_type = FALSE;
1357 } else {
1358 g_warning ("atttempting to sort in unknown direction");
1359 sort_type = TRUE;
1360 }
1361
1362 var = g_variant_new ("(sb)", strs[0], sort_type);
1363 g_strfreev (strs);
1364 return var;
1365 }
1366
1367 static void
1368 sync_paned_position (GSettings *settings, GObject *paned)
1369 {
1370 int pos;
1371 g_object_get (paned, "position", &pos, NULL);
1372
1373 if (pos != g_settings_get_int (settings, "paned-position")) {
1374 g_settings_set_int (settings, "paned-position", pos);
1375 }
1376 }
1377
1378 static void
1379 paned_position_changed_cb (GObject *paned, GParamSpec *pspec, GSettings *settings)
1380 {
1381 rb_settings_delayed_sync (settings,
1382 (RBDelayedSyncFunc) sync_paned_position,
1383 g_object_ref (paned),
1384 g_object_unref);
1385 }
1386
1387 /**
1388 * rb_source_bind_settings:
1389 * @source: the #RBSource
1390 * @entry_view: (allow-none): the #RBEntryView for the source
1391 * @paned: (allow-none): the #GtkPaned containing the entry view and the browser
1392 * @browser: (allow-none): the browser (typically a #RBLibraryBrowser) for the source
1393 *
1394 * Binds the source's #GSettings instance to the given widgets. Should be called
1395 * from the source's constructed method.
1396 *
1397 * If the browser widget has a browser-views property, it will be bound to the
1398 * browser-views settings key.
1399 */
1400 void
1401 rb_source_bind_settings (RBSource *source, GtkWidget *entry_view, GtkWidget *paned, GtkWidget *browser)
1402 {
1403 char *name;
1404 GSettings *common_settings;
1405
1406 common_settings = g_settings_new ("org.gnome.rhythmbox.sources");
1407 g_object_get (source, "name", &name, NULL);
1408
1409 if (entry_view != NULL) {
1410 rb_debug ("binding entry view sort order for %s", name);
1411 if (source->priv->settings) {
1412 g_settings_bind_with_mapping (source->priv->settings, "sorting", entry_view, "sort-order",
1413 G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET | G_SETTINGS_BIND_NO_SENSITIVITY,
1414 (GSettingsBindGetMapping) sort_order_get_mapping,
1415 (GSettingsBindSetMapping) sort_order_set_mapping,
1416 NULL, NULL);
1417 }
1418
1419 g_settings_bind (common_settings, "visible-columns",
1420 entry_view, "visible-columns",
1421 G_SETTINGS_BIND_DEFAULT);
1422 }
1423
1424 if (paned != NULL && source->priv->settings != NULL) {
1425 rb_debug ("binding paned position for %s", name);
1426 /* can't use a normal binding here, as we want to delay writing to the
1427 * setting while the separator is being dragged.
1428 */
1429 g_settings_bind (source->priv->settings, "paned-position", paned, "position", G_SETTINGS_BIND_GET);
1430 g_signal_connect_object (paned, "notify::position", G_CALLBACK (paned_position_changed_cb), source->priv->settings, 0);
1431 }
1432
1433 if (browser) {
1434 rb_debug ("binding show-browser for %s", name);
1435 if (source->priv->settings) {
1436 g_settings_bind (source->priv->settings, "show-browser", source, "show-browser", G_SETTINGS_BIND_DEFAULT);
1437 }
1438
1439 if (g_object_class_find_property (G_OBJECT_GET_CLASS (browser), "browser-views")) {
1440 g_settings_bind (common_settings, "browser-views", browser, "browser-views", G_SETTINGS_BIND_DEFAULT);
1441 }
1442 }
1443
1444 g_free (name);
1445 }
1446
1447 /* This should really be standard. */
1448 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
1449
1450 GType
1451 rb_source_eof_type_get_type (void)
1452 {
1453 static GType etype = 0;
1454
1455 if (etype == 0) {
1456 static const GEnumValue values[] = {
1457 ENUM_ENTRY (RB_SOURCE_EOF_ERROR, "error"),
1458 ENUM_ENTRY (RB_SOURCE_EOF_STOP, "stop"),
1459 ENUM_ENTRY (RB_SOURCE_EOF_RETRY, "retry"),
1460 ENUM_ENTRY (RB_SOURCE_EOF_NEXT, "next"),
1461 { 0, 0, 0 }
1462 };
1463
1464 etype = g_enum_register_static ("RBSourceEOFType", values);
1465 }
1466
1467 return etype;
1468 }
1469
1470 GType
1471 rb_source_load_status_get_type (void)
1472 {
1473 static GType etype = 0;
1474
1475 if (etype == 0) {
1476 static const GEnumValue values[] = {
1477 ENUM_ENTRY (RB_SOURCE_LOAD_STATUS_NOT_LOADED, "not-loaded"),
1478 ENUM_ENTRY (RB_SOURCE_LOAD_STATUS_WAITING, "waiting"),
1479 ENUM_ENTRY (RB_SOURCE_LOAD_STATUS_LOADING, "loading"),
1480 ENUM_ENTRY (RB_SOURCE_LOAD_STATUS_LOADED, "loaded"),
1481 { 0, 0, 0 }
1482 };
1483
1484 etype = g_enum_register_static ("RBSourceLoadStatus", values);
1485 }
1486
1487 return etype;
1488 }
1489
1490 /* introspection annotations for vmethods */
1491
1492 /**
1493 * impl_get_entry_view:
1494 * @source: a #RBSource
1495 *
1496 * Return value: (transfer none): the RBEntryView for the source
1497 */
1498
1499 /**
1500 * impl_get_property_views:
1501 * @source: a #RBSource
1502 *
1503 * Return value: (element-type RB.PropertyView) (transfer container): list of property views
1504 */
1505
1506 /**
1507 * impl_cut:
1508 * @source: a #RBSource
1509 *
1510 * Return value: (element-type RB.RhythmDBEntry) (transfer full): list of entries
1511 */
1512
1513 /**
1514 * impl_copy:
1515 * @source: a #RBSource
1516 *
1517 * Return value: (element-type RB.RhythmDBEntry) (transfer full): list of entries
1518 */
1519
1520 /**
1521 * impl_paste:
1522 * @source: a #RBSource
1523 * @entries: (element-type RB.RhythmDBEntry) (transfer none): list of entries to paste
1524 */