Location | Tool | Test ID | Function | Issue |
---|---|---|---|---|
rb-auto-playlist-source.c:673:26 | clang-analyzer | Access to field 'data' results in a dereference of a null pointer (loaded from variable 'limit_value') | ||
rb-auto-playlist-source.c:673:26 | clang-analyzer | Access to field 'data' results in a dereference of a null pointer (loaded from variable 'limit_value') | ||
rb-auto-playlist-source.c:685:27 | clang-analyzer | Access to field 'data' results in a dereference of a null pointer (loaded from variable 'limit_value') | ||
rb-auto-playlist-source.c:685:27 | clang-analyzer | Access to field 'data' results in a dereference of a null pointer (loaded from variable 'limit_value') |
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 <string.h>
33 #include <libxml/tree.h>
34 #include <glib/gi18n.h>
35 #include <gtk/gtk.h>
36
37 #include "rb-auto-playlist-source.h"
38 #include "rb-library-browser.h"
39 #include "rb-util.h"
40 #include "rb-debug.h"
41 #include "rb-stock-icons.h"
42 #include "rb-playlist-xml.h"
43 #include "rb-source-search-basic.h"
44 #include "rb-source-toolbar.h"
45
46 /**
47 * SECTION:rb-auto-playlist-source
48 * @short_description: automatic playlist source, based on a database query
49 *
50 * A playlist populated with the results of a database query.
51 *
52 * The query, limit, and sort settings are saved to the playlists file, so
53 * they are persistent.
54 *
55 * Searching is implemented by appending the query criteria generated from
56 * the search text to the query. Browsing is implemented by using the base
57 * query model (or a query model using the query generated from the search text,
58 * there is some) as the input to a #RBLibraryBrowser.
59 *
60 * If the user has not set a sort order as part of the playlist definition,
61 * the entry view columns are made clickable to allow the user to sort the
62 * results.
63 */
64
65 static void rb_auto_playlist_source_constructed (GObject *object);
66 static void rb_auto_playlist_source_dispose (GObject *object);
67 static void rb_auto_playlist_source_finalize (GObject *object);
68 static void rb_auto_playlist_source_set_property (GObject *object,
69 guint prop_id,
70 const GValue *value,
71 GParamSpec *pspec);
72 static void rb_auto_playlist_source_get_property (GObject *object,
73 guint prop_id,
74 GValue *value,
75 GParamSpec *pspec);
76
77 /* source methods */
78 static gboolean impl_show_popup (RBDisplayPage *page);
79 static gboolean impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data);
80 static void impl_search (RBSource *source, RBSourceSearch *search, const char *cur_text, const char *new_text);
81 static void impl_reset_filters (RBSource *asource);
82
83 /* playlist methods */
84 static void impl_save_contents_to_xml (RBPlaylistSource *source,
85 xmlNodePtr node);
86
87 static void rb_auto_playlist_source_songs_sort_order_changed_cb (GObject *object,
88 GParamSpec *pspec,
89 RBAutoPlaylistSource *source);
90 static void rb_auto_playlist_source_do_query (RBAutoPlaylistSource *source,
91 gboolean subset);
92
93 /* browser stuff */
94 static GList *impl_get_property_views (RBSource *source);
95 void rb_auto_playlist_source_browser_views_activated_cb (GtkWidget *widget,
96 RBAutoPlaylistSource *source);
97 static void rb_auto_playlist_source_browser_changed_cb (RBLibraryBrowser *entry,
98 GParamSpec *pspec,
99 RBAutoPlaylistSource *source);
100
101 static GtkRadioActionEntry rb_auto_playlist_source_radio_actions [] =
102 {
103 { "AutoPlaylistSearchAll", NULL, N_("Search all fields"), NULL, NULL, RHYTHMDB_PROP_SEARCH_MATCH },
104 { "AutoPlaylistSearchArtists", NULL, N_("Search artists"), NULL, NULL, RHYTHMDB_PROP_ARTIST_FOLDED },
105 { "AutoPlaylistSearchAlbums", NULL, N_("Search albums"), NULL, NULL, RHYTHMDB_PROP_ALBUM_FOLDED },
106 { "AutoPlaylistSearchTitles", NULL, N_("Search titles"), NULL, NULL, RHYTHMDB_PROP_TITLE_FOLDED }
107 };
108
109 enum
110 {
111 PROP_0,
112 PROP_BASE_QUERY_MODEL,
113 PROP_SHOW_BROWSER
114 };
115
116 #define AUTO_PLAYLIST_SOURCE_POPUP_PATH "/AutoPlaylistSourcePopup"
117
118 typedef struct _RBAutoPlaylistSourcePrivate RBAutoPlaylistSourcePrivate;
119
120 struct _RBAutoPlaylistSourcePrivate
121 {
122 RhythmDBQueryModel *cached_all_query;
123 GPtrArray *query;
124 gboolean query_resetting;
125 RhythmDBQueryModelLimitType limit_type;
126 GArray *limit_value;
127
128 gboolean query_active;
129 gboolean search_on_completion;
130
131 GtkWidget *paned;
132 RBLibraryBrowser *browser;
133 RBSourceToolbar *toolbar;
134
135 RBSourceSearch *default_search;
136 RhythmDBQuery *search_query;
137 };
138
139 static gpointer playlist_pixbuf = NULL;
140
141 G_DEFINE_TYPE (RBAutoPlaylistSource, rb_auto_playlist_source, RB_TYPE_PLAYLIST_SOURCE)
142 #define GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), RB_TYPE_AUTO_PLAYLIST_SOURCE, RBAutoPlaylistSourcePrivate))
143
144 static void
145 rb_auto_playlist_source_class_init (RBAutoPlaylistSourceClass *klass)
146 {
147 GObjectClass *object_class = G_OBJECT_CLASS (klass);
148 RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
149 RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
150 RBPlaylistSourceClass *playlist_class = RB_PLAYLIST_SOURCE_CLASS (klass);
151
152 object_class->constructed = rb_auto_playlist_source_constructed;
153 object_class->dispose = rb_auto_playlist_source_dispose;
154 object_class->finalize = rb_auto_playlist_source_finalize;
155 object_class->set_property = rb_auto_playlist_source_set_property;
156 object_class->get_property = rb_auto_playlist_source_get_property;
157
158 page_class->show_popup = impl_show_popup;
159 page_class->receive_drag = impl_receive_drag;
160
161 source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
162 source_class->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;
163 source_class->impl_search = impl_search;
164 source_class->impl_reset_filters = impl_reset_filters;
165 source_class->impl_get_property_views = impl_get_property_views;
166
167 playlist_class->impl_save_contents_to_xml = impl_save_contents_to_xml;
168
169 g_object_class_override_property (object_class, PROP_BASE_QUERY_MODEL, "base-query-model");
170 g_object_class_override_property (object_class, PROP_SHOW_BROWSER, "show-browser");
171
172 g_type_class_add_private (klass, sizeof (RBAutoPlaylistSourcePrivate));
173 }
174
175 static void
176 set_playlist_pixbuf (RBAutoPlaylistSource *source)
177 {
178 if (playlist_pixbuf == NULL) {
179 gint size;
180
181 gtk_icon_size_lookup (RB_SOURCE_ICON_SIZE, &size, NULL);
182 playlist_pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
183 RB_STOCK_AUTO_PLAYLIST,
184 size,
185 0, NULL);
186 if (playlist_pixbuf) {
187 g_object_add_weak_pointer (playlist_pixbuf,
188 (gpointer *) &playlist_pixbuf);
189
190 g_object_set (source, "pixbuf", playlist_pixbuf, NULL);
191 g_object_unref (playlist_pixbuf);
192 }
193 } else {
194 g_object_set (source, "pixbuf", playlist_pixbuf, NULL);
195 }
196 }
197
198 static void
199 rb_auto_playlist_source_init (RBAutoPlaylistSource *source)
200 {
201 }
202
203 static void
204 rb_auto_playlist_source_dispose (GObject *object)
205 {
206 RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (object);
207
208 if (priv->cached_all_query != NULL) {
209 g_object_unref (priv->cached_all_query);
210 priv->cached_all_query = NULL;
211 }
212
213 if (priv->default_search != NULL) {
214 g_object_unref (priv->default_search);
215 priv->default_search = NULL;
216 }
217
218 G_OBJECT_CLASS (rb_auto_playlist_source_parent_class)->dispose (object);
219 }
220
221 static void
222 rb_auto_playlist_source_finalize (GObject *object)
223 {
224 RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (object);
225
226 if (priv->query) {
227 rhythmdb_query_free (priv->query);
228 }
229
230 if (priv->search_query) {
231 rhythmdb_query_free (priv->search_query);
232 }
233
234 if (priv->limit_value) {
235 g_array_unref (priv->limit_value);
236 }
237
238 G_OBJECT_CLASS (rb_auto_playlist_source_parent_class)->finalize (object);
239 }
240
241 void
242 rb_auto_playlist_source_create_actions (RBShell *shell)
243 {
244 RBAutoPlaylistSourceClass *klass;
245 GtkUIManager *uimanager;
246
247 klass = RB_AUTO_PLAYLIST_SOURCE_CLASS (g_type_class_ref (RB_TYPE_AUTO_PLAYLIST_SOURCE));
248
249 klass->action_group = gtk_action_group_new ("AutoPlaylistActions");
250 gtk_action_group_set_translation_domain (klass->action_group, GETTEXT_PACKAGE);
251
252 g_object_get (shell, "ui-manager", &uimanager, NULL);
253 gtk_ui_manager_insert_action_group (uimanager, klass->action_group, 0);
254 g_object_unref (uimanager);
255
256 gtk_action_group_add_radio_actions (klass->action_group,
257 rb_auto_playlist_source_radio_actions,
258 G_N_ELEMENTS (rb_auto_playlist_source_radio_actions),
259 0,
260 NULL,
261 NULL);
262 rb_source_search_basic_create_for_actions (klass->action_group,
263 rb_auto_playlist_source_radio_actions,
264 G_N_ELEMENTS (rb_auto_playlist_source_radio_actions));
265
266 g_type_class_unref (klass);
267 }
268
269 static void
270 rb_auto_playlist_source_constructed (GObject *object)
271 {
272 RBEntryView *songs;
273 RBAutoPlaylistSource *source;
274 RBAutoPlaylistSourcePrivate *priv;
275 RBShell *shell;
276 RhythmDBEntryType *entry_type;
277 GtkUIManager *ui_manager;
278 GtkWidget *grid;
279
280 RB_CHAIN_GOBJECT_METHOD (rb_auto_playlist_source_parent_class, constructed, object);
281
282 source = RB_AUTO_PLAYLIST_SOURCE (object);
283 priv = GET_PRIVATE (source);
284
285 priv->paned = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
286
287 set_playlist_pixbuf (source);
288
289 g_object_get (RB_PLAYLIST_SOURCE (source), "entry-type", &entry_type, NULL);
290 priv->browser = rb_library_browser_new (rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source)),
291 entry_type);
292 g_object_unref (entry_type);
293 gtk_paned_pack1 (GTK_PANED (priv->paned), GTK_WIDGET (priv->browser), TRUE, FALSE);
294 g_signal_connect_object (G_OBJECT (priv->browser), "notify::output-model",
295 G_CALLBACK (rb_auto_playlist_source_browser_changed_cb),
296 source, 0);
297
298 songs = rb_source_get_entry_view (RB_SOURCE (source));
299 g_signal_connect_object (songs, "notify::sort-order",
300 G_CALLBACK (rb_auto_playlist_source_songs_sort_order_changed_cb),
301 source, 0);
302
303 priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH);
304
305 /* set up toolbar */
306 g_object_get (source, "shell", &shell, NULL);
307 g_object_get (shell, "ui-manager", &ui_manager, NULL);
308 priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), ui_manager);
309 rb_source_toolbar_add_search_entry (priv->toolbar, "/AutoPlaylistSourceSearchMenu", NULL);
310
311 g_object_unref (ui_manager);
312 g_object_unref (shell);
313
314 /* reparent the entry view */
315 g_object_ref (songs);
316 gtk_container_remove (GTK_CONTAINER (source), GTK_WIDGET (songs));
317 gtk_paned_pack2 (GTK_PANED (priv->paned), GTK_WIDGET (songs), TRUE, FALSE);
318
319 grid = gtk_grid_new ();
320 gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
321 gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
322 gtk_widget_set_margin_top (GTK_WIDGET (grid), 6);
323 gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (priv->toolbar), 0, 0, 1, 1);
324 gtk_grid_attach (GTK_GRID (grid), priv->paned, 0, 1, 1, 1);
325 gtk_container_add (GTK_CONTAINER (source), grid);
326
327 rb_source_bind_settings (RB_SOURCE (source), GTK_WIDGET (songs), priv->paned, GTK_WIDGET (priv->browser));
328 g_object_unref (songs);
329
330 gtk_widget_show_all (GTK_WIDGET (source));
331 }
332
333 /**
334 * rb_auto_playlist_source_new:
335 * @shell: the #RBShell instance
336 * @name: the name of the new playlist
337 * @local: if TRUE, the playlist will be considered local
338 *
339 * Creates a new automatic playlist source, initially with an empty query.
340 *
341 * Return value: the new source
342 */
343 RBSource *
344 rb_auto_playlist_source_new (RBShell *shell, const char *name, gboolean local)
345 {
346 if (name == NULL)
347 name = "";
348
349 return RB_SOURCE (g_object_new (RB_TYPE_AUTO_PLAYLIST_SOURCE,
350 "name", name,
351 "shell", shell,
352 "is-local", local,
353 "entry-type", RHYTHMDB_ENTRY_TYPE_SONG,
354 "toolbar-path", "/AutoPlaylistSourceToolBar",
355 NULL));
356 }
357
358 static void
359 rb_auto_playlist_source_set_property (GObject *object,
360 guint prop_id,
361 const GValue *value,
362 GParamSpec *pspec)
363 {
364 RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (object);
365
366 switch (prop_id) {
367 case PROP_SHOW_BROWSER:
368 if (g_value_get_boolean (value))
369 gtk_widget_show (GTK_WIDGET (priv->browser));
370 else
371 gtk_widget_hide (GTK_WIDGET (priv->browser));
372 break;
373 default:
374 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
375 break;
376 }
377 }
378
379 static void
380 rb_auto_playlist_source_get_property (GObject *object,
381 guint prop_id,
382 GValue *value,
383 GParamSpec *pspec)
384 {
385 RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (object);
386
387 switch (prop_id) {
388 case PROP_BASE_QUERY_MODEL:
389 g_value_set_object (value, priv->cached_all_query);
390 break;
391 case PROP_SHOW_BROWSER:
392 g_value_set_boolean (value, gtk_widget_get_visible (GTK_WIDGET (priv->browser)));
393 break;
394 default:
395 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
396 break;
397 }
398 }
399
400 /**
401 * rb_auto_playlist_source_new_from_xml:
402 * @shell: the #RBShell instance
403 * @node: libxml node containing the playlist
404 *
405 * Creates a new auto playlist source by parsing an XML-encoded query.
406 *
407 * Return value: the new source
408 */
409 RBSource *
410 rb_auto_playlist_source_new_from_xml (RBShell *shell, xmlNodePtr node)
411 {
412 RBAutoPlaylistSource *source = RB_AUTO_PLAYLIST_SOURCE (rb_auto_playlist_source_new (shell, NULL, TRUE));
413 xmlNodePtr child;
414 xmlChar *tmp;
415 GPtrArray *query;
416 RhythmDBQueryModelLimitType limit_type = RHYTHMDB_QUERY_MODEL_LIMIT_NONE;
417 GArray *limit_value = NULL;
418 gchar *sort_key = NULL;
419 gint sort_direction = 0;
420 GValue val = {0,};
421
422 child = node->children;
423 while (xmlNodeIsText (child))
424 child = child->next;
425
426 query = rhythmdb_query_deserialize (rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source)),
427 child);
428
429 limit_value = g_array_sized_new (FALSE, TRUE, sizeof (GValue), 0);
430 g_array_set_clear_func (limit_value, (GDestroyNotify) g_value_unset);
431 tmp = xmlGetProp (node, RB_PLAYLIST_LIMIT_COUNT);
432 if (!tmp) /* Backwards compatibility */
433 tmp = xmlGetProp (node, RB_PLAYLIST_LIMIT);
434 if (tmp) {
435 gulong l = strtoul ((char *)tmp, NULL, 0);
436 if (l > 0) {
437 limit_type = RHYTHMDB_QUERY_MODEL_LIMIT_COUNT;
438
439 g_value_init (&val, G_TYPE_ULONG);
440 g_value_set_ulong (&val, l);
441 g_array_append_val (limit_value, val);
442 g_free (tmp);
443 g_value_unset (&val);
444 }
445 }
446
447 if (limit_type == RHYTHMDB_QUERY_MODEL_LIMIT_NONE) {
448 tmp = xmlGetProp (node, RB_PLAYLIST_LIMIT_SIZE);
449 if (tmp) {
450 guint64 l = g_ascii_strtoull ((char *)tmp, NULL, 0);
451 if (l > 0) {
452 limit_type = RHYTHMDB_QUERY_MODEL_LIMIT_SIZE;
453
454 g_value_init (&val, G_TYPE_UINT64);
455 g_value_set_uint64 (&val, l);
456 g_array_append_val (limit_value, val);
457 g_free (tmp);
458 g_value_unset (&val);
459 }
460 }
461 }
462
463 if (limit_type == RHYTHMDB_QUERY_MODEL_LIMIT_NONE) {
464 tmp = xmlGetProp (node, RB_PLAYLIST_LIMIT_TIME);
465 if (tmp) {
466 gulong l = strtoul ((char *)tmp, NULL, 0);
467 if (l > 0) {
468 limit_type = RHYTHMDB_QUERY_MODEL_LIMIT_TIME;
469
470 g_value_init (&val, G_TYPE_ULONG);
471 g_value_set_ulong (&val, l);
472 g_array_append_val (limit_value, val);
473 g_free (tmp);
474 g_value_unset (&val);
475 }
476 }
477 }
478
479 sort_key = (gchar*) xmlGetProp (node, RB_PLAYLIST_SORT_KEY);
480 if (sort_key && *sort_key) {
481 tmp = xmlGetProp (node, RB_PLAYLIST_SORT_DIRECTION);
482 if (tmp) {
483 sort_direction = atoi ((char*) tmp);
484 g_free (tmp);
485 }
486 } else {
487 g_free (sort_key);
488 sort_key = NULL;
489 sort_direction = 0;
490 }
491
492 rb_auto_playlist_source_set_query (source, query,
493 limit_type,
494 limit_value,
495 sort_key,
496 sort_direction);
497 g_free (sort_key);
498 g_array_unref (limit_value);
499 rhythmdb_query_free (query);
500
501 return RB_SOURCE (source);
502 }
503
504 static gboolean
505 impl_show_popup (RBDisplayPage *page)
506 {
507 _rb_display_page_show_popup (page, AUTO_PLAYLIST_SOURCE_POPUP_PATH);
508 return TRUE;
509 }
510
511 static void
512 impl_reset_filters (RBSource *source)
513 {
514 RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (source);
515 gboolean changed = FALSE;
516
517 if (rb_library_browser_reset (priv->browser))
518 changed = TRUE;
519
520 if (priv->search_query != NULL) {
521 changed = TRUE;
522 rhythmdb_query_free (priv->search_query);
523 priv->search_query = NULL;
524 }
525
526 rb_source_toolbar_clear_search_entry (priv->toolbar);
527
528 if (changed)
529 rb_auto_playlist_source_do_query (RB_AUTO_PLAYLIST_SOURCE (source), FALSE);
530 }
531
532 static void
533 impl_search (RBSource *asource, RBSourceSearch *search, const char *cur_text, const char *new_text)
534 {
535 RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (asource);
536 RhythmDB *db;
537 gboolean subset;
538
539 if (search == NULL) {
540 search = priv->default_search;
541 }
542
543 /* replace our search query */
544 if (priv->search_query != NULL) {
545 rhythmdb_query_free (priv->search_query);
546 priv->search_query = NULL;
547 }
548 db = rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (asource));
549 priv->search_query = rb_source_search_create_query (search, db, new_text);
550
551 /* if we don't have the base query yet, we can't do searches */
552 if (priv->cached_all_query == NULL) {
553 rb_debug ("deferring search for \"%s\" until we have the base query", new_text ? new_text : "<NULL>");
554 priv->search_on_completion = TRUE;
555 return;
556 }
557
558 /* we can only do subset searches once the original query is complete */
559 subset = rb_source_search_is_subset (search, cur_text, new_text);
560 if (priv->query_active && subset) {
561 rb_debug ("deferring search for \"%s\" until query completion", new_text ? new_text : "<NULL>");
562 priv->search_on_completion = TRUE;
563 } else {
564 rb_debug ("doing search for \"%s\"", new_text ? new_text : "<NULL>");
565 rb_auto_playlist_source_do_query (RB_AUTO_PLAYLIST_SOURCE (asource), subset);
566 }
567 }
568
569 static GList *
570 impl_get_property_views (RBSource *source)
571 {
572 RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (source);
573 GList *ret;
574
575 ret = rb_library_browser_get_property_views (priv->browser);
576 return ret;
577 }
578
579 static RhythmDBPropType
580 rb_auto_playlist_source_drag_atom_to_prop (GdkAtom smasher)
581 {
582 if (smasher == gdk_atom_intern ("text/x-rhythmbox-album", TRUE))
583 return RHYTHMDB_PROP_ALBUM;
584 else if (smasher == gdk_atom_intern ("text/x-rhythmbox-artist", TRUE))
585 return RHYTHMDB_PROP_ARTIST;
586 else if (smasher == gdk_atom_intern ("text/x-rhythmbox-genre", TRUE))
587 return RHYTHMDB_PROP_GENRE;
588 else {
589 g_assert_not_reached ();
590 return 0;
591 }
592 }
593
594 static gboolean
595 impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data)
596 {
597 RBAutoPlaylistSource *source = RB_AUTO_PLAYLIST_SOURCE (page);
598
599 GdkAtom type;
600 GPtrArray *subquery = NULL;
601 gchar **names;
602 guint propid;
603 int i;
604 RhythmDB *db;
605
606 type = gtk_selection_data_get_data_type (data);
607
608 /* ignore URI and entry ID lists */
609 if (type == gdk_atom_intern ("text/uri-list", TRUE) ||
610 type == gdk_atom_intern ("application/x-rhythmbox-entry", TRUE))
611 return TRUE;
612
613 names = g_strsplit ((char *) gtk_selection_data_get_data (data), "\r\n", 0);
614 propid = rb_auto_playlist_source_drag_atom_to_prop (type);
615
616 g_object_get (page, "db", &db, NULL);
617
618 for (i = 0; names[i]; i++) {
619 if (subquery == NULL) {
620 subquery = rhythmdb_query_parse (db,
621 RHYTHMDB_QUERY_PROP_EQUALS,
622 propid,
623 names[i],
624 RHYTHMDB_QUERY_END);
625 } else {
626 rhythmdb_query_append (db,
627 subquery,
628 RHYTHMDB_QUERY_DISJUNCTION,
629 RHYTHMDB_QUERY_PROP_EQUALS,
630 propid,
631 names[i],
632 RHYTHMDB_QUERY_END);
633 }
634 }
635
636 g_strfreev (names);
637
638 if (subquery != NULL) {
639 RhythmDBEntryType *qtype;
640 GPtrArray *query;
641
642 g_object_get (source, "entry-type", &qtype, NULL);
643 if (qtype == NULL)
644 qtype = g_object_ref (RHYTHMDB_ENTRY_TYPE_SONG);
645
646 query = rhythmdb_query_parse (db,
647 RHYTHMDB_QUERY_PROP_EQUALS,
648 RHYTHMDB_PROP_TYPE,
649 qtype,
650 RHYTHMDB_QUERY_SUBQUERY,
651 subquery,
652 RHYTHMDB_QUERY_END);
653 rb_auto_playlist_source_set_query (RB_AUTO_PLAYLIST_SOURCE (source), query,
654 RHYTHMDB_QUERY_MODEL_LIMIT_NONE, NULL,
655 NULL, 0);
656
657 rhythmdb_query_free (subquery);
658 rhythmdb_query_free (query);
659 g_object_unref (qtype);
660 }
661
662 g_object_unref (db);
663
664 return TRUE;
665 }
666
667 static void
668 _save_write_ulong (xmlNodePtr node, GArray *limit_value, const xmlChar *key)
669 {
670 gulong l;
671 gchar *str;
672
673 l = g_value_get_ulong (&g_array_index (limit_value, GValue, 0));
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
674 str = g_strdup_printf ("%u", (guint)l);
675 xmlSetProp (node, key, BAD_CAST str);
676 g_free (str);
677 }
678
679 static void
680 _save_write_uint64 (xmlNodePtr node, GArray *limit_value, const xmlChar *key)
681 {
682 guint64 l;
683 gchar *str;
684
685 l = g_value_get_uint64 (&g_array_index (limit_value, GValue, 0));
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
686 str = g_strdup_printf ("%" G_GUINT64_FORMAT, l);
687 xmlSetProp (node, key, BAD_CAST str);
688 g_free (str);
689 }
690
691 static void
692 impl_save_contents_to_xml (RBPlaylistSource *psource,
693 xmlNodePtr node)
694 {
695 GPtrArray *query;
696 RhythmDBQueryModelLimitType limit_type;
697 GArray *limit_value = NULL;
698 char *sort_key;
699 gint sort_direction;
700 RBAutoPlaylistSource *source = RB_AUTO_PLAYLIST_SOURCE (psource);
701
702 xmlSetProp (node, RB_PLAYLIST_TYPE, RB_PLAYLIST_AUTOMATIC);
703
704 sort_key = NULL;
705 rb_auto_playlist_source_get_query (source,
706 &query,
707 &limit_type,
708 &limit_value,
709 &sort_key,
710 &sort_direction);
711
712 switch (limit_type) {
713 case RHYTHMDB_QUERY_MODEL_LIMIT_NONE:
714 break;
715
716 case RHYTHMDB_QUERY_MODEL_LIMIT_COUNT:
717 _save_write_ulong (node, limit_value, RB_PLAYLIST_LIMIT_COUNT);
718 break;
719
720 case RHYTHMDB_QUERY_MODEL_LIMIT_SIZE:
721 _save_write_uint64 (node, limit_value, RB_PLAYLIST_LIMIT_SIZE);
722 break;
723
724 case RHYTHMDB_QUERY_MODEL_LIMIT_TIME:
725 _save_write_ulong (node, limit_value, RB_PLAYLIST_LIMIT_TIME);
726 break;
727
728 default:
729 g_assert_not_reached ();
730 }
731
732 if (sort_key && *sort_key) {
733 char *temp_str;
734
735 xmlSetProp (node, RB_PLAYLIST_SORT_KEY, BAD_CAST sort_key);
736 temp_str = g_strdup_printf ("%d", sort_direction);
737 xmlSetProp (node, RB_PLAYLIST_SORT_DIRECTION, BAD_CAST temp_str);
738
739 g_free (temp_str);
740 }
741
742 rhythmdb_query_serialize (rb_playlist_source_get_db (psource), query, node);
743 rhythmdb_query_free (query);
744
745 if (limit_value != NULL) {
746 g_array_unref (limit_value);
747 }
748 g_free (sort_key);
749 }
750
751 static void
752 rb_auto_playlist_source_query_complete_cb (RhythmDBQueryModel *model,
753 RBAutoPlaylistSource *source)
754 {
755 RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (source);
756
757 priv->query_active = FALSE;
758 if (priv->search_on_completion) {
759 priv->search_on_completion = FALSE;
760 rb_debug ("performing deferred search");
761 /* this is only done for subset searches */
762 rb_auto_playlist_source_do_query (source, TRUE);
763 }
764 }
765
766 static void
767 rb_auto_playlist_source_do_query (RBAutoPlaylistSource *source, gboolean subset)
768 {
769 RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (source);
770 RhythmDB *db;
771 RhythmDBQueryModel *query_model;
772 GPtrArray *query;
773
774 /* this doesn't add a ref */
775 db = rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source));
776
777 g_assert (priv->cached_all_query);
778
779 if (priv->search_query == NULL) {
780 rb_library_browser_set_model (priv->browser,
781 priv->cached_all_query,
782 FALSE);
783 return;
784 }
785
786 query = rhythmdb_query_copy (priv->query);
787 rhythmdb_query_append (db, query,
788 RHYTHMDB_QUERY_SUBQUERY, priv->search_query,
789 RHYTHMDB_QUERY_END);
790
791 g_object_get (priv->browser, "input-model", &query_model, NULL);
792
793 if (subset && query_model != priv->cached_all_query) {
794 /* just apply the new query to the existing query model */
795 g_object_set (query_model, "query", query, NULL);
796 rhythmdb_query_model_reapply_query (query_model, FALSE);
797 g_object_unref (query_model);
798 } else {
799 /* otherwise, we need a new query model */
800 g_object_unref (query_model);
801
802 query_model = g_object_new (RHYTHMDB_TYPE_QUERY_MODEL,
803 "db", db,
804 "limit-type", priv->limit_type,
805 "limit-value", priv->limit_value,
806 NULL);
807 rhythmdb_query_model_chain (query_model, priv->cached_all_query, FALSE);
808 rb_library_browser_set_model (priv->browser, query_model, TRUE);
809
810 priv->query_active = TRUE;
811 priv->search_on_completion = FALSE;
812 g_signal_connect_object (G_OBJECT (query_model),
813 "complete", G_CALLBACK (rb_auto_playlist_source_query_complete_cb),
814 source, 0);
815 rhythmdb_do_full_query_async_parsed (db,
816 RHYTHMDB_QUERY_RESULTS (query_model),
817 query);
818 g_object_unref (query_model);
819 }
820
821 rhythmdb_query_free (query);
822 }
823
824 /**
825 * rb_auto_playlist_source_set_query:
826 * @source: the #RBAutoPlaylistSource
827 * @query: (transfer none): the new database query
828 * @limit_type: the playlist limit type
829 * @limit_value: the playlist limit value
830 * @sort_key: the sorting key
831 * @sort_order: the sorting direction (as a #GtkSortType)
832 *
833 * Sets the database query used to populate the playlist, and also the limit on
834 * playlist size, and the sorting type used.
835 */
836 void
837 rb_auto_playlist_source_set_query (RBAutoPlaylistSource *source,
838 GPtrArray *query,
839 RhythmDBQueryModelLimitType limit_type,
840 GArray *limit_value,
841 const char *sort_key,
842 gint sort_order)
843 {
844 RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (source);
845 RhythmDB *db = rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source));
846 RBEntryView *songs = rb_source_get_entry_view (RB_SOURCE (source));
847
848 priv->query_resetting = TRUE;
849 if (priv->query) {
850 rhythmdb_query_free (priv->query);
851 }
852
853 if (priv->cached_all_query) {
854 g_object_unref (G_OBJECT (priv->cached_all_query));
855 }
856
857 if (priv->limit_value) {
858 g_array_unref (priv->limit_value);
859 }
860
861 /* playlists that aren't limited, with a particular sort order, are user-orderable */
862 rb_entry_view_set_columns_clickable (songs, (limit_type == RHYTHMDB_QUERY_MODEL_LIMIT_NONE));
863 rb_entry_view_set_sorting_order (songs, sort_key, sort_order);
864
865 priv->query = rhythmdb_query_copy (query);
866 priv->limit_type = limit_type;
867 priv->limit_value = limit_value ? g_array_ref (limit_value) : NULL;
868
869 priv->cached_all_query = g_object_new (RHYTHMDB_TYPE_QUERY_MODEL,
870 "db", db,
871 "limit-type", priv->limit_type,
872 "limit-value", priv->limit_value,
873 NULL);
874 rb_library_browser_set_model (priv->browser, priv->cached_all_query, TRUE);
875 rhythmdb_do_full_query_async_parsed (db,
876 RHYTHMDB_QUERY_RESULTS (priv->cached_all_query),
877 priv->query);
878
879 priv->query_resetting = FALSE;
880 }
881
882 /**
883 * rb_auto_playlist_source_get_query:
884 * @source: the #RBAutoPlaylistSource
885 * @query: (out caller-allocates) (transfer full): returns the database query for the playlist
886 * @limit_type: (out callee-allocates): returns the playlist limit type
887 * @limit_value: (out) (transfer full): returns the playlist limit value
888 * @sort_key: (out callee-allocates) (transfer full): returns the playlist sorting key
889 * @sort_order: (out callee-allocates): returns the playlist sorting direction (as a #GtkSortType)
890 *
891 * Extracts the current query, playlist limit, and sorting settings for the playlist.
892 */
893 void
894 rb_auto_playlist_source_get_query (RBAutoPlaylistSource *source,
895 GPtrArray **query,
896 RhythmDBQueryModelLimitType *limit_type,
897 GArray **limit_value,
898 char **sort_key,
899 gint *sort_order)
900 {
901 RBAutoPlaylistSourcePrivate *priv;
902 RBEntryView *songs;
903
904 g_return_if_fail (RB_IS_AUTO_PLAYLIST_SOURCE (source));
905
906 priv = GET_PRIVATE (source);
907 songs = rb_source_get_entry_view (RB_SOURCE (source));
908
909 *query = rhythmdb_query_copy (priv->query);
910 *limit_type = priv->limit_type;
911 *limit_value = (priv->limit_value) ? g_array_ref (priv->limit_value) : NULL;
912
913 rb_entry_view_get_sorting_order (songs, sort_key, sort_order);
914 }
915
916 static void
917 rb_auto_playlist_source_songs_sort_order_changed_cb (GObject *object, GParamSpec *pspec, RBAutoPlaylistSource *source)
918 {
919 RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (source);
920
921 /* don't process this if we are in the middle of setting a query */
922 if (priv->query_resetting)
923 return;
924 rb_debug ("sort order changed");
925
926 rb_entry_view_resort_model (RB_ENTRY_VIEW (object));
927 }
928
929 static void
930 rb_auto_playlist_source_browser_changed_cb (RBLibraryBrowser *browser,
931 GParamSpec *pspec,
932 RBAutoPlaylistSource *source)
933 {
934 RBEntryView *songs = rb_source_get_entry_view (RB_SOURCE (source));
935 RhythmDBQueryModel *query_model;
936
937 g_object_get (browser, "output-model", &query_model, NULL);
938 rb_entry_view_set_model (songs, query_model);
939 rb_playlist_source_set_query_model (RB_PLAYLIST_SOURCE (source), query_model);
940 g_object_unref (query_model);
941
942 rb_source_notify_filter_changed (RB_SOURCE (source));
943 }