1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3
* Copyright (C) 2011 Jonathan Matthew
5
* This program is free software; you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation; either version 2 of the License, or
8
* (at your option) any later version.
10
* The Rhythmbox authors hereby grant permission for non-GPL compatible
11
* GStreamer plugins to be used and distributed together with GStreamer
12
* and Rhythmbox. This permission is above and beyond the permissions granted
13
* by the GPL license by which Rhythmbox is covered. If you modify this code
14
* you may extend this exception to your version of the code, but you are not
15
* obligated to do so. If you do not wish to do so, delete this exception
16
* statement from your version.
18
* This program is distributed in the hope that it will be useful,
19
* but WITHOUT ANY WARRANTY; without even the implied warranty of
20
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
* GNU General Public License for more details.
23
* You should have received a copy of the GNU General Public License
24
* along with this program; if not, write to the Free Software
25
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
33
#include <glib/gi18n.h>
38
#include "rb-shell-player.h"
39
#include "rb-grilo-source.h"
42
#include "rb-file-helpers.h"
43
#include "rb-gst-media-types.h"
44
#include "rb-search-entry.h"
46
/* number of items to check before giving up on finding any
47
* of a particular type
49
#define CONTAINER_GIVE_UP_POINT 100
51
/* maximum number of tracks to fetch before stopping and
52
* requiring the user to ask for more.
54
#define CONTAINER_MAX_TRACKS 1000
56
/* number of items to fetch at once */
57
#define CONTAINER_FETCH_SIZE 50
60
CONTAINER_UNKNOWN_MEDIA = 0,
71
static void rb_grilo_source_dispose (GObject *object);
72
static void rb_grilo_source_finalize (GObject *object);
73
static void rb_grilo_source_constructed (GObject *object);
74
static void impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
75
static void impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
77
static void browser_selection_changed_cb (GtkTreeSelection *selection, RBGriloSource *source);
78
static void browser_row_expanded_cb (GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, RBGriloSource *source);
79
static void scroll_adjust_changed_cb (GtkAdjustment *adjustment, RBGriloSource *source);
80
static void scroll_adjust_value_changed_cb (GtkAdjustment *adjustment, RBGriloSource *source);
81
static gboolean maybe_expand_container (RBGriloSource *source);
82
static void fetch_more_cb (GtkInfoBar *bar, gint response, RBGriloSource *source);
83
static void search_cb (RBSearchEntry *search, const char *text, RBGriloSource *source);
84
static void notify_sort_order_cb (GObject *object, GParamSpec *pspec, RBGriloSource *source);
86
static void impl_delete_thyself (RBDisplayPage *page);
87
static void impl_selected (RBDisplayPage *page);
88
static void impl_deselected (RBDisplayPage *page);
90
static RBEntryView *impl_get_entry_view (RBSource *source);
92
struct _RBGriloSourcePrivate
94
GrlMediaSource *grilo_source;
97
RhythmDBEntryType *entry_type;
99
/* some widgets and things */
101
RhythmDBQueryModel *query_model;
102
RBEntryView *entry_view;
103
GtkTreeStore *browser_model;
104
GtkWidget *browser_view;
105
gboolean done_initial_browse;
107
GtkWidget *info_bar_label;
108
RBSearchEntry *search_entry;
110
/* current browsing operation (should allow multiple concurrent ops?) */
112
GrlMedia *browse_container;
113
GtkTreeIter browse_container_iter;
114
guint browse_position;
115
gboolean browse_got_results;
116
gboolean browse_got_media;
117
guint maybe_expand_idle;
119
/* current media browse operation */
120
guint media_browse_op;
122
GrlMedia *media_browse_container;
123
GtkTreeIter media_browse_container_iter;
124
guint media_browse_position;
125
gboolean media_browse_got_results;
126
gboolean media_browse_got_containers;
127
guint media_browse_limit;
132
G_DEFINE_DYNAMIC_TYPE (RBGriloSource, rb_grilo_source, RB_TYPE_SOURCE)
136
G_DEFINE_DYNAMIC_TYPE (RBGriloEntryType, rb_grilo_entry_type, RHYTHMDB_TYPE_ENTRY_TYPE);
139
rb_grilo_entry_type_destroy_entry (RhythmDBEntryType *etype, RhythmDBEntry *entry)
141
RBGriloEntryData *data;
143
data = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RBGriloEntryData);
144
g_object_unref (data->grilo_data);
145
if (data->grilo_container != NULL)
146
g_object_unref (data->grilo_container);
150
rb_grilo_entry_type_class_init (RBGriloEntryTypeClass *klass)
152
RhythmDBEntryTypeClass *etype_class = RHYTHMDB_ENTRY_TYPE_CLASS (klass);
153
etype_class->can_sync_metadata = (RhythmDBEntryTypeBooleanFunc) rb_true_function;
154
etype_class->sync_metadata = (RhythmDBEntryTypeSyncFunc) rb_null_function;
155
etype_class->destroy_entry = rb_grilo_entry_type_destroy_entry;
159
rb_grilo_entry_type_class_finalize (RBGriloEntryTypeClass *klass)
164
rb_grilo_entry_type_init (RBGriloEntryType *etype)
169
rb_grilo_source_class_init (RBGriloSourceClass *klass)
171
GObjectClass *object_class = G_OBJECT_CLASS (klass);
172
RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
173
RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
175
object_class->constructed = rb_grilo_source_constructed;
176
object_class->dispose = rb_grilo_source_dispose;
177
object_class->finalize = rb_grilo_source_finalize;
178
object_class->set_property = impl_set_property;
179
object_class->get_property = impl_get_property;
181
page_class->delete_thyself = impl_delete_thyself;
182
page_class->selected = impl_selected;
183
page_class->deselected = impl_deselected;
185
source_class->impl_get_entry_view = impl_get_entry_view;
187
g_object_class_install_property (object_class,
189
g_param_spec_object ("grilo-source",
191
"grilo source object",
192
GRL_TYPE_MEDIA_SOURCE,
193
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
195
g_type_class_add_private (klass, sizeof (RBGriloSourcePrivate));
199
rb_grilo_source_class_finalize (RBGriloSourceClass *klass)
204
rb_grilo_source_init (RBGriloSource *self)
206
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, RB_TYPE_GRILO_SOURCE, RBGriloSourcePrivate);
210
rb_grilo_source_finalize (GObject *object)
212
RBGriloSource *source = RB_GRILO_SOURCE (object);
214
g_free (source->priv->search_text);
216
g_list_free (source->priv->grilo_keys);
218
G_OBJECT_CLASS (rb_grilo_source_parent_class)->finalize (object);
222
rb_grilo_source_dispose (GObject *object)
224
RBGriloSource *source = RB_GRILO_SOURCE (object);
226
if (source->priv->browse_op != 0) {
227
grl_operation_cancel (source->priv->browse_op);
228
source->priv->browse_op = 0;
231
if (source->priv->media_browse_op != 0) {
232
grl_operation_cancel (source->priv->media_browse_op);
233
source->priv->media_browse_op = 0;
236
if (source->priv->query_model != NULL) {
237
g_object_unref (source->priv->query_model);
238
source->priv->query_model = NULL;
241
if (source->priv->entry_type != NULL) {
242
g_object_unref (source->priv->entry_type);
243
source->priv->entry_type = NULL;
246
if (source->priv->maybe_expand_idle != 0) {
247
g_source_remove (source->priv->maybe_expand_idle);
248
source->priv->maybe_expand_idle = 0;
251
G_OBJECT_CLASS (rb_grilo_source_parent_class)->dispose (object);
255
rb_grilo_source_constructed (GObject *object)
257
RBGriloSource *source;
259
RBShellPlayer *shell_player;
260
const GList *source_keys;
261
GtkTreeViewColumn *column;
262
GtkCellRenderer *renderer;
263
GtkTreeSelection *selection;
265
GtkWidget *browserbox;
268
GtkAdjustment *adjustment;
270
RB_CHAIN_GOBJECT_METHOD (rb_grilo_source_parent_class, constructed, object);
271
source = RB_GRILO_SOURCE (object);
273
g_object_get (source, "shell", &shell, NULL);
275
"db", &source->priv->db,
276
"shell-player", &shell_player,
278
g_object_unref (shell);
280
g_object_get (source, "entry-type", &source->priv->entry_type, NULL);
282
source->priv->entry_view = rb_entry_view_new (source->priv->db, G_OBJECT (shell_player), TRUE, FALSE);
283
g_object_unref (shell_player);
284
g_signal_connect (source->priv->entry_view,
285
"notify::sort-order",
286
G_CALLBACK (notify_sort_order_cb),
289
source_keys = grl_metadata_source_supported_keys (GRL_METADATA_SOURCE (source->priv->grilo_source));
291
if (g_list_find ((GList *)source_keys, GRL_METADATA_KEY_TRACK_NUMBER)) {
292
rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_TRACK_NUMBER, FALSE);
293
source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
294
GRL_METADATA_KEY_TRACK_NUMBER);
297
if (g_list_find ((GList *)source_keys, GRL_METADATA_KEY_TITLE)) {
298
rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_TITLE, TRUE);
299
source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
300
GRL_METADATA_KEY_TITLE);
303
if (g_list_find ((GList *)source_keys, GRL_METADATA_KEY_GENRE)) {
304
rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_GENRE, FALSE);
305
source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
306
GRL_METADATA_KEY_GENRE);
308
if (g_list_find ((GList *)source_keys, GRL_METADATA_KEY_ARTIST)) {
309
rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_ARTIST, FALSE);
310
source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
311
GRL_METADATA_KEY_ARTIST);
313
if (g_list_find ((GList *)source_keys, GRL_METADATA_KEY_ALBUM)) {
314
rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_ALBUM, FALSE);
315
source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
316
GRL_METADATA_KEY_ALBUM);
319
if (g_list_find ((GList *)source_keys, GRL_METADATA_KEY_DATE)) {
320
rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_YEAR, FALSE);
321
source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
322
GRL_METADATA_KEY_DATE);
325
if (g_list_find ((GList *)source_keys, GRL_METADATA_KEY_DURATION)) {
326
rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_DURATION, FALSE);
327
source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
328
GRL_METADATA_KEY_DURATION);
331
source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys, GRL_METADATA_KEY_CHILDCOUNT);
332
source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys, GRL_METADATA_KEY_URL);
333
source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys, GRL_METADATA_KEY_THUMBNAIL);
335
/* probably add an image column too? */
336
source->priv->browser_model = gtk_tree_store_new (4, GRL_TYPE_MEDIA, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
337
source->priv->browser_view = gtk_tree_view_new ();
338
gtk_tree_view_set_model (GTK_TREE_VIEW (source->priv->browser_view), GTK_TREE_MODEL (source->priv->browser_model));
340
column = gtk_tree_view_column_new ();
341
renderer = gtk_cell_renderer_text_new ();
342
gtk_tree_view_column_set_title (column, _("Browse"));
343
gtk_tree_view_column_pack_start (column, renderer, FALSE);
344
gtk_tree_view_column_add_attribute (column, renderer, "text", 1);
345
gtk_tree_view_column_set_expand (column, TRUE);
346
gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
348
gtk_tree_view_append_column (GTK_TREE_VIEW (source->priv->browser_view), column);
349
gtk_tree_view_set_show_expanders (GTK_TREE_VIEW (source->priv->browser_view), TRUE);
350
gtk_tree_view_set_expander_column (GTK_TREE_VIEW (source->priv->browser_view), column);
351
gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (source->priv->browser_view), TRUE);
352
gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (source->priv->browser_view), TRUE);
354
g_signal_connect (source->priv->browser_view, "row-expanded", G_CALLBACK (browser_row_expanded_cb), source);
355
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (source->priv->browser_view));
356
gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); /* should be multiple eventually */
357
g_signal_connect (selection, "changed", G_CALLBACK (browser_selection_changed_cb), source);
359
scrolled = gtk_scrolled_window_new (NULL, NULL);
360
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_IN);
361
adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled));
362
g_signal_connect (adjustment, "changed", G_CALLBACK (scroll_adjust_changed_cb), source);
363
g_signal_connect (adjustment, "value-changed", G_CALLBACK (scroll_adjust_value_changed_cb), source);
365
browserbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
367
/* search bar (if the source supports searching) */
368
if (grl_metadata_source_supported_operations (GRL_METADATA_SOURCE (source->priv->grilo_source)) & GRL_OP_SEARCH) {
369
source->priv->search_entry = rb_search_entry_new (FALSE);
370
g_object_set (source->priv->search_entry, "explicit-mode", TRUE, NULL);
371
g_signal_connect (source->priv->search_entry, "search", G_CALLBACK (search_cb), source);
372
g_signal_connect (source->priv->search_entry, "activate", G_CALLBACK (search_cb), source);
373
gtk_box_pack_start (GTK_BOX (browserbox), GTK_WIDGET (source->priv->search_entry), FALSE, FALSE, 6);
375
gtk_container_add (GTK_CONTAINER (scrolled), source->priv->browser_view);
376
gtk_box_pack_start (GTK_BOX (browserbox), scrolled, TRUE, TRUE, 0);
378
mainbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
379
gtk_box_pack_start (GTK_BOX (source), mainbox, TRUE, TRUE, 0);
382
source->priv->info_bar_label = gtk_label_new ("");
383
source->priv->info_bar = gtk_info_bar_new ();
384
gtk_info_bar_set_message_type (GTK_INFO_BAR (source->priv->info_bar), GTK_MESSAGE_INFO);
385
gtk_info_bar_add_button (GTK_INFO_BAR (source->priv->info_bar), _("Fetch more tracks"), GTK_RESPONSE_OK);
386
gtk_container_add (GTK_CONTAINER (gtk_info_bar_get_content_area (GTK_INFO_BAR (source->priv->info_bar))),
387
source->priv->info_bar_label);
388
gtk_widget_show (GTK_WIDGET (source->priv->info_bar_label));
389
gtk_widget_set_no_show_all (GTK_WIDGET (source->priv->info_bar), TRUE);
390
g_signal_connect (source->priv->info_bar, "response", G_CALLBACK (fetch_more_cb), source);
392
/* don't allow the browser to be hidden? */
393
source->priv->paned = gtk_hpaned_new ();
394
rb_source_bind_settings (RB_SOURCE (source), GTK_WIDGET (source->priv->entry_view), source->priv->paned, NULL);
395
gtk_paned_pack1 (GTK_PANED (source->priv->paned), browserbox, FALSE, FALSE);
397
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
398
gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (source->priv->entry_view), TRUE, TRUE, 0);
399
gtk_box_pack_start (GTK_BOX (vbox), source->priv->info_bar, FALSE, FALSE, 0);
400
gtk_paned_pack2 (GTK_PANED (source->priv->paned), vbox, TRUE, FALSE);
402
gtk_box_pack_start (GTK_BOX (mainbox), source->priv->paned, TRUE, TRUE, 0);
404
gtk_widget_show_all (GTK_WIDGET (source));
408
rb_grilo_source_new (GObject *plugin, GrlMediaSource *grilo_source)
413
RhythmDBEntryType *entry_type;
417
name = g_strdup_printf ("grilo:%s", grl_media_plugin_get_id (GRL_MEDIA_PLUGIN (grilo_source)));
419
g_object_get (plugin, "object", &shell, NULL);
420
g_object_get (shell, "db", &db, NULL);
421
entry_type = g_object_new (rb_grilo_entry_type_get_type (),
424
"save-to-disk", FALSE,
425
"category", RHYTHMDB_ENTRY_NORMAL,
426
"type-data-size", sizeof(RBGriloEntryData),
428
rhythmdb_register_entry_type (db, entry_type);
432
settings = g_settings_new ("org.gnome.rhythmbox.plugins.grilo");
433
source = g_object_new (RB_TYPE_GRILO_SOURCE,
434
"name", grl_metadata_source_get_name (GRL_METADATA_SOURCE (grilo_source)),
435
"entry-type", entry_type,
438
"show-browser", FALSE,
439
"settings", g_settings_get_child (settings, "source"),
440
"grilo-source", grilo_source,
442
g_object_unref (settings);
444
rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source), entry_type);
446
g_object_unref (shell);
447
return RB_SOURCE (source);
451
impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
453
RBGriloSource *source = RB_GRILO_SOURCE (object);
455
case PROP_GRILO_SOURCE:
456
source->priv->grilo_source = g_value_get_object (value);
459
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
465
impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
467
RBGriloSource *source = RB_GRILO_SOURCE (object);
470
case PROP_GRILO_SOURCE:
471
g_value_set_object (value, source->priv->grilo_source);
474
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
480
impl_delete_thyself (RBDisplayPage *page)
482
RBGriloSource *source = RB_GRILO_SOURCE (page);
483
RhythmDBEntryType *entry_type;
485
if (source->priv->browse_op != 0) {
486
grl_operation_cancel (source->priv->browse_op);
487
source->priv->browse_op = 0;
490
if (source->priv->media_browse_op != 0) {
491
grl_operation_cancel (source->priv->media_browse_op);
492
source->priv->media_browse_op = 0;
495
g_object_get (source, "entry-type", &entry_type, NULL);
496
rhythmdb_entry_delete_by_type (source->priv->db, entry_type);
497
g_object_unref (entry_type);
499
rhythmdb_commit (source->priv->db);
503
_rb_grilo_source_register_type (GTypeModule *module)
505
rb_grilo_source_register_type (module);
506
rb_grilo_entry_type_register_type (module);
509
/* grilo media -> rhythmdb entry */
512
set_string_prop_from_key (RhythmDB *db, RhythmDBEntry *entry, RhythmDBPropType prop, GrlData *data, GrlKeyID key)
515
if (grl_data_has_key (data, key) == FALSE)
518
g_value_init (&v, G_TYPE_STRING);
519
g_value_set_string (&v, grl_data_get_string (data, key));
520
rhythmdb_entry_set (db, entry, prop, &v);
524
static RhythmDBEntry *
525
create_entry_for_media (RhythmDB *db, RhythmDBEntryType *entry_type, GrlData *data, GrlData *container)
527
RhythmDBEntry *entry;
528
RBGriloEntryData *entry_data;
530
entry = rhythmdb_entry_lookup_by_location (db, grl_media_get_url (GRL_MEDIA (data)));
535
rb_debug ("creating entry for %s / %s", grl_media_get_url (GRL_MEDIA (data)), grl_media_get_id (GRL_MEDIA (data)));
537
entry = rhythmdb_entry_new (db, entry_type, grl_media_get_url (GRL_MEDIA (data))); /* just use the url? */
543
set_string_prop_from_key (db, entry, RHYTHMDB_PROP_TITLE, data, GRL_METADATA_KEY_TITLE);
544
set_string_prop_from_key (db, entry, RHYTHMDB_PROP_ALBUM, data, GRL_METADATA_KEY_ALBUM);
545
set_string_prop_from_key (db, entry, RHYTHMDB_PROP_ARTIST, data, GRL_METADATA_KEY_ARTIST);
546
set_string_prop_from_key (db, entry, RHYTHMDB_PROP_GENRE, data, GRL_METADATA_KEY_GENRE);
547
set_string_prop_from_key (db, entry, RHYTHMDB_PROP_TITLE, data, GRL_METADATA_KEY_TITLE);
549
if (grl_data_has_key (data, GRL_METADATA_KEY_DATE)) {
550
/* something - grilo has this as a string? */
553
if (grl_data_has_key (data, GRL_METADATA_KEY_BITRATE)) {
555
g_value_init (&v, G_TYPE_ULONG);
556
g_value_set_ulong (&v, grl_data_get_int (data, GRL_METADATA_KEY_BITRATE));
557
rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_BITRATE, &v);
561
if (grl_data_has_key (data, GRL_METADATA_KEY_DURATION)) {
562
/* this is probably in seconds */
564
g_value_init (&v, G_TYPE_ULONG);
565
g_value_set_ulong (&v, grl_data_get_int (data, GRL_METADATA_KEY_DURATION));
566
rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DURATION, &v);
570
if (grl_data_has_key (data, GRL_METADATA_KEY_MIME)) {
571
const char *media_type;
572
media_type = rb_gst_mime_type_to_media_type (grl_data_get_string (data, GRL_METADATA_KEY_MIME));
575
g_value_init (&v, G_TYPE_STRING);
576
g_value_set_string (&v, media_type);
577
rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_MEDIA_TYPE, &v);
582
if (grl_data_has_key (data, GRL_METADATA_KEY_TRACK_NUMBER)) {
584
g_value_init (&v, G_TYPE_ULONG);
585
g_value_set_ulong (&v, grl_data_get_int (data, GRL_METADATA_KEY_TRACK_NUMBER));
586
rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_TRACK_NUMBER, &v);
590
/* rating and play count? */
592
entry_data = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RBGriloEntryData);
593
entry_data->grilo_data = g_object_ref (data);
594
if (container != NULL) {
595
entry_data->grilo_container = g_object_ref (container);
598
/* might want to consider batching this */
599
rhythmdb_commit (db);
604
/* container browsing */
606
static void browse_next (RBGriloSource *source);
609
delete_marker_row (RBGriloSource *source, GtkTreeIter *iter)
611
GtkTreeIter marker_iter;
612
if (gtk_tree_model_iter_children (GTK_TREE_MODEL (source->priv->browser_model), &marker_iter, iter)) {
615
gtk_tree_model_get (GTK_TREE_MODEL (source->priv->browser_model), &marker_iter,
618
if (container == NULL) {
619
gtk_tree_store_remove (GTK_TREE_STORE (source->priv->browser_model), &marker_iter);
622
} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (source->priv->browser_model), &marker_iter));
627
set_container_type (RBGriloSource *source, GtkTreeIter *iter, gboolean has_media)
631
gtk_tree_model_get (GTK_TREE_MODEL (source->priv->browser_model),
635
if (container_type == CONTAINER_UNKNOWN_MEDIA) {
636
container_type = has_media ? CONTAINER_HAS_MEDIA : CONTAINER_NO_MEDIA;
639
gtk_tree_store_set (source->priv->browser_model,
646
grilo_browse_cb (GrlMediaSource *grilo_source, guint operation_id, GrlMedia *media, guint remaining, RBGriloSource *source, const GError *error)
648
if (operation_id != source->priv->browse_op) {
654
rb_debug ("got error for %s: %s", grl_metadata_source_get_name (GRL_METADATA_SOURCE (grilo_source)), error->message);
659
source->priv->browse_got_results = TRUE;
660
source->priv->browse_position++;
663
if (media && GRL_IS_MEDIA_BOX (media)) {
666
if (source->priv->browse_container == NULL) {
667
/* insert at the end */
668
gtk_tree_store_insert_with_values (source->priv->browser_model,
672
0, g_object_ref (media),
673
1, grl_media_get_title (media),
674
2, CONTAINER_UNKNOWN_MEDIA,
679
/* insert before the expand marker row */
680
n = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (source->priv->browser_model),
681
&source->priv->browse_container_iter);
682
gtk_tree_store_insert_with_values (source->priv->browser_model,
684
&source->priv->browse_container_iter,
686
0, g_object_ref (media),
687
1, grl_media_get_title (media),
688
2, CONTAINER_UNKNOWN_MEDIA,
693
/* and insert an expand marker below it too */
694
gtk_tree_store_insert_with_values (source->priv->browser_model,
699
1, "...", /* needs to be translatable? */
700
2, CONTAINER_NO_MEDIA,
703
} else if (media && GRL_IS_MEDIA_AUDIO (media)) {
704
source->priv->browse_got_media = TRUE;
707
if (remaining == 0) {
708
source->priv->browse_op = 0;
709
if (source->priv->browse_got_results == FALSE &&
710
source->priv->browse_container != NULL) {
711
/* no more results for this container, so delete the marker row */
712
delete_marker_row (source, &source->priv->browse_container_iter);
714
set_container_type (source, &source->priv->browse_container_iter, source->priv->browse_got_media);
715
gtk_tree_store_set (source->priv->browser_model,
716
&source->priv->browse_container_iter,
719
} else if (source->priv->browse_container != NULL) {
720
if (source->priv->browse_position >= CONTAINER_GIVE_UP_POINT &&
721
gtk_tree_model_iter_n_children (GTK_TREE_MODEL (source->priv->browser_model),
722
&source->priv->browse_container_iter) == 1) {
723
/* no containers yet, so remove the marker row */
724
delete_marker_row (source, &source->priv->browse_container_iter);
726
/* store browse position for next time we want more */
727
gtk_tree_store_set (source->priv->browser_model,
728
&source->priv->browse_container_iter,
729
3, source->priv->browse_position,
731
maybe_expand_container (source);
734
} else if (source->priv->browse_got_results && source->priv->browse_container == NULL) {
735
/* get all top-level containers */
736
browse_next (source);
742
browse_next (RBGriloSource *source)
744
rb_debug ("next browse op for %s (%d)",
745
grl_metadata_source_get_name (GRL_METADATA_SOURCE (source->priv->grilo_source)),
746
source->priv->browse_position);
747
source->priv->browse_got_results = FALSE;
748
source->priv->browse_op = grl_media_source_browse (source->priv->grilo_source,
749
source->priv->browse_container,
750
source->priv->grilo_keys,
751
source->priv->browse_position,
752
CONTAINER_FETCH_SIZE,
754
(GrlMediaSourceResultCb) grilo_browse_cb,
759
start_browse (RBGriloSource *source, GrlMedia *container, GtkTreeIter *container_iter, int position)
761
rb_debug ("starting browse op for %s", grl_metadata_source_get_name (GRL_METADATA_SOURCE (source->priv->grilo_source)));
763
/* cancel existing operation? */
764
if (source->priv->browse_op != 0) {
765
grl_operation_cancel (source->priv->browse_op);
768
if (source->priv->browse_container != NULL) {
769
g_object_unref (source->priv->browse_container);
771
source->priv->browse_container = container;
772
if (container_iter != NULL) {
773
/* hrm, probably have to use row references here.. */
774
source->priv->browse_container_iter = *container_iter;
776
source->priv->browse_position = position;
777
source->priv->browse_got_media = FALSE;
779
browse_next (source);
784
static void media_browse_next (RBGriloSource *source);
787
grilo_media_browse_cb (GrlMediaSource *grilo_source, guint operation_id, GrlMedia *media, guint remaining, RBGriloSource *source, const GError *error)
789
if (operation_id != source->priv->media_browse_op) {
795
rb_debug ("got error for %s: %s",
796
grl_metadata_source_get_name (GRL_METADATA_SOURCE (grilo_source)),
801
GDK_THREADS_ENTER ();
803
source->priv->media_browse_got_results = TRUE;
804
source->priv->media_browse_position++;
806
if (GRL_IS_MEDIA_AUDIO (media)) {
807
RhythmDBEntry *entry;
808
entry = create_entry_for_media (source->priv->db,
809
source->priv->entry_type,
811
GRL_DATA (source->priv->browse_container));
813
rhythmdb_query_model_add_entry (source->priv->query_model, entry, -1);
815
} else if (GRL_IS_MEDIA_BOX (media)) {
816
source->priv->media_browse_got_containers = TRUE;
820
if (remaining == 0) {
821
source->priv->media_browse_op = 0;
823
if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (source->priv->query_model), NULL) == 0 &&
824
source->priv->media_browse_position >= CONTAINER_GIVE_UP_POINT) {
825
/* if we don't find any media within the first 100 or so results,
826
* assume this container doesn't have any. otherwise we'd load
827
* the entire thing when it got selected, which would suck.
829
rb_debug ("didn't find any media in %s, giving up", grl_media_get_title (source->priv->media_browse_container));
830
gtk_tree_store_set (source->priv->browser_model,
831
&source->priv->media_browse_container_iter,
832
2, CONTAINER_NO_MEDIA,
834
} else if (source->priv->media_browse_got_results) {
835
if (source->priv->media_browse_position < source->priv->media_browse_limit) {
836
media_browse_next (source);
840
text = g_strdup_printf (ngettext ("Only showing %d result",
841
"Only showing %d results",
842
source->priv->media_browse_position),
843
source->priv->media_browse_position);
844
gtk_label_set_text (GTK_LABEL (source->priv->info_bar_label), text);
847
gtk_widget_show (source->priv->info_bar);
849
} else if (source->priv->media_browse_got_containers == FALSE &&
850
source->priv->media_browse_container != NULL) {
851
/* make sure there's no marker row for this container */
852
delete_marker_row (source, &source->priv->media_browse_container_iter);
855
GDK_THREADS_LEAVE ();
859
media_browse_next (RBGriloSource *source)
861
rb_debug ("next media_browse op for %s (%d)",
862
grl_metadata_source_get_name (GRL_METADATA_SOURCE (source->priv->grilo_source)),
863
source->priv->media_browse_position);
865
source->priv->media_browse_got_results = FALSE;
866
if (source->priv->media_browse_container != NULL) {
867
source->priv->media_browse_op =
868
grl_media_source_browse (source->priv->grilo_source,
869
source->priv->media_browse_container,
870
source->priv->grilo_keys,
871
source->priv->media_browse_position,
872
CONTAINER_FETCH_SIZE,
874
(GrlMediaSourceResultCb) grilo_media_browse_cb,
877
source->priv->media_browse_op =
878
grl_media_source_search (source->priv->grilo_source,
879
source->priv->search_text,
880
source->priv->grilo_keys,
881
source->priv->media_browse_position,
882
CONTAINER_FETCH_SIZE,
884
(GrlMediaSourceResultCb) grilo_media_browse_cb,
890
start_media_browse (RBGriloSource *source, GrlMedia *container, GtkTreeIter *container_iter, guint limit)
892
rb_debug ("starting media browse for %s",
893
grl_metadata_source_get_name (GRL_METADATA_SOURCE (source->priv->grilo_source)));
895
/* cancel existing operation? */
896
if (source->priv->media_browse_op != 0) {
897
grl_operation_cancel (source->priv->media_browse_op);
900
if (source->priv->media_browse_container != NULL) {
901
g_object_unref (source->priv->media_browse_container);
903
source->priv->media_browse_container = container;
904
if (container_iter != NULL) {
905
/* hrm, probably have to use row references here.. */
906
source->priv->media_browse_container_iter = *container_iter;
908
source->priv->media_browse_position = 0;
909
source->priv->media_browse_limit = limit;
910
source->priv->media_browse_got_containers = FALSE;
912
if (source->priv->query_model != NULL) {
913
g_object_unref (source->priv->query_model);
915
source->priv->query_model = rhythmdb_query_model_new_empty (source->priv->db);
916
rb_entry_view_set_model (RB_ENTRY_VIEW (source->priv->entry_view), source->priv->query_model);
917
g_object_set (source, "query-model", source->priv->query_model, NULL);
919
media_browse_next (source);
923
fetch_more_cb (GtkInfoBar *bar, gint response, RBGriloSource *source)
925
if (response != GTK_RESPONSE_OK) {
929
gtk_widget_hide (GTK_WIDGET (bar));
930
source->priv->media_browse_limit += CONTAINER_MAX_TRACKS;
931
media_browse_next (source);
935
expand_from_marker (RBGriloSource *source, GtkTreeIter *iter)
937
/* this is a marker row, fetch more containers underneath the parent */
941
gtk_tree_model_iter_parent (GTK_TREE_MODEL (source->priv->browser_model), &browse, iter);
942
gtk_tree_model_get (GTK_TREE_MODEL (source->priv->browser_model),
948
start_browse (source, container, &browse, position);
956
browser_selection_changed_cb (GtkTreeSelection *selection, RBGriloSource *source)
962
gtk_widget_hide (GTK_WIDGET (source->priv->info_bar));
963
if (gtk_tree_selection_get_selected (selection, NULL, &iter) == FALSE) {
964
rb_debug ("nothing selected");
968
if (source->priv->search_entry != NULL) {
969
rb_search_entry_clear (source->priv->search_entry);
972
gtk_tree_model_get (GTK_TREE_MODEL (source->priv->browser_model), &iter,
977
if (container == NULL) {
978
expand_from_marker (source, &iter);
979
} else if (container_type != CONTAINER_NO_MEDIA) {
980
/* fetch media directly under this container */
981
start_media_browse (source, container, &iter, CONTAINER_MAX_TRACKS);
983
/* clear the track list? */
988
maybe_expand_container (RBGriloSource *source)
993
GtkTreeIter end_iter;
998
source->priv->maybe_expand_idle = 0;
1000
if (source->priv->browse_op != 0) {
1001
rb_debug ("not expanding, already browsing");
1005
/* if we find a visible marker row, find more results */
1006
if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (source->priv->browser_view), &path, &end) == FALSE) {
1007
rb_debug ("not expanding, nothing to expand");
1011
gtk_tree_model_get_iter (GTK_TREE_MODEL (source->priv->browser_model), &iter, path);
1012
gtk_tree_model_get_iter (GTK_TREE_MODEL (source->priv->browser_model), &end_iter, end);
1015
gtk_tree_path_free (path);
1016
path = gtk_tree_model_get_path (GTK_TREE_MODEL (source->priv->browser_model), &iter);
1017
last = (gtk_tree_path_compare (path, end) >= 0);
1018
gtk_tree_model_get (GTK_TREE_MODEL (source->priv->browser_model), &iter,
1021
if (container == NULL) {
1022
if (expand_from_marker (source, &iter)) {
1023
rb_debug ("expanding");
1029
if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (source->priv->browser_view), path) &&
1030
gtk_tree_model_iter_has_child (GTK_TREE_MODEL (source->priv->browser_model), &iter)) {
1031
gtk_tree_model_iter_children (GTK_TREE_MODEL (source->priv->browser_model), &iter, &next);
1032
} else if (gtk_tree_model_iter_next (GTK_TREE_MODEL (source->priv->browser_model), &next)) {
1035
if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (source->priv->browser_model), &next, &iter) == FALSE) {
1039
if (gtk_tree_model_iter_next (GTK_TREE_MODEL (source->priv->browser_model), &iter) == FALSE) {
1043
} while (last == FALSE);
1045
gtk_tree_path_free (path);
1046
gtk_tree_path_free (end);
1051
maybe_expand_container_idle (RBGriloSource *source)
1053
if (source->priv->maybe_expand_idle == 0) {
1054
source->priv->maybe_expand_idle = g_idle_add ((GSourceFunc)maybe_expand_container, source);
1059
browser_row_expanded_cb (GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, RBGriloSource *source)
1061
maybe_expand_container_idle (source);
1065
scroll_adjust_changed_cb (GtkAdjustment *adjustment, RBGriloSource *source)
1067
maybe_expand_container_idle (source);
1071
scroll_adjust_value_changed_cb (GtkAdjustment *adjustment, RBGriloSource *source)
1073
maybe_expand_container_idle (source);
1077
impl_selected (RBDisplayPage *page)
1079
RBGriloSource *source = RB_GRILO_SOURCE (page);
1081
RB_DISPLAY_PAGE_CLASS (rb_grilo_source_parent_class)->selected (page);
1083
if (source->priv->done_initial_browse == FALSE) {
1084
source->priv->done_initial_browse = TRUE;
1085
start_browse (source, NULL, NULL, 0);
1088
rb_search_entry_set_mnemonic (source->priv->search_entry, TRUE);
1092
impl_deselected (RBDisplayPage *page)
1094
RBGriloSource *source = RB_GRILO_SOURCE (page);
1096
RB_DISPLAY_PAGE_CLASS (rb_grilo_source_parent_class)->deselected (page);
1098
rb_search_entry_set_mnemonic (source->priv->search_entry, FALSE);
1101
static RBEntryView *
1102
impl_get_entry_view (RBSource *bsource)
1104
RBGriloSource *source = RB_GRILO_SOURCE (bsource);
1105
return source->priv->entry_view;
1109
search_cb (RBSearchEntry *search, const char *text, RBGriloSource *source)
1111
g_free (source->priv->search_text);
1112
source->priv->search_text = g_strdup (text);
1114
gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (source->priv->browser_view)));
1116
start_media_browse (source, NULL, NULL, CONTAINER_MAX_TRACKS);
1120
notify_sort_order_cb (GObject *object, GParamSpec *pspec, RBGriloSource *source)
1122
rb_entry_view_resort_model (RB_ENTRY_VIEW (object));