hythmbox-2.98/sources/rb-auto-playlist-source.c

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));
Access to field 'data' results in a dereference of a null pointer (loaded from variable 'limit_value')
(emitted by clang-analyzer)

TODO: a detailed trace is available in the data model (not yet rendered in this report)

Access to field 'data' results in a dereference of a null pointer (loaded from variable 'limit_value')
(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));
Access to field 'data' results in a dereference of a null pointer (loaded from variable 'limit_value')
(emitted by clang-analyzer)

TODO: a detailed trace is available in the data model (not yet rendered in this report)

Access to field 'data' results in a dereference of a null pointer (loaded from variable 'limit_value')
(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 }