hythmbox-2.98/sources/rb-display-page-model.c

No issues found

Incomplete coverage

Tool Failure ID Location Function Message Data
clang-analyzer no-output-found rb-display-page-model.c Message(text='Unable to locate XML output from invoke-clang-analyzer') None
clang-analyzer no-output-found rb-display-page-model.c Message(text='Unable to locate XML output from invoke-clang-analyzer') None
Failure running clang-analyzer ('no-output-found')
Message
Unable to locate XML output from invoke-clang-analyzer
Failure running clang-analyzer ('no-output-found')
Message
Unable to locate XML output from invoke-clang-analyzer
  1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
  2 /*
  3  * Copyright (C) 2003 Colin Walters <walters@verbum.org>
  4  *
  5  * This program is free software; you can redistribute it and/or
  6  * modify it under the terms of the GNU General Public License as
  7  * published by the Free Software Foundation; either version 2 of the
  8  * License, or (at your option) any later version.
  9  *
 10  * The Rhythmbox authors hereby grant permission for non-GPL compatible
 11  * GStreamer plugins to be used and distributed together with GStreamer
 12  * and Rhythmbox. This permission is above and beyond the permissions granted
 13  * by the GPL license by which Rhythmbox is covered. If you modify this code
 14  * you may extend this exception to your version of the code, but you are not
 15  * obligated to do so. If you do not wish to do so, delete this exception
 16  * statement from your version.
 17  *
 18  * This program is distributed in the hope that it will be useful,
 19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 21  * General Public License for more details.
 22  *
 23  * You should have received a copy of the GNU General Public
 24  * License along with this program; if not, write to the
 25  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 26  * Boston, MA 02110-1301  USA.
 27  *
 28  */
 29 
 30 #include "config.h"
 31 
 32 #include <string.h>
 33 
 34 #include <glib/gi18n.h>
 35 #include <gdk/gdk.h>
 36 #include <gtk/gtk.h>
 37 
 38 #include "rb-display-page-group.h"
 39 #include "rb-display-page-model.h"
 40 #include "rb-tree-dnd.h"
 41 #include "rb-debug.h"
 42 #include "rb-marshal.h"
 43 #include "rb-playlist-source.h"
 44 #include "rb-auto-playlist-source.h"
 45 #include "rb-static-playlist-source.h"
 46 
 47 /**
 48  * SECTION:rb-display-page-model
 49  * @short_description: models backing the display page tree
 50  *
 51  * The #RBDisplayPageTree widget is backed by a #GtkTreeStore containing
 52  * the sources and a set of attributes used to structure and display
 53  * them, and a #GtkTreeModelFilter that hides sources with the
 54  * visibility property set to FALSE.  This class implements the filter
 55  * model and also creates the actual model.
 56  *
 57  * The display page model supports drag and drop in a variety of formats.
 58  * The simplest of these are text/uri-list and application/x-rhythmbox-entry,
 59  * which convey URIs and IDs of existing database entries.  When dragged
 60  * to an existing source, these just add the URIs or entries to the target
 61  * source.  When dragged to an empty space in the tree widget, this results
 62  * in the creation of a static playlist.
 63  *
 64  * text/x-rhythmbox-artist, text/x-rhythmbox-album, and text/x-rhythmbox-genre
 65  * are used when dragging items from the library browser.  When dragged to
 66  * the display page tree, these result in the creation of a new auto playlist with
 67  * the dragged items as criteria.
 68  */
 69 
 70 enum
 71 {
 72 	DROP_RECEIVED,
 73 	PAGE_INSERTED,
 74 	LAST_SIGNAL
 75 };
 76 
 77 static guint rb_display_page_model_signals[LAST_SIGNAL] = { 0 };
 78 
 79 enum {
 80 	TARGET_PROPERTY,
 81 	TARGET_SOURCE,
 82 	TARGET_URIS,
 83 	TARGET_ENTRIES,
 84 	TARGET_DELETE
 85 };
 86 
 87 static const GtkTargetEntry dnd_targets[] = {
 88 	{ "text/x-rhythmbox-album", 0, TARGET_PROPERTY },
 89 	{ "text/x-rhythmbox-artist", 0, TARGET_PROPERTY },
 90 	{ "text/x-rhythmbox-genre", 0, TARGET_PROPERTY },
 91 	{ "application/x-rhythmbox-source", 0, TARGET_SOURCE },
 92 	{ "application/x-rhythmbox-entry", 0, TARGET_ENTRIES },
 93 	{ "text/uri-list", 0, TARGET_URIS },
 94 	{ "application/x-delete-me", 0, TARGET_DELETE }
 95 };
 96 
 97 static GtkTargetList *drag_target_list = NULL;
 98 
 99 static void rb_display_page_model_drag_dest_init (RbTreeDragDestIface *iface);
100 static void rb_display_page_model_drag_source_init (RbTreeDragSourceIface *iface);
101 
102 G_DEFINE_TYPE_EXTENDED (RBDisplayPageModel,
103                         rb_display_page_model,
104                         GTK_TYPE_TREE_MODEL_FILTER,
105                         0,
106                         G_IMPLEMENT_INTERFACE (RB_TYPE_TREE_DRAG_SOURCE,
107                                                rb_display_page_model_drag_source_init)
108                         G_IMPLEMENT_INTERFACE (RB_TYPE_TREE_DRAG_DEST,
109                                                rb_display_page_model_drag_dest_init));
110 
111 static gboolean
112 rb_display_page_model_drag_data_received (RbTreeDragDest *drag_dest,
113 					  GtkTreePath *dest,
114 					  GtkTreeViewDropPosition pos,
115 					  GtkSelectionData *selection_data)
116 {
117 	RBDisplayPageModel *model;
118 	GdkAtom type;
119 
120 	g_return_val_if_fail (RB_IS_DISPLAY_PAGE_MODEL (drag_dest), FALSE);
121 	model = RB_DISPLAY_PAGE_MODEL (drag_dest);
122 	type = gtk_selection_data_get_data_type (selection_data);
123 
124 	if (type == gdk_atom_intern ("text/uri-list", TRUE) ||
125 	    type == gdk_atom_intern ("application/x-rhythmbox-entry", TRUE)) {
126 		GtkTreeIter iter;
127 		RBDisplayPage *target = NULL;
128 
129 		rb_debug ("text/uri-list or application/x-rhythmbox-entry drag data received");
130 
131 		if (dest != NULL && gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, dest)) {
132 			gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
133 					    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &target, -1);
134 		}
135 
136 		g_signal_emit (G_OBJECT (model), rb_display_page_model_signals[DROP_RECEIVED],
137 			       0, target, pos, selection_data);
138 
139 		if (target != NULL) {
140 			g_object_unref (target);
141 		}
142 
143 		return TRUE;
144 	}
145 
146         /* if artist, album or genre, only allow new playlists */
147         if (type == gdk_atom_intern ("text/x-rhythmbox-album", TRUE) ||
148             type == gdk_atom_intern ("text/x-rhythmbox-artist", TRUE) ||
149             type == gdk_atom_intern ("text/x-rhythmbox-genre", TRUE)) {
150                 rb_debug ("text/x-rhythmbox-(album|artist|genre) drag data received");
151                 g_signal_emit (G_OBJECT (model), rb_display_page_model_signals[DROP_RECEIVED],
152                                0, NULL, pos, selection_data);
153                 return TRUE;
154         }
155 
156 	if (type == gdk_atom_intern ("application/x-rhythmbox-source", TRUE)) {
157 		/* don't support dnd of sources */
158 		return FALSE;
159 	}
160 
161 	return FALSE;
162 }
163 
164 static gboolean
165 rb_display_page_model_row_drop_possible (RbTreeDragDest *drag_dest,
166 					 GtkTreePath *dest,
167 					 GtkTreeViewDropPosition pos,
168 					 GtkSelectionData *selection_data)
169 {
170 	RBDisplayPageModel *model;
171 
172 	rb_debug ("row drop possible");
173 	g_return_val_if_fail (RB_IS_DISPLAY_PAGE_MODEL (drag_dest), FALSE);
174 
175 	model = RB_DISPLAY_PAGE_MODEL (drag_dest);
176 
177 	if (!dest)
178 		return TRUE;
179 
180 	/* Call the superclass method */
181 	return gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (GTK_TREE_STORE (model)),
182 						     dest, selection_data);
183 }
184 
185 static gboolean
186 path_is_droppable (RBDisplayPageModel *model,
187 		   GtkTreePath *dest)
188 {
189 	GtkTreeIter iter;
190 	gboolean res;
191 
192 	res = FALSE;
193 
194 	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, dest)) {
195 		RBDisplayPage *page;
196 
197 		gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
198 				    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page, -1);
199 
200 		if (page != NULL) {
201 			if (RB_IS_SOURCE (page)) {
202 				res = rb_source_can_paste (RB_SOURCE (page));
203 			}
204 			g_object_unref (page);
205 		}
206 	}
207 
208 	return res;
209 }
210 
211 static gboolean
212 rb_display_page_model_row_drop_position (RbTreeDragDest   *drag_dest,
213 					 GtkTreePath       *dest_path,
214 					 GList *targets,
215 					 GtkTreeViewDropPosition *pos)
216 {
217 	GtkTreeModel *model = GTK_TREE_MODEL (drag_dest);
218 
219 	if (g_list_find (targets, gdk_atom_intern ("application/x-rhythmbox-source", TRUE)) && dest_path) {
220 		rb_debug ("application/x-rhythmbox-source type");
221 		return FALSE;
222 	}
223 
224 	if (g_list_find (targets, gdk_atom_intern ("text/uri-list", TRUE)) ||
225 	    g_list_find (targets, gdk_atom_intern ("application/x-rhythmbox-entry", TRUE))) {
226 		rb_debug ("text/uri-list or application/x-rhythmbox-entry type");
227 		if (dest_path && !path_is_droppable (RB_DISPLAY_PAGE_MODEL (model), dest_path))
228 			return FALSE;
229 
230 		*pos = GTK_TREE_VIEW_DROP_INTO_OR_BEFORE;
231 		return TRUE;
232 	}
233 
234 	if ((g_list_find (targets, gdk_atom_intern ("text/x-rhythmbox-artist", TRUE))
235 	     || g_list_find (targets, gdk_atom_intern ("text/x-rhythmbox-album", TRUE))
236 	     || g_list_find (targets, gdk_atom_intern ("text/x-rhythmbox-genre", TRUE)))
237 	    && !g_list_find (targets, gdk_atom_intern ("application/x-rhythmbox-source", TRUE))) {
238 		rb_debug ("genre, album, or artist type");
239 		*pos = GTK_TREE_VIEW_DROP_AFTER;
240 		return TRUE;
241 	}
242 
243 	return FALSE;
244 }
245 
246 static GdkAtom
247 rb_display_page_model_get_drag_target (RbTreeDragDest *drag_dest,
248 				       GtkWidget *widget,
249 				       GdkDragContext *context,
250 				       GtkTreePath *path,
251 				       GtkTargetList *target_list)
252 {
253 	if (g_list_find (gdk_drag_context_list_targets (context),
254 	    gdk_atom_intern ("application/x-rhythmbox-source", TRUE))) {
255 		/* always accept rb source path if offered */
256 		return gdk_atom_intern ("application/x-rhythmbox-source", TRUE);
257 	}
258 
259 	if (path) {
260 		/* only accept text/uri-list or application/x-rhythmbox-entry drops into existing sources */
261 		GdkAtom entry_atom;
262 
263 		entry_atom = gdk_atom_intern ("application/x-rhythmbox-entry", FALSE);
264 		if (g_list_find (gdk_drag_context_list_targets (context), entry_atom))
265 			return entry_atom;
266 
267 		return gdk_atom_intern ("text/uri-list", FALSE);
268 	}
269 
270 	return gtk_drag_dest_find_target (widget, context, target_list);
271 }
272 
273 static gboolean
274 rb_display_page_model_row_draggable (RbTreeDragSource *drag_source, GList *path_list)
275 {
276 	return FALSE;
277 }
278 
279 static gboolean
280 rb_display_page_model_drag_data_get (RbTreeDragSource *drag_source,
281 				     GList *path_list,
282 				     GtkSelectionData *selection_data)
283 {
284 	char *path_str;
285 	GtkTreePath *path;
286 	GdkAtom selection_data_target;
287 	guint target;
288 
289 	selection_data_target = gtk_selection_data_get_target (selection_data);
290 	path = gtk_tree_row_reference_get_path (path_list->data);
291 	if (path == NULL)
292 		return FALSE;
293 
294 	if (!gtk_target_list_find (drag_target_list,
295 				   selection_data_target,
296 				   &target)) {
297 		return FALSE;
298 	}
299 
300 	switch (target) {
301 	case TARGET_SOURCE:
302 		rb_debug ("getting drag data as rb display page path");
303 		path_str = gtk_tree_path_to_string (path);
304 		gtk_selection_data_set (selection_data,
305 					selection_data_target,
306 					8, (guchar *) path_str,
307 					strlen (path_str));
308 		g_free (path_str);
309 		gtk_tree_path_free (path);
310 		return TRUE;
311 	case TARGET_URIS:
312 	case TARGET_ENTRIES:
313 	{
314 		RBDisplayPage *page;
315 		RhythmDBQueryModel *query_model;
316 		GtkTreeIter iter;
317 		GString *data;
318 		gboolean first = TRUE;
319 
320 		rb_debug ("getting drag data as uri list");
321 		if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source), &iter, path))
322 			return FALSE;
323 
324 		data = g_string_new ("");
325 		gtk_tree_model_get (GTK_TREE_MODEL (drag_source),
326 				    &iter,
327 				    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
328 				    -1);
329 		if (RB_IS_SOURCE (page) == FALSE) {
330 			g_object_unref (page);
331 			return FALSE;
332 		}
333 		g_object_get (page, "query-model", &query_model, NULL);
334 		g_object_unref (page);
335 
336 		if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (query_model), &iter)) {
337 			g_object_unref (query_model);
338 			return FALSE;
339 		}
340 
341 		do {
342 			RhythmDBEntry *entry;
343 
344 			if (first) {
345 				g_string_append(data, "\r\n");
346 				first = FALSE;
347 			}
348 
349 			entry = rhythmdb_query_model_iter_to_entry (query_model, &iter);
350 			if (target == TARGET_URIS) {
351 				g_string_append (data, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
352 			} else {
353 				g_string_append_printf (data,
354 							"%lu",
355 							rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_ENTRY_ID));
356 			}
357 
358 			rhythmdb_entry_unref (entry);
359 
360 		} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (query_model), &iter));
361 
362 		g_object_unref (query_model);
363 
364 		gtk_selection_data_set (selection_data,
365 					selection_data_target,
366 					8, (guchar *) data->str,
367 					data->len);
368 
369 		g_string_free (data, TRUE);
370 		return TRUE;
371 	}
372 	default:
373 		/* unsupported target */
374 		return FALSE;
375 	}
376 }
377 
378 static gboolean
379 rb_display_page_model_drag_data_delete (RbTreeDragSource *drag_source,
380 					GList *paths)
381 {
382 	return TRUE;
383 }
384 
385 typedef struct _DisplayPageIter {
386 	RBDisplayPage *page;
387 	GtkTreeIter iter;
388 	gboolean found;
389 } DisplayPageIter;
390 
391 static gboolean
392 match_page_to_iter (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, DisplayPageIter *dpi)
393 {
394 	RBDisplayPage *target = NULL;
395 
396 	gtk_tree_model_get (model, iter, RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &target, -1);
397 
398 	if (target == dpi->page) {
399 		dpi->iter = *iter;
400 		dpi->found = TRUE;
401 	}
402 
403 	if (target != NULL) {
404 		g_object_unref (target);
405 	}
406 
407 	return dpi->found;
408 }
409 
410 static gboolean
411 find_in_real_model (RBDisplayPageModel *page_model, RBDisplayPage *page, GtkTreeIter *iter)
412 {
413 	GtkTreeModel *model;
414 	DisplayPageIter dpi = {0, };
415 	dpi.page = page;
416 
417 	model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (page_model));
418 	gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc) match_page_to_iter, &dpi);
419 	if (dpi.found) {
420 		*iter = dpi.iter;
421 		return TRUE;
422 	} else {
423 		return FALSE;
424 	}
425 }
426 
427 static void
428 walk_up_to_page_group (GtkTreeModel *model, GtkTreeIter *page_group, GtkTreeIter *page)
429 {
430 	GtkTreeIter walk_iter;
431 	GtkTreeIter group_iter;
432 
433 	walk_iter = *page;
434 	do {
435 		group_iter = walk_iter;
436 	} while (gtk_tree_model_iter_parent (model, &walk_iter, &group_iter));
437 	*page_group = group_iter;
438 }
439 
440 static int
441 compare_rows (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, RBDisplayPageModel *page_model)
442 {
443 	RBDisplayPage *a_page;
444 	RBDisplayPage *b_page;
445 	char *a_name;
446 	char *b_name;
447 	int ret;
448 
449 	gtk_tree_model_get (model, a, RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &a_page, -1);
450 	gtk_tree_model_get (model, b, RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &b_page, -1);
451 
452 	g_object_get (a_page, "name", &a_name, NULL);
453 	g_object_get (b_page, "name", &b_name, NULL);
454 
455 	if (RB_IS_DISPLAY_PAGE_GROUP (a_page) && RB_IS_DISPLAY_PAGE_GROUP (b_page)) {
456 		RBDisplayPageGroupCategory a_cat;
457 		RBDisplayPageGroupCategory b_cat;
458 		g_object_get (a_page, "category", &a_cat, NULL);
459 		g_object_get (b_page, "category", &b_cat, NULL);
460 		if (a_cat < b_cat) {
461 			ret = -1;
462 		} else if (a_cat > b_cat) {
463 			ret = 1;
464 		} else {
465 			ret = g_utf8_collate (a_name, b_name);
466 		}
467 	} else {
468 		/* walk up the tree until we find the group, then get its category
469 		 * to figure out how to sort the pages
470 		 */
471 		GtkTreeIter group_iter;
472 		RBDisplayPage *group_page;
473 		RBDisplayPageGroupCategory category;
474 		walk_up_to_page_group (model, &group_iter, a);
475 		gtk_tree_model_get (model, &group_iter, RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &group_page, -1);
476 		g_object_get (group_page, "category", &category, NULL);
477 		g_object_unref (group_page);
478 
479 		/* sort mostly by name */
480 		switch (category) {
481 		case RB_DISPLAY_PAGE_GROUP_CATEGORY_FIXED:
482 			/* fixed sources go in order of appearance */
483 			ret = -1;
484 			break;
485 		case RB_DISPLAY_PAGE_GROUP_CATEGORY_PERSISTENT:
486 			/* sort auto and static playlists separately */
487 			if (RB_IS_AUTO_PLAYLIST_SOURCE (a_page)
488 			    && RB_IS_AUTO_PLAYLIST_SOURCE (b_page)) {
489 				ret = g_utf8_collate (a_name, b_name);
490 			} else if (RB_IS_STATIC_PLAYLIST_SOURCE (a_page)
491 				   && RB_IS_STATIC_PLAYLIST_SOURCE (b_page)) {
492 				ret = g_utf8_collate (a_name, b_name);
493 			} else if (RB_IS_AUTO_PLAYLIST_SOURCE (a_page)) {
494 				ret = -1;
495 			} else {
496 				ret = 1;
497 			}
498 
499 			break;
500 		case RB_DISPLAY_PAGE_GROUP_CATEGORY_REMOVABLE:
501 		case RB_DISPLAY_PAGE_GROUP_CATEGORY_TRANSIENT:
502 			ret = g_utf8_collate (a_name, b_name);
503 			break;
504 		default:
505 			g_assert_not_reached ();
506 			break;
507 		}
508 	}
509 
510 	g_object_unref (a_page);
511 	g_object_unref (b_page);
512 	g_free (a_name);
513 	g_free (b_name);
514 
515 	return ret;
516 }
517 
518 static gboolean
519 rb_display_page_model_is_row_visible (GtkTreeModel *model,
520 				      GtkTreeIter *iter,
521 				      RBDisplayPageModel *page_model)
522 {
523 	RBDisplayPage *page = NULL;
524 	gboolean visibility = FALSE;
525 
526 	gtk_tree_model_get (model, iter,
527 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
528 			    -1);
529 	if (page != NULL) {
530 		g_object_get (page, "visibility", &visibility, NULL);
531 		g_object_unref (page);
532 	}
533 
534 	return visibility;
535 }
536 
537 static void
538 update_group_visibility (GtkTreeModel *model, GtkTreeIter *iter, RBDisplayPageModel *page_model)
539 {
540 	RBDisplayPage *page;
541 
542 	gtk_tree_model_get (model, iter,
543 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
544 			    -1);
545 	if (RB_IS_DISPLAY_PAGE_GROUP (page)) {
546 		gboolean has_children = FALSE;
547 		gboolean current;
548 		GtkTreeIter children;
549 
550 		if (gtk_tree_model_iter_children (model, &children, iter)) {
551 			do {
552 				has_children |= rb_display_page_model_is_row_visible (model, &children, page_model);
553 			} while (gtk_tree_model_iter_next (model, &children));
554 		}
555 
556 		g_object_get (page, "visibility", &current, NULL);
557 
558 		if (current != has_children) {
559 			char *name;
560 			g_object_get (page, "name", &name, NULL);
561 			rb_debug ("page group %s changing visibility from %d to %d", name, current, has_children);
562 			g_free (name);
563 
564 			g_object_set (page, "visibility", has_children, NULL);
565 		}
566 	}
567 	g_object_unref (page);
568 }
569 
570 
571 /**
572  * rb_display_page_model_set_dnd_targets:
573  * @page_model: the #RBDisplayPageModel
574  * @treeview: the sourcel ist #GtkTreeView
575  *
576  * Sets up the drag and drop targets for the display page tree.
577  */
578 void
579 rb_display_page_model_set_dnd_targets (RBDisplayPageModel *display_page_model,
580 				       GtkTreeView *treeview)
581 {
582 	int n_targets = G_N_ELEMENTS (dnd_targets);
583 
584 	rb_tree_dnd_add_drag_dest_support (treeview,
585 					   (RB_TREE_DEST_EMPTY_VIEW_DROP | RB_TREE_DEST_SELECT_ON_DRAG_TIMEOUT),
586 					   dnd_targets, n_targets,
587 					   GDK_ACTION_LINK);
588 
589 	rb_tree_dnd_add_drag_source_support (treeview,
590 					     GDK_BUTTON1_MASK,
591 					     dnd_targets, n_targets,
592 					     GDK_ACTION_COPY);
593 }
594 
595 
596 static void
597 page_notify_cb (GObject *object,
598 		GParamSpec *pspec,
599 		RBDisplayPageModel *page_model)
600 {
601 	RBDisplayPage *page = RB_DISPLAY_PAGE (object);
602 	GtkTreeIter iter;
603 	GtkTreeModel *model;
604 	GtkTreePath *path;
605 
606 	if (find_in_real_model (page_model, page, &iter) == FALSE) {
607 		return;
608 	}
609 
610 	model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (page_model));
611 	path = gtk_tree_model_get_path (model, &iter);
612 	gtk_tree_model_row_changed (model, path, &iter);
613 	gtk_tree_path_free (path);
614 
615 	/* update the page group's visibility */
616 	if (g_strcmp0 (pspec->name, "visibility") == 0 && RB_IS_DISPLAY_PAGE_GROUP (page) == FALSE) {
617 		GtkTreeIter group_iter;
618 		walk_up_to_page_group (model, &group_iter, &iter);
619 		update_group_visibility (model, &group_iter, page_model);
620 	}
621 }
622 
623 
624 /**
625  * rb_display_page_model_add_page:
626  * @page_model: the #RBDisplayPageModel
627  * @page: the #RBDisplayPage to add
628  * @parent: the parent under which to add @page
629  *
630  * Adds a page to the model, either below a specified page (if it's a source or
631  * something else) or at the top level (if it's a group)
632  */
633 void
634 rb_display_page_model_add_page (RBDisplayPageModel *page_model, RBDisplayPage *page, RBDisplayPage *parent)
635 {
636 	GtkTreeModel *model;
637 	GtkTreeIter parent_iter;
638 	GtkTreeIter group_iter;
639 	GtkTreeIter *parent_iter_ptr;
640 	GtkTreeIter iter;
641 	char *name;
642 	GList *child, *children;
643 
644 	g_return_if_fail (RB_IS_DISPLAY_PAGE_MODEL (page_model));
645 	g_return_if_fail (RB_IS_DISPLAY_PAGE (page));
646 
647 	g_object_get (page, "name", &name, NULL);
648 
649 	model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (page_model));
650 	if (parent != NULL) {
651 		if (find_in_real_model (page_model, parent, &parent_iter) == FALSE) {
652 			/* it's okay for a page to create and add its own children
653 			 * in its constructor, but we need to defer adding the children
654 			 * until the parent is added.
655 			 */
656 			rb_debug ("parent %p for source %s isn't in the model yet", parent, name);
657 			_rb_display_page_add_pending_child (parent, page);
658 			g_free (name);
659 			return;
660 		}
661 		rb_debug ("inserting source %s with parent %p", name, parent);
662 		parent_iter_ptr = &parent_iter;
663 	} else {
664 		rb_debug ("appending page %s with no parent", name);
665 		g_object_set (page, "visibility", FALSE, NULL);	/* hide until it has some content */
666 		parent_iter_ptr = NULL;
667 	}
668 	g_free (name);
669 
670 	gtk_tree_store_insert_with_values (GTK_TREE_STORE (model), &iter, parent_iter_ptr, G_MAXINT,
671 				           RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING, FALSE,
672 					   RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, page,
673 					   -1);
674 	g_signal_emit (G_OBJECT (page_model), rb_display_page_model_signals[PAGE_INSERTED], 0, page, &iter);
675 
676 	g_signal_connect_object (page, "notify::name", G_CALLBACK (page_notify_cb), page_model, 0);
677 	g_signal_connect_object (page, "notify::visibility", G_CALLBACK (page_notify_cb), page_model, 0);
678 	g_signal_connect_object (page, "notify::pixbuf", G_CALLBACK (page_notify_cb), page_model, 0);
679 
680 	walk_up_to_page_group (model, &group_iter, &iter);
681 	update_group_visibility (model, &group_iter, page_model);
682 
683 	children = _rb_display_page_get_pending_children (page);
684 	for (child = children; child != NULL; child = child->next) {
685 		rb_display_page_model_add_page (page_model, RB_DISPLAY_PAGE (child->data), page);
686 	}
687 	g_list_free (children);
688 }
689 
690 /**
691  * rb_display_page_model_remove_page:
692  * @page_model: the #RBDisplayPageModel
693  * @page: the #RBDisplayPage to remove
694  *
695  * Removes a page from the model.
696  */
697 void
698 rb_display_page_model_remove_page (RBDisplayPageModel *page_model,
699 				   RBDisplayPage *page)
700 {
701 	GtkTreeIter iter;
702 	GtkTreeIter group_iter;
703 	GtkTreeModel *model;
704 
705 	g_assert (find_in_real_model (page_model, page, &iter));
706 
707 	model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (page_model));
708 
709 	walk_up_to_page_group (model, &group_iter, &iter);
710 	gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
711 	g_signal_handlers_disconnect_by_func (page, G_CALLBACK (page_notify_cb), page_model);
712 
713 	update_group_visibility (model, &group_iter, page_model);
714 }
715 
716 
717 
718 /**
719  * rb_display_page_model_find_page:
720  * @page_model: the #RBDisplayPageModel
721  * @page: the #RBDisplayPage to find
722  * @iter: returns a #GtkTreeIter for the page
723  *
724  * Finds a #GtkTreeIter for a specified page in the model.  This will only
725  * find pages that are currently visible.  The returned #GtkTreeIter can be used
726  * with the #RBDisplayPageModel.
727  *
728  * Return value: %TRUE if the page was found
729  */
730 gboolean
731 rb_display_page_model_find_page (RBDisplayPageModel *page_model, RBDisplayPage *page, GtkTreeIter *iter)
732 {
733 	DisplayPageIter dpi = {0, };
734 	dpi.page = page;
735 
736 	gtk_tree_model_foreach (GTK_TREE_MODEL (page_model), (GtkTreeModelForeachFunc) match_page_to_iter, &dpi);
737 	if (dpi.found) {
738 		*iter = dpi.iter;
739 		return TRUE;
740 	} else {
741 		return FALSE;
742 	}
743 }
744 
745 static gboolean
746 set_playing_flag (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, RBDisplayPage *source)
747 {
748 	RBDisplayPage *page;
749 	gboolean old_playing;
750 
751 	gtk_tree_model_get (model,
752 			    iter,
753 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
754 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING, &old_playing,
755 			    -1);
756 	if (RB_IS_SOURCE (page)) {
757 		gboolean new_playing = (page == source);
758 		if (old_playing || new_playing) {
759 			gtk_tree_store_set (GTK_TREE_STORE (model),
760 					    iter,
761 					    RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING, new_playing,
762 					    -1);
763 		}
764 	}
765 	g_object_unref (page);
766 
767 	return FALSE;
768 }
769 
770 /**
771  * rb_display_page_model_set_playing_source:
772  * @page_model: the #RBDisplayPageModel
773  * @source: the new playing #RBSource (as a #RBDisplayPage)
774  *
775  * Updates the model with the new playing source.
776  */
777 void
778 rb_display_page_model_set_playing_source (RBDisplayPageModel *page_model, RBDisplayPage *source)
779 {
780 	GtkTreeModel *model;
781 	model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (page_model));
782 	gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc) set_playing_flag, source);
783 }
784 
785 /**
786  * rb_display_page_model_new:
787  *
788  * This constructs both the GtkTreeStore holding the display page
789  * data and the filter model that hides invisible pages.
790  *
791  * Return value: the #RBDisplayPageModel
792  */
793 RBDisplayPageModel *
794 rb_display_page_model_new (void)
795 {
796 	RBDisplayPageModel *model;
797 	GtkTreeStore *store;
798 	GType *column_types = g_new (GType, RB_DISPLAY_PAGE_MODEL_N_COLUMNS);
799 
800 	column_types[RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING] = G_TYPE_BOOLEAN;
801 	column_types[RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE] = RB_TYPE_DISPLAY_PAGE;
802 	store = gtk_tree_store_newv (RB_DISPLAY_PAGE_MODEL_N_COLUMNS,
803 				     column_types);
804 	gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
805 					 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE,
806 					 (GtkTreeIterCompareFunc) compare_rows,
807 					 NULL, NULL);
808 	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
809 					      RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE,
810 					      GTK_SORT_ASCENDING);
811 
812 	model = RB_DISPLAY_PAGE_MODEL (g_object_new (RB_TYPE_DISPLAY_PAGE_MODEL,
813 						     "child-model", store,
814 						     "virtual-root", NULL,
815 						     NULL));
816 	g_object_unref (store);
817 
818 	gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (model),
819 						(GtkTreeModelFilterVisibleFunc) rb_display_page_model_is_row_visible,
820 						model, NULL);
821 
822 	g_free (column_types);
823 
824 	return model;
825 }
826 
827 
828 static void
829 rb_display_page_model_init (RBDisplayPageModel *model)
830 {
831 }
832 
833 static void
834 rb_display_page_model_drag_dest_init (RbTreeDragDestIface *iface)
835 {
836 	iface->rb_drag_data_received = rb_display_page_model_drag_data_received;
837 	iface->rb_row_drop_possible = rb_display_page_model_row_drop_possible;
838 	iface->rb_row_drop_position = rb_display_page_model_row_drop_position;
839 	iface->rb_get_drag_target = rb_display_page_model_get_drag_target;
840 }
841 
842 static void
843 rb_display_page_model_drag_source_init (RbTreeDragSourceIface *iface)
844 {
845 	iface->rb_row_draggable = rb_display_page_model_row_draggable;
846 	iface->rb_drag_data_get = rb_display_page_model_drag_data_get;
847 	iface->rb_drag_data_delete = rb_display_page_model_drag_data_delete;
848 }
849 
850 static void
851 rb_display_page_model_class_init (RBDisplayPageModelClass *klass)
852 {
853 	GObjectClass   *object_class;
854 
855 	object_class = G_OBJECT_CLASS (klass);
856 
857 	/**
858 	 * RBDisplayPageModel::drop-received:
859 	 * @model: the #RBDisplayPageModel
860 	 * @target: the #RBSource receiving the drop
861 	 * @pos: the drop position
862 	 * @data: the drop data
863 	 *
864 	 * Emitted when a drag and drop operation to the display page tree completes.
865 	 */
866 	rb_display_page_model_signals[DROP_RECEIVED] =
867 		g_signal_new ("drop-received",
868 			      G_OBJECT_CLASS_TYPE (object_class),
869 			      G_SIGNAL_RUN_LAST,
870 			      G_STRUCT_OFFSET (RBDisplayPageModelClass, drop_received),
871 			      NULL, NULL,
872 			      rb_marshal_VOID__OBJECT_INT_POINTER,
873 			      G_TYPE_NONE,
874 			      3,
875 			      RB_TYPE_DISPLAY_PAGE, G_TYPE_INT, G_TYPE_POINTER);
876 
877 	/**
878 	 * RBDisplayPageModel::page-inserted:
879 	 * @model: the #RBDisplayPageModel
880 	 * @page: the #RBDisplayPage that was inserted
881 	 * @iter: a #GtkTreeIter indicating the page position
882 	 *
883 	 * Emitted when a new page is inserted into the model.
884 	 * Use this instead of GtkTreeModel::row-inserted as this
885 	 * doesn't get complicated by visibility filtering.
886 	 */
887 	rb_display_page_model_signals[PAGE_INSERTED] =
888 		g_signal_new ("page-inserted",
889 			      G_OBJECT_CLASS_TYPE (object_class),
890 			      G_SIGNAL_RUN_LAST,
891 			      G_STRUCT_OFFSET (RBDisplayPageModelClass, page_inserted),
892 			      NULL, NULL,
893 			      NULL,
894 			      G_TYPE_NONE,
895 			      2,
896 			      RB_TYPE_DISPLAY_PAGE, GTK_TYPE_TREE_ITER);
897 
898 	if (!drag_target_list) {
899 		drag_target_list = gtk_target_list_new (dnd_targets, G_N_ELEMENTS (dnd_targets));
900 	}
901 }
902 
903 /**
904  * RBDisplayPageModelColumn:
905  * @RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING: TRUE if the page is the playing source
906  * @RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE: the #RBDisplayPage object
907  * @RB_DISPLAY_PAGE_MODEL_N_COLUMNS: the number of columns
908  *
909  * Columns present in the display page model.
910  */
911 
912 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
913 
914 GType
915 rb_display_page_model_column_get_type (void)
916 {
917 	static GType etype = 0;
918 
919 	if (etype == 0)	{
920 		static const GEnumValue values[] = {
921 			ENUM_ENTRY (RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING, "playing"),
922 			ENUM_ENTRY (RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, "page"),
923 			{ 0, 0, 0 }
924 		};
925 
926 		etype = g_enum_register_static ("RBDisplayPageModelColumn", values);
927 	}
928 
929 	return etype;
930 }