No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
4 * Copyright (C) 2003,2004 Colin Walters <walters@verbum.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * The Rhythmbox authors hereby grant permission for non-GPL compatible
12 * GStreamer plugins to be used and distributed together with GStreamer
13 * and Rhythmbox. This permission is above and beyond the permissions granted
14 * by the GPL license by which Rhythmbox is covered. If you modify this code
15 * you may extend this exception to your version of the code, but you are not
16 * obligated to do so. If you do not wish to do so, delete this exception
17 * statement from your version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
27 *
28 */
29
30 /**
31 * SECTION:rb-browser-source
32 * @short_description: base class for sources that include genre/artist/album browsers
33 *
34 * This class simplifies implementation of sources that include genre/artist/album browsers.
35 * It also handles searching (using the search box) and a few other UI niceties.
36 *
37 * Instances of browser sources will use a query that will match all entries of
38 * the entry type assigned to the source, so it's mostly suited for sources that
39 * have an entry type of their own.
40 */
41
42 #include "config.h"
43
44 #include <string.h>
45
46 #include <gtk/gtk.h>
47 #include <glib/gi18n.h>
48
49 #include "rb-source.h"
50 #include "rb-library-source.h"
51 #include "rb-source-search-basic.h"
52
53 #include "rhythmdb-query-model.h"
54 #include "rb-property-view.h"
55 #include "rb-entry-view.h"
56 #include "rb-library-browser.h"
57 #include "rb-util.h"
58 #include "rb-file-helpers.h"
59 #include "rb-dialog.h"
60 #include "rb-debug.h"
61 #include "rb-song-info.h"
62 #include "rb-search-entry.h"
63 #include "rb-source-toolbar.h"
64 #include "rb-shell-preferences.h"
65
66 static void rb_browser_source_class_init (RBBrowserSourceClass *klass);
67 static void rb_browser_source_init (RBBrowserSource *source);
68 static void rb_browser_source_constructed (GObject *object);
69 static void rb_browser_source_dispose (GObject *object);
70 static void rb_browser_source_finalize (GObject *object);
71 static void rb_browser_source_set_property (GObject *object,
72 guint prop_id,
73 const GValue *value,
74 GParamSpec *pspec);
75 static void rb_browser_source_get_property (GObject *object,
76 guint prop_id,
77 GValue *value,
78 GParamSpec *pspec);
79 static void rb_browser_source_cmd_choose_genre (GtkAction *action, RBSource *source);
80 static void rb_browser_source_cmd_choose_artist (GtkAction *action, RBSource *source);
81 static void rb_browser_source_cmd_choose_album (GtkAction *action, RBSource *source);
82 static void songs_view_sort_order_changed_cb (GObject *object, GParamSpec *pspec, RBBrowserSource *source);
83 static void rb_browser_source_browser_changed_cb (RBLibraryBrowser *entry,
84 GParamSpec *param,
85 RBBrowserSource *source);
86
87 /* source methods */
88 static RBEntryView *impl_get_entry_view (RBSource *source);
89 static GList *impl_get_property_views (RBSource *source);
90 static void impl_delete (RBSource *source);
91 static void impl_search (RBSource *source, RBSourceSearch *search, const char *cur_text, const char *new_text);
92 static void impl_reset_filters (RBSource *source);
93 static void impl_song_properties (RBSource *source);
94 static void default_show_entry_popup (RBBrowserSource *source);
95 static void default_pack_content (RBBrowserSource *source, GtkWidget *content);
96
97 void rb_browser_source_browser_views_activated_cb (GtkWidget *widget,
98 RBBrowserSource *source);
99 static void songs_view_drag_data_received_cb (GtkWidget *widget,
100 GdkDragContext *dc,
101 gint x, gint y,
102 GtkSelectionData *data,
103 guint info, guint time,
104 RBBrowserSource *source);
105 static void rb_browser_source_do_query (RBBrowserSource *source,
106 gboolean subset);
107 static void rb_browser_source_populate (RBBrowserSource *source);
108
109 struct RBBrowserSourcePrivate
110 {
111 RhythmDB *db;
112
113 RBLibraryBrowser *browser;
114 RBEntryView *songs;
115 RBSourceToolbar *toolbar;
116
117 RhythmDBQueryModel *cached_all_query;
118 RhythmDBQuery *search_query;
119 RhythmDBPropType search_prop;
120 gboolean populate;
121 gboolean query_active;
122 gboolean search_on_completion;
123 RBSourceSearch *default_search;
124
125 GtkActionGroup *action_group;
126 GtkActionGroup *search_action_group;
127
128 gboolean dispose_has_run;
129 };
130
131 #define RB_BROWSER_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_BROWSER_SOURCE, RBBrowserSourcePrivate))
132
133 static GtkActionEntry rb_browser_source_actions [] =
134 {
135 { "BrowserSrcChooseGenre", NULL, N_("Browse This _Genre"), NULL,
136 N_("Set the browser to view only this genre"),
137 G_CALLBACK (rb_browser_source_cmd_choose_genre) },
138 { "BrowserSrcChooseArtist", NULL , N_("Browse This _Artist"), NULL,
139 N_("Set the browser to view only this artist"),
140 G_CALLBACK (rb_browser_source_cmd_choose_artist) },
141 { "BrowserSrcChooseAlbum", NULL, N_("Browse This A_lbum"), NULL,
142 N_("Set the browser to view only this album"),
143 G_CALLBACK (rb_browser_source_cmd_choose_album) }
144 };
145
146 static GtkRadioActionEntry rb_browser_source_radio_actions [] =
147 {
148 { "BrowserSourceSearchAll", NULL, N_("Search all fields"), NULL, NULL, RHYTHMDB_PROP_SEARCH_MATCH },
149 { "BrowserSourceSearchArtists", NULL, N_("Search artists"), NULL, NULL, RHYTHMDB_PROP_ARTIST_FOLDED },
150 { "BrowserSourceSearchAlbums", NULL, N_("Search albums"), NULL, NULL, RHYTHMDB_PROP_ALBUM_FOLDED },
151 { "BrowserSourceSearchTitles", NULL, N_("Search titles"), NULL, NULL, RHYTHMDB_PROP_TITLE_FOLDED }
152 };
153
154 static const GtkTargetEntry songs_view_drag_types[] = {
155 { "application/x-rhythmbox-entry", 0, 0 },
156 { "text/uri-list", 0, 1 }
157 };
158
159 enum
160 {
161 PROP_0,
162 PROP_BASE_QUERY_MODEL,
163 PROP_POPULATE,
164 PROP_SHOW_BROWSER
165 };
166
167 G_DEFINE_ABSTRACT_TYPE (RBBrowserSource, rb_browser_source, RB_TYPE_SOURCE)
168
169 static void
170 rb_browser_source_class_init (RBBrowserSourceClass *klass)
171 {
172 GObjectClass *object_class = G_OBJECT_CLASS (klass);
173 RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
174
175 object_class->dispose = rb_browser_source_dispose;
176 object_class->finalize = rb_browser_source_finalize;
177 object_class->constructed = rb_browser_source_constructed;
178
179 object_class->set_property = rb_browser_source_set_property;
180 object_class->get_property = rb_browser_source_get_property;
181
182 source_class->impl_search = impl_search;
183 source_class->impl_get_entry_view = impl_get_entry_view;
184 source_class->impl_get_property_views = impl_get_property_views;
185 source_class->impl_reset_filters = impl_reset_filters;
186 source_class->impl_song_properties = impl_song_properties;
187 source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
188 source_class->impl_can_copy = (RBSourceFeatureFunc) rb_true_function;
189 source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
190 source_class->impl_can_add_to_queue = (RBSourceFeatureFunc) rb_true_function;
191 source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_true_function;
192 source_class->impl_delete = impl_delete;
193
194 klass->pack_content = default_pack_content;
195 klass->has_drop_support = (RBBrowserSourceFeatureFunc) rb_false_function;
196 klass->show_entry_popup = default_show_entry_popup;
197
198 g_object_class_override_property (object_class,
199 PROP_BASE_QUERY_MODEL,
200 "base-query-model");
201
202 g_object_class_install_property (object_class,
203 PROP_POPULATE,
204 g_param_spec_boolean ("populate",
205 "populate",
206 "whether to populate the source",
207 TRUE,
208 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
209
210 g_object_class_override_property (object_class,
211 PROP_SHOW_BROWSER,
212 "show-browser");
213
214 g_type_class_add_private (klass, sizeof (RBBrowserSourcePrivate));
215 }
216
217 static void
218 rb_browser_source_init (RBBrowserSource *source)
219 {
220 source->priv = RB_BROWSER_SOURCE_GET_PRIVATE (source);
221 }
222
223 static void
224 rb_browser_source_dispose (GObject *object)
225 {
226 RBBrowserSource *source;
227 source = RB_BROWSER_SOURCE (object);
228
229 if (source->priv->dispose_has_run) {
230 /* If dispose did already run, return. */
231 return;
232 }
233 /* Make sure dispose does not run twice. */
234 source->priv->dispose_has_run = TRUE;
235
236 if (source->priv->db != NULL) {
237 g_object_unref (source->priv->db);
238 source->priv->db = NULL;
239 }
240
241 if (source->priv->search_query != NULL) {
242 rhythmdb_query_free (source->priv->search_query);
243 source->priv->search_query = NULL;
244 }
245
246 if (source->priv->cached_all_query != NULL) {
247 g_object_unref (source->priv->cached_all_query);
248 source->priv->cached_all_query = NULL;
249 }
250
251 if (source->priv->action_group != NULL) {
252 g_object_unref (source->priv->action_group);
253 source->priv->action_group = NULL;
254 }
255
256 if (source->priv->default_search != NULL) {
257 g_object_unref (source->priv->default_search);
258 source->priv->default_search = NULL;
259 }
260
261 G_OBJECT_CLASS (rb_browser_source_parent_class)->dispose (object);
262 }
263
264 static void
265 rb_browser_source_finalize (GObject *object)
266 {
267 RBBrowserSource *source;
268
269 g_return_if_fail (object != NULL);
270 g_return_if_fail (RB_IS_BROWSER_SOURCE (object));
271
272 source = RB_BROWSER_SOURCE (object);
273
274 g_return_if_fail (source->priv != NULL);
275
276 G_OBJECT_CLASS (rb_browser_source_parent_class)->finalize (object);
277 }
278
279 static void
280 rb_browser_source_songs_show_popup_cb (RBEntryView *view,
281 gboolean over_entry,
282 RBBrowserSource *source)
283 {
284 if (over_entry) {
285 RBBrowserSourceClass *klass = RB_BROWSER_SOURCE_GET_CLASS (source);
286
287 klass->show_entry_popup (source);
288 } else {
289 rb_display_page_show_popup (RB_DISPLAY_PAGE (source));
290 }
291 }
292
293 static void
294 default_show_entry_popup (RBBrowserSource *source)
295 {
296 _rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/BrowserSourceViewPopup");
297 }
298
299 static void
300 rb_browser_source_constructed (GObject *object)
301 {
302 RBBrowserSource *source;
303 RBBrowserSourceClass *klass;
304 RBShell *shell;
305 GObject *shell_player;
306 GtkUIManager *ui_manager;
307 RhythmDBEntryType *entry_type;
308 GtkWidget *content;
309 GtkWidget *paned;
310
311 RB_CHAIN_GOBJECT_METHOD (rb_browser_source_parent_class, constructed, object);
312
313 source = RB_BROWSER_SOURCE (object);
314
315 g_object_get (source,
316 "shell", &shell,
317 "entry-type", &entry_type,
318 NULL);
319 g_object_get (shell,
320 "db", &source->priv->db,
321 "shell-player", &shell_player,
322 "ui-manager", &ui_manager,
323 NULL);
324
325 source->priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
326 "BrowserSourceActions",
327 NULL, 0, NULL);
328 _rb_action_group_add_display_page_actions (source->priv->action_group,
329 G_OBJECT (shell),
330 rb_browser_source_actions,
331 G_N_ELEMENTS (rb_browser_source_actions));
332
333 /* only add the actions if we haven't already */
334 if (gtk_action_group_get_action (source->priv->action_group,
335 rb_browser_source_radio_actions[0].name) == NULL) {
336 gtk_action_group_add_radio_actions (source->priv->action_group,
337 rb_browser_source_radio_actions,
338 G_N_ELEMENTS (rb_browser_source_radio_actions),
339 0,
340 NULL,
341 NULL);
342
343 rb_source_search_basic_create_for_actions (source->priv->action_group,
344 rb_browser_source_radio_actions,
345 G_N_ELEMENTS (rb_browser_source_radio_actions));
346 }
347 g_object_unref (shell);
348
349 source->priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH);
350
351 paned = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
352
353 source->priv->browser = rb_library_browser_new (source->priv->db, entry_type);
354 gtk_widget_set_no_show_all (GTK_WIDGET (source->priv->browser), TRUE);
355 gtk_paned_pack1 (GTK_PANED (paned), GTK_WIDGET (source->priv->browser), TRUE, FALSE);
356 gtk_container_child_set (GTK_CONTAINER (paned),
357 GTK_WIDGET (source->priv->browser),
358 "resize", FALSE,
359 NULL);
360 g_signal_connect_object (G_OBJECT (source->priv->browser), "notify::output-model",
361 G_CALLBACK (rb_browser_source_browser_changed_cb),
362 source, 0);
363
364 /* set up songs tree view */
365 source->priv->songs = rb_entry_view_new (source->priv->db, shell_player,
366 TRUE, FALSE);
367
368 rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_TRACK_NUMBER, FALSE);
369 rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_TITLE, TRUE);
370 rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_GENRE, FALSE);
371 rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_ARTIST, FALSE);
372 rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_ALBUM, FALSE);
373 rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_YEAR, FALSE);
374 rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_DURATION, FALSE);
375 rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_QUALITY, FALSE);
376 rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_PLAY_COUNT, FALSE);
377 rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_BPM, FALSE);
378 rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_COMMENT, FALSE);
379 rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_LOCATION, FALSE);
380
381 g_signal_connect_object (G_OBJECT (source->priv->songs), "show_popup",
382 G_CALLBACK (rb_browser_source_songs_show_popup_cb), source, 0);
383 g_signal_connect_object (source->priv->songs,
384 "notify::sort-order",
385 G_CALLBACK (songs_view_sort_order_changed_cb),
386 source, 0);
387
388 rb_source_bind_settings (RB_SOURCE (source),
389 GTK_WIDGET (source->priv->songs),
390 paned,
391 GTK_WIDGET (source->priv->browser));
392
393 if (rb_browser_source_has_drop_support (source)) {
394 gtk_drag_dest_set (GTK_WIDGET (source->priv->songs),
395 GTK_DEST_DEFAULT_ALL,
396 songs_view_drag_types, G_N_ELEMENTS (songs_view_drag_types),
397 GDK_ACTION_COPY | GDK_ACTION_MOVE); /* really accept move actions? */
398
399 /* set up drag and drop for the song tree view.
400 * we don't use RBEntryView's DnD support because it does too much.
401 * we just want to be able to drop songs in to add them to the
402 * library.
403 */
404 g_signal_connect_object (G_OBJECT (source->priv->songs),
405 "drag_data_received",
406 G_CALLBACK (songs_view_drag_data_received_cb),
407 source, 0);
408 }
409
410 gtk_paned_pack2 (GTK_PANED (paned), GTK_WIDGET (source->priv->songs), TRUE, FALSE);
411
412 /* set up toolbar */
413 source->priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), ui_manager);
414 rb_source_toolbar_add_search_entry (source->priv->toolbar, "/BrowserSourceSearchMenu", NULL);
415
416 content = gtk_grid_new ();
417 gtk_grid_set_column_spacing (GTK_GRID (content), 6);
418 gtk_grid_set_row_spacing (GTK_GRID (content), 6);
419 gtk_widget_set_margin_top (content, 6);
420 gtk_grid_attach (GTK_GRID (content), GTK_WIDGET (source->priv->toolbar), 0, 0, 1, 1);
421 gtk_widget_set_vexpand (paned, TRUE);
422 gtk_widget_set_hexpand (paned, TRUE);
423 gtk_grid_attach (GTK_GRID (content), paned, 0, 1, 1, 1);
424
425 klass = RB_BROWSER_SOURCE_GET_CLASS (source);
426 klass->pack_content (source, content);
427
428 gtk_widget_show_all (GTK_WIDGET (source));
429
430 /* use a throwaway model until the real one is ready */
431 rb_library_browser_set_model (source->priv->browser,
432 rhythmdb_query_model_new_empty (source->priv->db),
433 FALSE);
434
435 source->priv->cached_all_query = rhythmdb_query_model_new_empty (source->priv->db);
436 rb_browser_source_populate (source);
437
438 g_object_unref (entry_type);
439 g_object_unref (shell_player);
440 }
441
442 static void
443 rb_browser_source_set_property (GObject *object,
444 guint prop_id,
445 const GValue *value,
446 GParamSpec *pspec)
447 {
448 RBBrowserSource *source = RB_BROWSER_SOURCE (object);
449
450 switch (prop_id) {
451 case PROP_POPULATE:
452 source->priv->populate = g_value_get_boolean (value);
453
454 /* if being set after construction, run the query now. otherwise the constructor will do it. */
455 if (source->priv->songs != NULL) {
456 rb_browser_source_populate (source);
457 }
458 break;
459 case PROP_SHOW_BROWSER:
460 if (g_value_get_boolean (value)) {
461 gtk_widget_show (GTK_WIDGET (source->priv->browser));
462 } else {
463 gtk_widget_hide (GTK_WIDGET (source->priv->browser));
464 rb_library_browser_reset (source->priv->browser);
465 }
466 break;
467 default:
468 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
469 break;
470 }
471 }
472
473 static void
474 rb_browser_source_get_property (GObject *object,
475 guint prop_id,
476 GValue *value,
477 GParamSpec *pspec)
478 {
479 RBBrowserSource *source = RB_BROWSER_SOURCE (object);
480
481 switch (prop_id) {
482 case PROP_BASE_QUERY_MODEL:
483 g_value_set_object (value, source->priv->cached_all_query);
484 break;
485 case PROP_POPULATE:
486 g_value_set_boolean (value, source->priv->populate);
487 break;
488 case PROP_SHOW_BROWSER:
489 g_value_set_boolean (value, gtk_widget_get_visible (GTK_WIDGET (source->priv->browser)));
490 break;
491 default:
492 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
493 break;
494 }
495 }
496
497 static void
498 cached_all_query_complete_cb (RhythmDBQueryModel *model, RBBrowserSource *source)
499 {
500 rb_library_browser_set_model (source->priv->browser,
501 source->priv->cached_all_query,
502 FALSE);
503 }
504
505 static void
506 rb_browser_source_populate (RBBrowserSource *source)
507 {
508 RhythmDBEntryType *entry_type;
509
510 if (source->priv->populate == FALSE)
511 return;
512
513 /* only connect the model to the browser when it's complete. this avoids
514 * thousands of row-added signals, which is ridiculously slow with a11y enabled.
515 */
516 g_signal_connect_object (source->priv->cached_all_query,
517 "complete",
518 G_CALLBACK (cached_all_query_complete_cb),
519 source, 0);
520
521 g_object_get (source, "entry-type", &entry_type, NULL);
522 rhythmdb_do_full_query_async (source->priv->db,
523 RHYTHMDB_QUERY_RESULTS (source->priv->cached_all_query),
524 RHYTHMDB_QUERY_PROP_EQUALS, RHYTHMDB_PROP_TYPE, entry_type,
525 RHYTHMDB_QUERY_END);
526 g_object_unref (entry_type);
527 }
528
529 static void
530 browse_property (RBBrowserSource *source, RhythmDBPropType prop)
531 {
532 GList *props;
533 RBPropertyView *view;
534
535 props = rb_source_gather_selected_properties (RB_SOURCE (source), prop);
536 view = rb_library_browser_get_property_view (source->priv->browser, prop);
537 if (view) {
538 rb_property_view_set_selection (view, props);
539 }
540
541 rb_list_deep_free (props);
542 }
543
544 static void
545 rb_browser_source_cmd_choose_genre (GtkAction *action, RBSource *source)
546 {
547 rb_debug ("choosing genre");
548
549 if (RB_IS_BROWSER_SOURCE (source)) {
550 browse_property (RB_BROWSER_SOURCE (source), RHYTHMDB_PROP_GENRE);
551 }
552 }
553
554 static void
555 rb_browser_source_cmd_choose_artist (GtkAction *action, RBSource *source)
556 {
557 rb_debug ("choosing artist");
558
559 if (RB_IS_BROWSER_SOURCE (source)) {
560 browse_property (RB_BROWSER_SOURCE (source), RHYTHMDB_PROP_ARTIST);
561 }
562 }
563
564 static void
565 rb_browser_source_cmd_choose_album (GtkAction *action, RBSource *source)
566 {
567 rb_debug ("choosing album");
568
569 if (RB_IS_BROWSER_SOURCE (source)) {
570 browse_property (RB_BROWSER_SOURCE (source), RHYTHMDB_PROP_ALBUM);
571 }
572 }
573
574 static void
575 songs_view_sort_order_changed_cb (GObject *object, GParamSpec *pspec, RBBrowserSource *source)
576 {
577 rb_debug ("sort order changed");
578 rb_entry_view_resort_model (RB_ENTRY_VIEW (object));
579 }
580
581 static void
582 impl_search (RBSource *asource, RBSourceSearch *search, const char *cur_text, const char *new_text)
583 {
584 RBBrowserSource *source = RB_BROWSER_SOURCE (asource);
585 gboolean subset;
586
587 if (search == NULL) {
588 search = source->priv->default_search;
589 }
590
591 /* replace our search query */
592 if (source->priv->search_query != NULL) {
593 rhythmdb_query_free (source->priv->search_query);
594 source->priv->search_query = NULL;
595 }
596 source->priv->search_query = rb_source_search_create_query (search, source->priv->db, new_text);
597
598 /* for subset searches, we have to wait until the query
599 * has finished before we can refine the results.
600 */
601 subset = rb_source_search_is_subset (search, cur_text, new_text);
602 if (source->priv->query_active && subset) {
603 rb_debug ("deferring search for \"%s\" until query completion", new_text ? new_text : "<NULL>");
604 source->priv->search_on_completion = TRUE;
605 } else {
606 rb_debug ("doing search for \"%s\"", new_text ? new_text : "<NULL>");
607 rb_browser_source_do_query (source, subset);
608 }
609 }
610
611 static RBEntryView *
612 impl_get_entry_view (RBSource *asource)
613 {
614 RBBrowserSource *source = RB_BROWSER_SOURCE (asource);
615
616 return source->priv->songs;
617 }
618
619 static GList *
620 impl_get_property_views (RBSource *asource)
621 {
622 GList *ret;
623 RBBrowserSource *source = RB_BROWSER_SOURCE (asource);
624
625 ret = rb_library_browser_get_property_views (source->priv->browser);
626 return ret;
627 }
628
629 static void
630 impl_reset_filters (RBSource *asource)
631 {
632 RBBrowserSource *source = RB_BROWSER_SOURCE (asource);
633 gboolean changed = FALSE;
634
635 rb_debug ("Resetting search filters");
636
637 if (rb_library_browser_reset (source->priv->browser))
638 changed = TRUE;
639
640 if (source->priv->search_query != NULL) {
641 rhythmdb_query_free (source->priv->search_query);
642 source->priv->search_query = NULL;
643 changed = TRUE;
644 }
645
646 rb_source_toolbar_clear_search_entry (source->priv->toolbar);
647
648 if (changed)
649 rb_browser_source_do_query (source, FALSE);
650 }
651
652 static void
653 impl_delete (RBSource *asource)
654 {
655 RBBrowserSource *source = RB_BROWSER_SOURCE (asource);
656 GList *sel, *tem;
657
658 sel = rb_entry_view_get_selected_entries (source->priv->songs);
659 for (tem = sel; tem != NULL; tem = tem->next) {
660 rhythmdb_entry_delete (source->priv->db, tem->data);
661 rhythmdb_commit (source->priv->db);
662 }
663 g_list_foreach (sel, (GFunc)rhythmdb_entry_unref, NULL);
664 g_list_free (sel);
665 }
666
667 static void
668 impl_song_properties (RBSource *asource)
669 {
670 RBBrowserSource *source = RB_BROWSER_SOURCE (asource);
671 GtkWidget *song_info = NULL;
672
673 g_return_if_fail (source->priv->songs != NULL);
674
675 song_info = rb_song_info_new (asource, NULL);
676
677 g_return_if_fail (song_info != NULL);
678
679 if (song_info)
680 gtk_widget_show_all (song_info);
681 else
682 rb_debug ("failed to create dialog, or no selection!");
683 }
684
685 /**
686 * rb_browser_source_has_drop_support:
687 * @source: a #RBBrowserSource
688 *
689 * This is a virtual method that should be implemented by subclasses. It returns %TRUE
690 * if drag and drop target support for the source should be activated.
691 *
692 * Return value: %TRUE if drop support should be activated
693 */
694 gboolean
695 rb_browser_source_has_drop_support (RBBrowserSource *source)
696 {
697 RBBrowserSourceClass *klass = RB_BROWSER_SOURCE_GET_CLASS (source);
698
699 return klass->has_drop_support (source);
700 }
701
702 static void
703 songs_view_drag_data_received_cb (GtkWidget *widget,
704 GdkDragContext *dc,
705 gint x, gint y,
706 GtkSelectionData *selection_data,
707 guint info, guint time,
708 RBBrowserSource *source)
709 {
710 rb_debug ("data dropped on the library source song view");
711 rb_display_page_receive_drag (RB_DISPLAY_PAGE (source), selection_data);
712 }
713
714 static void
715 rb_browser_source_browser_changed_cb (RBLibraryBrowser *browser,
716 GParamSpec *pspec,
717 RBBrowserSource *source)
718 {
719 RhythmDBQueryModel *query_model;
720
721 g_object_get (browser, "output-model", &query_model, NULL);
722 rb_entry_view_set_model (source->priv->songs, query_model);
723 g_object_set (source, "query-model", query_model, NULL);
724 g_object_unref (query_model);
725
726 rb_source_notify_filter_changed (RB_SOURCE (source));
727 }
728
729 static void
730 rb_browser_source_query_complete_cb (RhythmDBQueryModel *query_model,
731 RBBrowserSource *source)
732 {
733 rb_library_browser_set_model (source->priv->browser, query_model, FALSE);
734
735 source->priv->query_active = FALSE;
736 if (source->priv->search_on_completion) {
737 rb_debug ("performing deferred search");
738 source->priv->search_on_completion = FALSE;
739 /* this is only done for subset queries */
740 rb_browser_source_do_query (source, TRUE);
741 }
742 }
743
744 static void
745 rb_browser_source_do_query (RBBrowserSource *source, gboolean subset)
746 {
747 RhythmDBQueryModel *query_model;
748 GPtrArray *query;
749 RhythmDBEntryType *entry_type;
750
751 /* use the cached 'all' query to optimise the no-search case */
752 if (source->priv->search_query == NULL) {
753 rb_library_browser_set_model (source->priv->browser,
754 source->priv->cached_all_query,
755 FALSE);
756 return;
757 }
758
759 g_object_get (source, "entry-type", &entry_type, NULL);
760 query = rhythmdb_query_parse (source->priv->db,
761 RHYTHMDB_QUERY_PROP_EQUALS,
762 RHYTHMDB_PROP_TYPE,
763 entry_type,
764 RHYTHMDB_QUERY_SUBQUERY,
765 source->priv->search_query,
766 RHYTHMDB_QUERY_END);
767 g_object_unref (entry_type);
768
769 if (subset) {
770 /* if we're appending text to an existing search string, the results will be a subset
771 * of the existing results, so rather than doing a whole new query, we can copy the
772 * results to a new query model with a more restrictive query.
773 */
774 RhythmDBQueryModel *old;
775 g_object_get (source->priv->browser, "input-model", &old, NULL);
776
777 query_model = rhythmdb_query_model_new_empty (source->priv->db);
778 g_object_set (query_model, "query", query, NULL);
779 rhythmdb_query_model_copy_contents (query_model, old);
780 g_object_unref (old);
781
782 rb_library_browser_set_model (source->priv->browser, query_model, FALSE);
783 g_object_unref (query_model);
784
785 } else {
786 /* otherwise build a query based on the search text, and feed it to the browser
787 * when the query finishes.
788 */
789 query_model = rhythmdb_query_model_new_empty (source->priv->db);
790 source->priv->query_active = TRUE;
791 source->priv->search_on_completion = FALSE;
792 g_signal_connect_object (query_model,
793 "complete", G_CALLBACK (rb_browser_source_query_complete_cb),
794 source, 0);
795 rhythmdb_do_full_query_async_parsed (source->priv->db,
796 RHYTHMDB_QUERY_RESULTS (query_model),
797 query);
798 g_object_unref (query_model);
799 }
800
801 rhythmdb_query_free (query);
802 }
803
804 static void
805 default_pack_content (RBBrowserSource *source, GtkWidget *content)
806 {
807 gtk_container_add (GTK_CONTAINER (source), content);
808 }