hythmbox-2.98/rhythmdb/rhythmdb-property-model.c

No issues found

Incomplete coverage

Tool Failure ID Location Function Message Data
clang-analyzer no-output-found rhythmdb-property-model.c Message(text='Unable to locate XML output from invoke-clang-analyzer') None
clang-analyzer no-output-found rhythmdb-property-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@gnome.org>
   4  *
   5  *  This program is free software; you can redistribute it and/or modify
   6  *  it under the terms of the GNU General Public License as published by
   7  *  the Free Software Foundation; either version 2 of the License, or
   8  *  (at your option) any later version.
   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
  21  *  GNU General Public License for more details.
  22  *
  23  *  You should have received a copy of the GNU General Public License
  24  *  along with this program; if not, write to the Free Software
  25  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
  26  *
  27  */
  28 
  29 #include "config.h"
  30 
  31 #include <unistd.h>
  32 #include <stdlib.h>
  33 #include <string.h>
  34 #include <glib/gi18n.h>
  35 #include <glib.h>
  36 
  37 #include "rhythmdb-property-model.h"
  38 #include "rb-debug.h"
  39 #include "rb-refstring.h"
  40 #include "rb-tree-dnd.h"
  41 
  42 static void rhythmdb_property_model_tree_model_init (GtkTreeModelIface *iface);
  43 static void rhythmdb_property_model_drag_source_init (RbTreeDragSourceIface *iface);
  44 
  45 G_DEFINE_TYPE_WITH_CODE(RhythmDBPropertyModel, rhythmdb_property_model, G_TYPE_OBJECT,
  46 			G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL,
  47 					      rhythmdb_property_model_tree_model_init)
  48 			G_IMPLEMENT_INTERFACE(RB_TYPE_TREE_DRAG_SOURCE,
  49 					      rhythmdb_property_model_drag_source_init))
  50 
  51 /*
  52  * Structure for entries in the property model.
  53  * The sort string is derived from one of a list of properties, so we
  54  * store the index in the list of the property that gave us the current
  55  * sort string, so that if a newly added entry has a value for a more preferred
  56  * property, we can use that instead.
  57  */
  58 typedef struct {
  59 	RBRefString *string;
  60 	RBRefString *sort_string;
  61 	gint sort_string_from;
  62 	gint refcount;
  63 } RhythmDBPropertyModelEntry;
  64 
  65 static void rhythmdb_property_model_dispose (GObject *object);
  66 static void rhythmdb_property_model_finalize (GObject *object);
  67 static void rhythmdb_property_model_set_property (GObject *object,
  68 					       guint prop_id,
  69 					       const GValue *value,
  70 					       GParamSpec *pspec);
  71 static void rhythmdb_property_model_get_property (GObject *object,
  72 					       guint prop_id,
  73 					       GValue *value,
  74 					       GParamSpec *pspec);
  75 static void rhythmdb_property_model_sync (RhythmDBPropertyModel *model);
  76 static void rhythmdb_property_model_row_inserted_cb (GtkTreeModel *model,
  77 						     GtkTreePath *path,
  78 						     GtkTreeIter *iter,
  79 						     RhythmDBPropertyModel *propmodel);
  80 static void rhythmdb_property_model_prop_changed_cb (RhythmDB *db, RhythmDBEntry *entry,
  81 						     RhythmDBPropType prop, const GValue *old,
  82 						     const GValue *new,
  83 						     RhythmDBPropertyModel *propmodel);
  84 static void rhythmdb_property_model_entry_removed_cb (RhythmDBQueryModel *model,
  85 						      RhythmDBEntry *entry,
  86 						      RhythmDBPropertyModel *propmodel);
  87 static gboolean update_sort_string (RhythmDBPropertyModel *model,
  88                                     RhythmDBPropertyModelEntry *prop,
  89                                     RhythmDBEntry *entry);
  90 static void property_sort_changed (RhythmDBPropertyModel *model,
  91                                    GSequenceIter *ptr,
  92                                    GtkTreeIter *iter);
  93 static RhythmDBPropertyModelEntry* rhythmdb_property_model_insert (RhythmDBPropertyModel *model,
  94 								   RhythmDBEntry *entry);
  95 static void rhythmdb_property_model_delete (RhythmDBPropertyModel *model,
  96 					    RhythmDBEntry *entry);
  97 static void rhythmdb_property_model_delete_prop (RhythmDBPropertyModel *model,
  98 						 const char *propstr);
  99 static GtkTreeModelFlags rhythmdb_property_model_get_flags (GtkTreeModel *model);
 100 static gint rhythmdb_property_model_get_n_columns (GtkTreeModel *tree_model);
 101 static GType rhythmdb_property_model_get_column_type (GtkTreeModel *tree_model, int index);
 102 static gboolean rhythmdb_property_model_get_iter (GtkTreeModel *tree_model, GtkTreeIter *iter,
 103 					       GtkTreePath  *path);
 104 static GtkTreePath * rhythmdb_property_model_get_path (GtkTreeModel *tree_model,
 105 						    GtkTreeIter  *iter);
 106 static void rhythmdb_property_model_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter,
 107 					    gint column, GValue *value);
 108 static gboolean rhythmdb_property_model_iter_next (GtkTreeModel  *tree_model,
 109 						GtkTreeIter   *iter);
 110 static gboolean rhythmdb_property_model_iter_children (GtkTreeModel *tree_model,
 111 						    GtkTreeIter  *iter,
 112 						    GtkTreeIter  *parent);
 113 static gboolean rhythmdb_property_model_iter_has_child (GtkTreeModel *tree_model,
 114 						     GtkTreeIter  *iter);
 115 static gint rhythmdb_property_model_iter_n_children (GtkTreeModel *tree_model,
 116 						  GtkTreeIter  *iter);
 117 static gboolean rhythmdb_property_model_iter_nth_child (GtkTreeModel *tree_model,
 118 						     GtkTreeIter *iter, GtkTreeIter *parent,
 119 						     gint n);
 120 static gboolean rhythmdb_property_model_iter_parent (GtkTreeModel *tree_model,
 121 						  GtkTreeIter  *iter,
 122 						  GtkTreeIter  *child);
 123 
 124 static gboolean rhythmdb_property_model_drag_data_get (RbTreeDragSource *dragsource,
 125 						       GList *paths,
 126 						       GtkSelectionData *selection_data);
 127 static gboolean rhythmdb_property_model_drag_data_delete (RbTreeDragSource *dragsource,
 128 							  GList *paths);
 129 static gboolean rhythmdb_property_model_row_draggable (RbTreeDragSource *dragsource,
 130 						       GList *paths);
 131 
 132 enum {
 133 	TARGET_ALBUMS,
 134 	TARGET_GENRE,
 135 	TARGET_ARTISTS,
 136 	TARGET_LOCATION,
 137 	TARGET_ENTRIES,
 138 	TARGET_URIS,
 139 };
 140 
 141 static const GtkTargetEntry targets_album  [] = {
 142 	{ "text/x-rhythmbox-album",  0, TARGET_ALBUMS },
 143 	{ "application/x-rhythmbox-entry", 0, TARGET_ENTRIES },
 144 	{ "text/uri-list", 0, TARGET_URIS },
 145 };
 146 static const GtkTargetEntry targets_genre  [] = {
 147 	{ "text/x-rhythmbox-genre",  0, TARGET_GENRE },
 148 	{ "application/x-rhythmbox-entry", 0, TARGET_ENTRIES },
 149 	{ "text/uri-list", 0, TARGET_URIS },
 150 };
 151 static const GtkTargetEntry targets_artist [] = {
 152 	{ "text/x-rhythmbox-artist", 0, TARGET_ARTISTS },
 153 	{ "application/x-rhythmbox-entry", 0, TARGET_ENTRIES },
 154 	{ "text/uri-list", 0, TARGET_URIS },
 155 };
 156 static const GtkTargetEntry targets_location [] = {
 157 	{ "text/x-rhythmbox-location", 0, TARGET_LOCATION },
 158 	{ "application/x-rhythmbox-entry", 0, TARGET_ENTRIES },
 159 	{ "text/uri-list", 0, TARGET_URIS },
 160 };
 161 
 162 static GtkTargetList *rhythmdb_property_model_album_drag_target_list = NULL;
 163 static GtkTargetList *rhythmdb_property_model_artist_drag_target_list = NULL;
 164 static GtkTargetList *rhythmdb_property_model_genre_drag_target_list = NULL;
 165 static GtkTargetList *rhythmdb_property_model_location_drag_target_list = NULL;
 166 
 167 struct RhythmDBPropertyModelPrivate
 168 {
 169 	RhythmDB *db;
 170 
 171 	RhythmDBQueryModel *query_model;
 172 	GHashTable *entries;
 173 
 174 	RhythmDBPropType propid;
 175 	GArray *sort_propids;
 176 
 177 	guint stamp;
 178 
 179 	GSequence *properties;
 180 	GHashTable *reverse_map;
 181 
 182 	RhythmDBPropertyModelEntry *all;
 183 
 184 	guint syncing_id;
 185 };
 186 
 187 #define RHYTHMDB_PROPERTY_MODEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE_PROPERTY_MODEL, RhythmDBPropertyModelPrivate))
 188 
 189 enum
 190 {
 191 	PRE_ROW_DELETION,
 192 	LAST_SIGNAL
 193 };
 194 
 195 enum
 196 {
 197 	PROP_0,
 198 	PROP_RHYTHMDB,
 199 	PROP_PROP,
 200 	PROP_QUERY_MODEL,
 201 };
 202 
 203 static guint rhythmdb_property_model_signals[LAST_SIGNAL] = { 0 };
 204 
 205 /**
 206  * SECTION:rhythmdb-property-model
 207  * @short_description:  tree model grouping entries from a query model by property values
 208  *
 209  * A RhythmDBPropertyModel groups the entries in a #RhythmDBQueryModel by
 210  * the value of a property.  For example, a RhythmDBPropertyModel using
 211  * the RHYTHMDB_PROP_ARTIST property can be used as the model for a 
 212  * #GtkTreeView that will list the artists present in the query model.
 213  *
 214  * The album/artist/genre browsers displayed in the library and other sources are
 215  * populated using a RhythmDBPropertyModel for each property.
 216  */
 217 
 218 static void
 219 rhythmdb_property_model_class_init (RhythmDBPropertyModelClass *klass)
 220 {
 221 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 222 
 223 	if (!rhythmdb_property_model_artist_drag_target_list)
 224 		rhythmdb_property_model_artist_drag_target_list =
 225 			gtk_target_list_new (targets_artist,
 226 					     G_N_ELEMENTS (targets_artist));
 227 	if (!rhythmdb_property_model_album_drag_target_list)
 228 		rhythmdb_property_model_album_drag_target_list =
 229 			gtk_target_list_new (targets_album,
 230 					     G_N_ELEMENTS (targets_album));
 231 	if (!rhythmdb_property_model_genre_drag_target_list)
 232 		rhythmdb_property_model_genre_drag_target_list =
 233 			gtk_target_list_new (targets_genre,
 234 					     G_N_ELEMENTS (targets_genre));
 235 	if (!rhythmdb_property_model_location_drag_target_list)
 236 		rhythmdb_property_model_location_drag_target_list =
 237 			gtk_target_list_new (targets_location,
 238 					     G_N_ELEMENTS (targets_location));
 239 
 240 	object_class->set_property = rhythmdb_property_model_set_property;
 241 	object_class->get_property = rhythmdb_property_model_get_property;
 242 
 243 	object_class->dispose = rhythmdb_property_model_dispose;
 244 	object_class->finalize = rhythmdb_property_model_finalize;
 245 
 246 	/**
 247 	 * RhythmDBPropertyModel::pre-row-deletion:
 248 	 * @model: the #RhythmDBPropertyModel
 249 	 *
 250 	 * Emitted just before a row is deleted from the model.
 251 	 */
 252 	rhythmdb_property_model_signals[PRE_ROW_DELETION] =
 253 		g_signal_new ("pre-row-deletion",
 254 			      G_OBJECT_CLASS_TYPE (object_class),
 255 			      G_SIGNAL_RUN_LAST,
 256 			      G_STRUCT_OFFSET (RhythmDBPropertyModelClass, pre_row_deletion),
 257 			      NULL, NULL,
 258 			      g_cclosure_marshal_VOID__VOID,
 259 			      G_TYPE_NONE,
 260 			      0);
 261 
 262 	/**
 263 	 * RhythmDBPropertyModel:db:
 264 	 *
 265 	 * The #RhythmDB object the model is associated with.
 266 	 */
 267 	g_object_class_install_property (object_class,
 268 					 PROP_RHYTHMDB,
 269 					 g_param_spec_object ("db",
 270 							      "RhythmDB",
 271 							      "RhythmDB object",
 272 							      RHYTHMDB_TYPE,
 273 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 274 
 275 	/**
 276 	 * RhythmDBPropertyModel:prop:
 277 	 *
 278 	 * The property that this property model indexes.
 279 	 */
 280 	g_object_class_install_property (object_class,
 281 					 PROP_PROP,
 282 					 g_param_spec_int ("prop",
 283 							   "propid",
 284 							   "Property id",
 285 							   0, RHYTHMDB_NUM_PROPERTIES,
 286 							   RHYTHMDB_PROP_TYPE,
 287 							   G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 288 
 289 	/**
 290 	 * RhythmDBPropertyModel:query-model:
 291 	 *
 292 	 * The query model that this property model indexes.
 293 	 */
 294 	g_object_class_install_property (object_class,
 295 					 PROP_QUERY_MODEL,
 296 					 g_param_spec_object ("query-model",
 297 							      "RhythmDBQueryModel",
 298 							      "RhythmDBQueryModel object ",
 299 							      RHYTHMDB_TYPE_QUERY_MODEL,
 300 							      G_PARAM_READWRITE));
 301 
 302 	g_type_class_add_private (klass, sizeof (RhythmDBPropertyModelPrivate));
 303 }
 304 
 305 static void
 306 rhythmdb_property_model_tree_model_init (GtkTreeModelIface *iface)
 307 {
 308 	iface->get_flags = rhythmdb_property_model_get_flags;
 309 	iface->get_n_columns = rhythmdb_property_model_get_n_columns;
 310 	iface->get_column_type = rhythmdb_property_model_get_column_type;
 311 	iface->get_iter = rhythmdb_property_model_get_iter;
 312 	iface->get_path = rhythmdb_property_model_get_path;
 313 	iface->get_value = rhythmdb_property_model_get_value;
 314 	iface->iter_next = rhythmdb_property_model_iter_next;
 315 	iface->iter_children = rhythmdb_property_model_iter_children;
 316 	iface->iter_has_child = rhythmdb_property_model_iter_has_child;
 317 	iface->iter_n_children = rhythmdb_property_model_iter_n_children;
 318 	iface->iter_nth_child = rhythmdb_property_model_iter_nth_child;
 319 	iface->iter_parent = rhythmdb_property_model_iter_parent;
 320 }
 321 
 322 static void
 323 rhythmdb_property_model_drag_source_init (RbTreeDragSourceIface *iface)
 324 {
 325 	iface->rb_row_draggable = rhythmdb_property_model_row_draggable;
 326 	iface->rb_drag_data_delete = rhythmdb_property_model_drag_data_delete;
 327 	iface->rb_drag_data_get = rhythmdb_property_model_drag_data_get;
 328 }
 329 
 330 static gboolean
 331 _remove_entry_cb (GtkTreeModel *model,
 332 		  GtkTreePath *path,
 333 		  GtkTreeIter *iter,
 334 		  RhythmDBPropertyModel *propmodel)
 335 {
 336 	RhythmDBEntry *entry;
 337 
 338 	entry = rhythmdb_query_model_iter_to_entry (RHYTHMDB_QUERY_MODEL (model), iter);
 339 	rhythmdb_property_model_entry_removed_cb (RHYTHMDB_QUERY_MODEL (model),
 340 						  entry,
 341 						  propmodel);
 342 	rhythmdb_entry_unref (entry);
 343 	return FALSE;
 344 }
 345 
 346 static gboolean
 347 _add_entry_cb (GtkTreeModel *model,
 348 	       GtkTreePath *path,
 349 	       GtkTreeIter *iter,
 350 	       RhythmDBPropertyModel *propmodel)
 351 {
 352 	rhythmdb_property_model_row_inserted_cb (model, path, iter, propmodel);
 353 	return FALSE;
 354 }
 355 
 356 static void
 357 rhythmdb_property_model_set_query_model_internal (RhythmDBPropertyModel *model,
 358 						  RhythmDBQueryModel    *query_model)
 359 {
 360 	if (model->priv->query_model != NULL) {
 361 		g_signal_handlers_disconnect_by_func (model->priv->query_model,
 362 						      G_CALLBACK (rhythmdb_property_model_row_inserted_cb),
 363 						      model);
 364 		g_signal_handlers_disconnect_by_func (model->priv->query_model,
 365 						      G_CALLBACK (rhythmdb_property_model_entry_removed_cb),
 366 						      model);
 367 		g_signal_handlers_disconnect_by_func (model->priv->query_model,
 368 						      G_CALLBACK (rhythmdb_property_model_prop_changed_cb),
 369 						      model);
 370 
 371 		gtk_tree_model_foreach (GTK_TREE_MODEL (model->priv->query_model),
 372 					(GtkTreeModelForeachFunc)_remove_entry_cb,
 373 					model);
 374 
 375 		g_object_unref (model->priv->query_model);
 376 	}
 377 
 378 	model->priv->query_model = query_model;
 379 	g_assert (rhythmdb_property_model_iter_n_children (GTK_TREE_MODEL (model), NULL) == 1);
 380 
 381 	if (model->priv->query_model != NULL) {
 382 		g_object_ref (model->priv->query_model);
 383 
 384 		g_signal_connect_object (model->priv->query_model,
 385 					 "row_inserted",
 386 					 G_CALLBACK (rhythmdb_property_model_row_inserted_cb),
 387 					 model,
 388 					 0);
 389 		g_signal_connect_object (model->priv->query_model,
 390 					 "post-entry-delete",
 391 					 G_CALLBACK (rhythmdb_property_model_entry_removed_cb),
 392 					 model,
 393 					 0);
 394 		g_signal_connect_object (model->priv->query_model,
 395 					 "entry-prop-changed",
 396 					 G_CALLBACK (rhythmdb_property_model_prop_changed_cb),
 397 					 model,
 398 					 0);
 399 		gtk_tree_model_foreach (GTK_TREE_MODEL (model->priv->query_model),
 400 					(GtkTreeModelForeachFunc)_add_entry_cb,
 401 					model);
 402 	}
 403 }
 404 
 405 static void
 406 append_sort_property (RhythmDBPropertyModel *model, RhythmDBPropType prop)
 407 {
 408 	RhythmDBPropType p = prop;
 409 	g_array_append_val (model->priv->sort_propids, p);
 410 }
 411 
 412 static void
 413 rhythmdb_property_model_set_property (GObject *object,
 414 				      guint prop_id,
 415 				      const GValue *value,
 416 				      GParamSpec *pspec)
 417 {
 418 	RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (object);
 419 
 420 	switch (prop_id) {
 421 	case PROP_RHYTHMDB:
 422 		model->priv->db = g_value_get_object (value);
 423 		break;
 424 	case PROP_PROP:
 425 		model->priv->propid = g_value_get_int (value);
 426 		switch (model->priv->propid) {
 427 		case RHYTHMDB_PROP_GENRE:
 428 			append_sort_property (model, RHYTHMDB_PROP_GENRE);
 429 			break;
 430 		case RHYTHMDB_PROP_ARTIST:
 431 			append_sort_property (model, RHYTHMDB_PROP_ARTIST_SORTNAME);
 432 			append_sort_property (model, RHYTHMDB_PROP_ARTIST);
 433 			break;
 434 		case RHYTHMDB_PROP_ALBUM:
 435 			append_sort_property (model, RHYTHMDB_PROP_ALBUM_SORTNAME);
 436 			append_sort_property (model, RHYTHMDB_PROP_ALBUM);
 437 			break;
 438 		case RHYTHMDB_PROP_SUBTITLE:
 439 			append_sort_property (model, RHYTHMDB_PROP_ALBUM);
 440 			append_sort_property (model, RHYTHMDB_PROP_SUBTITLE);
 441 			break;
 442 		case RHYTHMDB_PROP_TITLE:
 443 		case RHYTHMDB_PROP_LOCATION:
 444 			append_sort_property (model, RHYTHMDB_PROP_TITLE);
 445 			break;
 446 		default:
 447 			g_assert_not_reached ();
 448 			break;
 449 		}
 450 		break;
 451 	case PROP_QUERY_MODEL:
 452 		rhythmdb_property_model_set_query_model_internal (model, g_value_get_object (value));
 453 		break;
 454 	default:
 455 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 456 		break;
 457 	}
 458 }
 459 
 460 static void
 461 rhythmdb_property_model_get_property (GObject *object,
 462 				      guint prop_id,
 463 				      GValue *value,
 464 				      GParamSpec *pspec)
 465 {
 466 	RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (object);
 467 
 468 	switch (prop_id) {
 469 	case PROP_RHYTHMDB:
 470 		g_value_set_object (value, model->priv->db);
 471 		break;
 472 	case PROP_PROP:
 473 		g_value_set_int (value, model->priv->propid);
 474 		break;
 475 	case PROP_QUERY_MODEL:
 476 		g_value_set_object (value, model->priv->query_model);
 477 		break;
 478 	default:
 479 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 480 		break;
 481 	}
 482 }
 483 
 484 static void
 485 rhythmdb_property_model_init (RhythmDBPropertyModel *model)
 486 {
 487 	model->priv = RHYTHMDB_PROPERTY_MODEL_GET_PRIVATE (model);
 488 
 489 	model->priv->stamp = g_random_int ();
 490 
 491 	model->priv->properties = g_sequence_new (NULL);
 492 	model->priv->reverse_map = g_hash_table_new (g_str_hash, g_str_equal);
 493 	model->priv->entries = g_hash_table_new (g_direct_hash, g_direct_equal);
 494 
 495 	model->priv->all = g_new0 (RhythmDBPropertyModelEntry, 1);
 496 	model->priv->all->string = rb_refstring_new (_("All"));
 497 
 498 	model->priv->sort_propids = g_array_new (FALSE, FALSE, sizeof (RhythmDBPropType));
 499 }
 500 
 501 static void
 502 _prop_model_entry_cleanup (RhythmDBPropertyModelEntry *prop, gpointer data)
 503 {
 504 	rb_refstring_unref (prop->string);
 505 	rb_refstring_unref (prop->sort_string);
 506 	g_free (prop);
 507 }
 508 
 509 static void
 510 rhythmdb_property_model_dispose (GObject *object)
 511 {
 512 	RhythmDBPropertyModel *model;
 513 
 514 	g_return_if_fail (object != NULL);
 515 	g_return_if_fail (RHYTHMDB_IS_PROPERTY_MODEL (object));
 516 
 517 	model = RHYTHMDB_PROPERTY_MODEL (object);
 518 
 519 	rb_debug ("disposing property model %p", model);
 520 
 521 	g_return_if_fail (model->priv != NULL);
 522 
 523 	if (model->priv->syncing_id != 0) {
 524 		g_source_remove (model->priv->syncing_id);
 525 		model->priv->syncing_id = 0;
 526 	}
 527 
 528 	if (model->priv->query_model != NULL) {
 529 		g_object_unref (model->priv->query_model);
 530 		model->priv->query_model = NULL;
 531 	}
 532 
 533 	G_OBJECT_CLASS (rhythmdb_property_model_parent_class)->dispose (object);
 534 }
 535 
 536 static void
 537 rhythmdb_property_model_finalize (GObject *object)
 538 {
 539 	RhythmDBPropertyModel *model;
 540 
 541 	g_return_if_fail (object != NULL);
 542 	g_return_if_fail (RHYTHMDB_IS_PROPERTY_MODEL (object));
 543 
 544 	model = RHYTHMDB_PROPERTY_MODEL (object);
 545 
 546 	rb_debug ("finalizing property model %p", model);
 547 
 548 	g_return_if_fail (model->priv != NULL);
 549 
 550 	g_hash_table_destroy (model->priv->reverse_map);
 551 
 552 	g_sequence_foreach (model->priv->properties, (GFunc)_prop_model_entry_cleanup, NULL);
 553 	g_sequence_free (model->priv->properties);
 554 
 555 	g_hash_table_destroy (model->priv->entries);
 556 
 557 	g_free (model->priv->all);
 558 
 559 	g_array_free (model->priv->sort_propids, TRUE);
 560 
 561 	G_OBJECT_CLASS (rhythmdb_property_model_parent_class)->finalize (object);
 562 }
 563 
 564 /**
 565  * rhythmdb_property_model_new:
 566  * @db: the #RhythmDB object
 567  * @propid: the property to index
 568  *
 569  * Creates a new property model for the specified property ID.
 570  *
 571  * Return value: the new #RhythmDBPropertyModel
 572  */
 573 RhythmDBPropertyModel *
 574 rhythmdb_property_model_new (RhythmDB *db,
 575 			     RhythmDBPropType propid)
 576 {
 577 	return g_object_new (RHYTHMDB_TYPE_PROPERTY_MODEL, "db", db, "prop", propid, NULL);
 578 }
 579 
 580 static void
 581 rhythmdb_property_model_row_inserted_cb (GtkTreeModel *model,
 582 					 GtkTreePath *path,
 583 					 GtkTreeIter *iter,
 584 					 RhythmDBPropertyModel *propmodel)
 585 {
 586 	RhythmDBEntry *entry;
 587 
 588 	entry = rhythmdb_query_model_iter_to_entry (RHYTHMDB_QUERY_MODEL (model), iter);
 589 
 590 	rhythmdb_property_model_insert (propmodel, entry);
 591 	rhythmdb_property_model_sync (propmodel);
 592 
 593 	rhythmdb_entry_unref (entry);
 594 }
 595 
 596 static void
 597 rhythmdb_property_model_prop_changed_cb (RhythmDB *db,
 598 					 RhythmDBEntry *entry,
 599 					 RhythmDBPropType propid,
 600 					 const GValue *old,
 601 					 const GValue *new,
 602 					 RhythmDBPropertyModel *propmodel)
 603 {
 604 	if (propid == RHYTHMDB_PROP_HIDDEN) {
 605 		gboolean old_val = g_value_get_boolean (old);
 606 		gboolean new_val = g_value_get_boolean (new);
 607 
 608 		if (old_val != new_val) {
 609 			if (new_val == FALSE) {
 610 				g_assert (g_hash_table_remove (propmodel->priv->entries, entry));
 611 				rhythmdb_property_model_insert (propmodel, entry);
 612 			} else {
 613 				g_assert (g_hash_table_lookup (propmodel->priv->entries, entry) == NULL);
 614 
 615 				rhythmdb_property_model_delete (propmodel, entry);
 616 				g_hash_table_insert (propmodel->priv->entries, entry, GINT_TO_POINTER (1));
 617 			}
 618 			rhythmdb_property_model_sync (propmodel);
 619 		}
 620 	} else if (g_hash_table_lookup (propmodel->priv->entries, entry) == NULL) {
 621 		RhythmDBPropertyModelEntry *prop;
 622 
 623 		if (propid == propmodel->priv->propid) {
 624 			/* the updated property is the propmodel's prop */
 625 			rhythmdb_property_model_delete_prop (propmodel, g_value_get_string (old));
 626 			prop = rhythmdb_property_model_insert (propmodel, entry);
 627 			rhythmdb_property_model_sync (propmodel);
 628 		} else {
 629 			int pi;
 630 			const char *propstr;
 631 			GtkTreeIter iter;
 632 			GSequenceIter *ptr;
 633 
 634 			/* check if the updated property is one of the propmodel's sort props */
 635 			for (pi = 0; pi < propmodel->priv->sort_propids->len; pi++) {
 636 				if (propid == g_array_index (propmodel->priv->sort_propids, RhythmDBPropType, pi)) {
 637 					propstr = rhythmdb_entry_get_string (entry, propmodel->priv->propid);
 638 					ptr = g_hash_table_lookup (propmodel->priv->reverse_map, propstr);
 639 					prop = g_sequence_get (ptr);
 640 					iter.stamp = propmodel->priv->stamp;
 641 					iter.user_data = ptr;
 642 
 643 					/* check if the sort prop needs updated and the propmodel needs resorting */
 644 					if (update_sort_string (propmodel, prop, entry)) {
 645 						property_sort_changed (propmodel, ptr, &iter);
 646 					} else if (pi == prop->sort_string_from) {
 647 						rb_refstring_unref (prop->sort_string);
 648 						prop->sort_string = rb_refstring_new (g_value_get_string (new));
 649 						property_sort_changed (propmodel, ptr, &iter);
 650 					}
 651 					break;
 652 				}
 653 			}
 654 		}
 655 	}
 656 }
 657 
 658 static void
 659 rhythmdb_property_model_entry_removed_cb (RhythmDBQueryModel *model,
 660 					  RhythmDBEntry *entry,
 661 					  RhythmDBPropertyModel *propmodel)
 662 {
 663 	if (g_hash_table_remove (propmodel->priv->entries, entry))
 664 		return;
 665 
 666 	rhythmdb_property_model_delete (propmodel, entry);
 667 	rhythmdb_property_model_sync (propmodel);
 668 }
 669 
 670 static gint
 671 rhythmdb_property_model_compare (RhythmDBPropertyModelEntry *a,
 672 				 RhythmDBPropertyModelEntry *b,
 673 				 RhythmDBPropertyModel *model)
 674 {
 675 	const char *a_str, *b_str;
 676 
 677 	a_str = rb_refstring_get_sort_key (a->sort_string);
 678 	b_str = rb_refstring_get_sort_key (b->sort_string);
 679 
 680 	return strcmp (a_str, b_str);
 681 }
 682 
 683 /*
 684  * looks for an updated sort string in a new entry for the specified
 685  * property.  if the new entry provides a value for a higher priority
 686  * sort property, updates the property to use the new sort string and
 687  * returns TRUE.  otherwise, returns FALSE.
 688  */
 689 static gboolean
 690 update_sort_string (RhythmDBPropertyModel *model,
 691 		    RhythmDBPropertyModelEntry *prop,
 692 		    RhythmDBEntry *entry)
 693 {
 694 	const char *newvalue = NULL;
 695 	int pi;
 696 	int upto;
 697 
 698 	/* if the property that gave us the current sort string is being cleared,
 699 	 * forget about the current string.
 700 	 */
 701 	if (prop->sort_string != NULL) {
 702 		RhythmDBPropType propid;
 703 		const char *newvalue;
 704 
 705 		propid = g_array_index (model->priv->sort_propids, RhythmDBPropType, prop->sort_string_from);
 706 		newvalue = rhythmdb_entry_get_string (entry, propid);
 707 		if (newvalue == NULL || newvalue[0] == '\0') {
 708 			rb_debug ("current sort string %s is being removed", rb_refstring_get (prop->sort_string));
 709 			rb_refstring_unref (prop->sort_string);
 710 			prop->sort_string = NULL;
 711 		}
 712 	}
 713 
 714 	/* we only need to check properties that are preferred to the one that
 715 	 * gave us the current sort string.
 716 	 */
 717 	upto = model->priv->sort_propids->len;
 718 	if (prop->sort_string != NULL) {
 719 		upto = prop->sort_string_from;
 720 	}
 721 
 722 	/* search for a non-empty sort string value */
 723 	for (pi = 0; pi < upto; pi++) {
 724 		RhythmDBPropType propid;
 725 
 726 		propid = g_array_index (model->priv->sort_propids, RhythmDBPropType, pi);
 727 		newvalue = rhythmdb_entry_get_string (entry, propid);
 728 		if (newvalue != NULL && newvalue[0] != '\0') {
 729 			break;
 730 		}
 731 	}
 732 
 733 	/* if we found one, replace the current sort string */
 734 	if (newvalue != NULL && newvalue[0] != '\0' && (prop->sort_string == NULL || pi < prop->sort_string_from)) {
 735 		rb_debug ("replacing current sort string %s with %s (%d -> %d)",
 736 			  prop->sort_string ? rb_refstring_get (prop->sort_string) : "NULL",
 737 			  newvalue,
 738 			  prop->sort_string_from,
 739 			  pi);
 740 		if (prop->sort_string != NULL) {
 741 			rb_refstring_unref (prop->sort_string);
 742 		}
 743 		prop->sort_string = rb_refstring_new (newvalue);
 744 		g_assert (pi < model->priv->sort_propids->len);
 745 		prop->sort_string_from = pi;
 746 		return TRUE;
 747 	}
 748 
 749 	/* if we can't find a sort string at all, use the display string as fallback */
 750 	if (prop->sort_string == NULL) {
 751 		prop->sort_string = rb_refstring_ref (prop->string);
 752 	}
 753 
 754 	return FALSE;
 755 }
 756 
 757 /*
 758  * to be called when a property's position in the sorted order may have changed.
 759  * emits row-deleted, resorts the property sequence, and emits row-inserted.
 760  */
 761 static void
 762 property_sort_changed (RhythmDBPropertyModel *model, GSequenceIter *ptr, GtkTreeIter *iter)
 763 {
 764 	GtkTreePath *path;
 765 
 766 	path = rhythmdb_property_model_get_path (GTK_TREE_MODEL (model), iter);
 767 	gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
 768 	gtk_tree_path_free (path);
 769 
 770 	g_sequence_sort_changed (ptr,
 771 				 (GCompareDataFunc) rhythmdb_property_model_compare,
 772 				 model);
 773 
 774 	path = rhythmdb_property_model_get_path (GTK_TREE_MODEL (model), iter);
 775 	gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, iter);
 776 	gtk_tree_path_free (path);
 777 }
 778 
 779 static RhythmDBPropertyModelEntry *
 780 rhythmdb_property_model_insert (RhythmDBPropertyModel *model,
 781 				RhythmDBEntry *entry)
 782 {
 783 	RhythmDBPropertyModelEntry *prop;
 784 	GtkTreeIter iter;
 785 	GtkTreePath *path;
 786 	GSequenceIter *ptr;
 787 	const char *propstr;
 788 
 789 	iter.stamp = model->priv->stamp;
 790 	propstr = rhythmdb_entry_get_string (entry, model->priv->propid);
 791 
 792 	g_atomic_int_inc (&model->priv->all->refcount);
 793 
 794 	if ((ptr = g_hash_table_lookup (model->priv->reverse_map, propstr))) {
 795 		iter.user_data = ptr;
 796 		prop = g_sequence_get (ptr);
 797 		g_atomic_int_inc (&prop->refcount);
 798 		rb_debug ("adding \"%s\": refcount %d", propstr, prop->refcount);
 799 
 800 		if (update_sort_string (model, prop, entry)) {
 801 			property_sort_changed (model, ptr, &iter);
 802 		}
 803 
 804 		path = rhythmdb_property_model_get_path (GTK_TREE_MODEL (model), &iter);
 805 		gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
 806 		gtk_tree_path_free (path);
 807 
 808 		return prop;
 809 	}
 810 	rb_debug ("adding new property \"%s\"", propstr);
 811 
 812 	prop = g_new0 (RhythmDBPropertyModelEntry, 1);
 813 	prop->string = rb_refstring_new (propstr);
 814 	update_sort_string (model, prop, entry);
 815 	g_atomic_int_set (&prop->refcount, 1);
 816 
 817 	ptr = g_sequence_insert_sorted (model->priv->properties, prop,
 818 					(GCompareDataFunc) rhythmdb_property_model_compare,
 819 					model);
 820 	g_hash_table_insert (model->priv->reverse_map,
 821 			     (gpointer)rb_refstring_get (prop->string),
 822 			     ptr);
 823 
 824 	iter.user_data = ptr;
 825 	path = rhythmdb_property_model_get_path (GTK_TREE_MODEL (model), &iter);
 826 	gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
 827 	gtk_tree_path_free (path);
 828 
 829 	return prop;
 830 }
 831 
 832 static void
 833 rhythmdb_property_model_delete (RhythmDBPropertyModel *model,
 834 				RhythmDBEntry *entry)
 835 {
 836 	const char *propstr;
 837 
 838 	if (g_hash_table_lookup (model->priv->entries, entry))
 839 		return;
 840 
 841 	propstr = rhythmdb_entry_get_string (entry, model->priv->propid);
 842 	rhythmdb_property_model_delete_prop (model, propstr);
 843 }
 844 
 845 static void
 846 rhythmdb_property_model_delete_prop (RhythmDBPropertyModel *model,
 847 				     const char *propstr)
 848 {
 849 	GSequenceIter *ptr;
 850 	RhythmDBPropertyModelEntry *prop;
 851 	GtkTreePath *path;
 852 	GtkTreeIter iter;
 853 	gboolean ret;
 854 
 855 	g_assert ((ptr = g_hash_table_lookup (model->priv->reverse_map, propstr)));
 856 
 857 	iter.stamp = model->priv->stamp;
 858 	iter.user_data = ptr;
 859 
 860 	ret = g_atomic_int_dec_and_test (&model->priv->all->refcount);
 861 
 862 	prop = g_sequence_get (ptr);
 863 	rb_debug ("deleting \"%s\": refcount: %d", propstr, prop->refcount);
 864 	if (g_atomic_int_dec_and_test (&prop->refcount) == FALSE) {
 865 		g_assert (ret == FALSE);
 866 		path = rhythmdb_property_model_get_path (GTK_TREE_MODEL (model), &iter);
 867 		gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
 868 		gtk_tree_path_free (path);
 869 		return;
 870 	}
 871 
 872 	path = rhythmdb_property_model_get_path (GTK_TREE_MODEL (model), &iter);
 873 	g_signal_emit (G_OBJECT (model), rhythmdb_property_model_signals[PRE_ROW_DELETION], 0);
 874 	gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
 875 	gtk_tree_path_free (path);
 876 
 877 	g_sequence_remove (ptr);
 878 	g_hash_table_remove (model->priv->reverse_map, propstr);
 879 	prop->refcount = 0xdeadbeef;
 880 	rb_refstring_unref (prop->string);
 881 	rb_refstring_unref (prop->sort_string);
 882 
 883 	g_free (prop);
 884 }
 885 
 886 /**
 887  * rhythmdb_property_model_iter_from_string:
 888  * @model: the #RhythmDBPropertyModel
 889  * @name: the property value to find
 890  * @iter: a #GtkTreeIter to point to the row
 891  *
 892  * Locates the row in the model for a property value.
 893  *
 894  * Return value: TRUE if the value was found.
 895  */
 896 gboolean
 897 rhythmdb_property_model_iter_from_string (RhythmDBPropertyModel *model,
 898 					  const char *name,
 899 					  GtkTreeIter *iter)
 900 {
 901 	GSequenceIter *ptr;
 902 
 903 	if (name == NULL) {
 904 		if (iter) {
 905 			iter->stamp = model->priv->stamp;
 906 			iter->user_data = model->priv->all;
 907 		}
 908 		return TRUE;
 909 	}
 910 
 911 	ptr = g_hash_table_lookup (model->priv->reverse_map, name);
 912 	if (!ptr)
 913 		return FALSE;
 914 
 915 	if (iter) {
 916 		iter->stamp = model->priv->stamp;
 917 		iter->user_data = ptr;
 918 	}
 919 
 920 	return TRUE;
 921 }
 922 
 923 static GtkTreeModelFlags
 924 rhythmdb_property_model_get_flags (GtkTreeModel *model)
 925 {
 926 	return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY;
 927 }
 928 
 929 static gint
 930 rhythmdb_property_model_get_n_columns (GtkTreeModel *tree_model)
 931 {
 932 	return RHYTHMDB_PROPERTY_MODEL_COLUMN_LAST;
 933 }
 934 
 935 static GType
 936 rhythmdb_property_model_get_column_type (GtkTreeModel *tree_model,
 937 					 int index)
 938 {
 939 	switch (index) {
 940 	case RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE:
 941 		return G_TYPE_STRING;
 942 	case RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY:
 943 		return G_TYPE_BOOLEAN;
 944 	case RHYTHMDB_PROPERTY_MODEL_COLUMN_NUMBER:
 945 		return G_TYPE_UINT;
 946 	default:
 947 		g_assert_not_reached ();
 948 		return G_TYPE_INVALID;
 949 	}
 950 }
 951 
 952 static gboolean
 953 rhythmdb_property_model_get_iter (GtkTreeModel *tree_model,
 954 				  GtkTreeIter *iter,
 955 				  GtkTreePath *path)
 956 {
 957 	RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
 958 	guint index;
 959 	GSequenceIter *ptr;
 960 
 961 	index = gtk_tree_path_get_indices (path)[0];
 962 
 963 	if (index == 0) {
 964 		iter->stamp = model->priv->stamp;
 965 		iter->user_data = model->priv->all;
 966 		return TRUE;
 967 	}
 968 
 969 	index--;
 970 	if (index >= g_sequence_get_length (model->priv->properties))
 971 		return FALSE;
 972 
 973 	ptr = g_sequence_get_iter_at_pos (model->priv->properties, index);
 974 
 975 	iter->stamp = model->priv->stamp;
 976 	iter->user_data = ptr;
 977 
 978 	return TRUE;
 979 }
 980 
 981 static GtkTreePath *
 982 rhythmdb_property_model_get_path (GtkTreeModel *tree_model,
 983 				  GtkTreeIter  *iter)
 984 {
 985 	RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
 986 	GtkTreePath *path;
 987 
 988 	g_return_val_if_fail (iter->stamp == model->priv->stamp, NULL);
 989 
 990 	if (iter->user_data == model->priv->all) {
 991 		return gtk_tree_path_new_first ();
 992 	}
 993 
 994 	if (g_sequence_iter_is_end (iter->user_data))
 995 		return NULL;
 996 
 997 	path = gtk_tree_path_new ();
 998 	if (iter->user_data == model->priv->all)
 999 		gtk_tree_path_append_index (path, 0);
1000 	else
1001 		gtk_tree_path_append_index (path, g_sequence_iter_get_position (iter->user_data) + 1);
1002 	return path;
1003 }
1004 
1005 static void
1006 rhythmdb_property_model_get_value (GtkTreeModel *tree_model,
1007 				   GtkTreeIter *iter,
1008 				   gint column,
1009 				   GValue *value)
1010 {
1011 	RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
1012 
1013 	g_return_if_fail (model->priv->stamp == iter->stamp);
1014 
1015 	if (iter->user_data == model->priv->all) {
1016 		switch (column) {
1017 		case RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE:
1018 			g_value_init (value, G_TYPE_STRING);
1019 			g_value_set_string (value, rb_refstring_get (model->priv->all->string));
1020 			break;
1021 		case RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY:
1022 			g_value_init (value, G_TYPE_BOOLEAN);
1023 			g_value_set_boolean (value, TRUE);
1024 			break;
1025 		case RHYTHMDB_PROPERTY_MODEL_COLUMN_NUMBER:
1026 			g_value_init (value, G_TYPE_UINT);
1027 			g_value_set_uint (value, g_atomic_int_get (&model->priv->all->refcount));
1028 			break;
1029 		default:
1030 			g_assert_not_reached ();
1031 		}
1032 	} else {
1033 		RhythmDBPropertyModelEntry *prop;
1034 
1035 		g_return_if_fail (!g_sequence_iter_is_end (iter->user_data));
1036 
1037 		prop = g_sequence_get (iter->user_data);
1038 		switch (column) {
1039 		case RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE:
1040 			g_value_init (value, G_TYPE_STRING);
1041 			g_value_set_string (value, rb_refstring_get (prop->string));
1042 			break;
1043 		case RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY:
1044 			g_value_init (value, G_TYPE_BOOLEAN);
1045 			g_value_set_boolean (value, prop == model->priv->all);
1046 			break;
1047 		case RHYTHMDB_PROPERTY_MODEL_COLUMN_NUMBER:
1048 			g_value_init (value, G_TYPE_UINT);
1049 			g_value_set_uint (value, g_atomic_int_get (&prop->refcount));
1050 			break;
1051 		default:
1052 			g_assert_not_reached ();
1053 		}
1054 	}
1055 }
1056 
1057 static gboolean
1058 rhythmdb_property_model_iter_next (GtkTreeModel  *tree_model,
1059 				   GtkTreeIter   *iter)
1060 {
1061 	RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
1062 
1063 	g_return_val_if_fail (iter->stamp == model->priv->stamp, FALSE);
1064 
1065 	if (iter->user_data == model->priv->all) {
1066 		iter->user_data = g_sequence_get_begin_iter (model->priv->properties);
1067 	} else {
1068 		g_return_val_if_fail (!g_sequence_iter_is_end (iter->user_data), FALSE);
1069 		iter->user_data = g_sequence_iter_next (iter->user_data);
1070 	}
1071 
1072 	return !g_sequence_iter_is_end (iter->user_data);
1073 }
1074 
1075 static gboolean
1076 rhythmdb_property_model_iter_children (GtkTreeModel *tree_model,
1077 				       GtkTreeIter  *iter,
1078 				       GtkTreeIter  *parent)
1079 {
1080 	RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
1081 
1082 	if (parent != NULL)
1083 		return FALSE;
1084 
1085 	iter->stamp = model->priv->stamp;
1086 	iter->user_data = model->priv->all;
1087 
1088 	return TRUE;
1089 }
1090 
1091 static gboolean
1092 rhythmdb_property_model_iter_has_child (GtkTreeModel *tree_model,
1093 					GtkTreeIter *iter)
1094 {
1095 	return FALSE;
1096 }
1097 
1098 static gint
1099 rhythmdb_property_model_iter_n_children (GtkTreeModel *tree_model,
1100 					 GtkTreeIter *iter)
1101 {
1102 	RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
1103 
1104 	if (iter)
1105 		g_return_val_if_fail (model->priv->stamp == iter->stamp, -1);
1106 
1107 	if (iter == NULL)
1108 		return 1 + g_sequence_get_length (model->priv->properties);
1109 
1110 	return 0;
1111 }
1112 
1113 static gboolean
1114 rhythmdb_property_model_iter_nth_child (GtkTreeModel *tree_model,
1115 					GtkTreeIter *iter,
1116 					GtkTreeIter *parent,
1117 					gint n)
1118 {
1119 	RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
1120 	GSequenceIter *child;
1121 
1122 	if (parent)
1123 		return FALSE;
1124 
1125 	if (n != 0) {
1126 		/* -1 to account for the 'all' property at position 0 */
1127 		child = g_sequence_get_iter_at_pos (model->priv->properties, n-1);
1128 
1129 		if (g_sequence_iter_is_end (child))
1130 			return FALSE;
1131 		iter->user_data = child;
1132 	} else {
1133 		iter->user_data = model->priv->all;
1134 	}
1135 
1136 	iter->stamp = model->priv->stamp;
1137 
1138 	return TRUE;
1139 }
1140 
1141 static gboolean
1142 rhythmdb_property_model_iter_parent (GtkTreeModel *tree_model,
1143 				     GtkTreeIter  *iter,
1144 				     GtkTreeIter  *child)
1145 {
1146 	return FALSE;
1147 }
1148 
1149 static gboolean
1150 rhythmdb_property_model_row_draggable (RbTreeDragSource *dragsource,
1151 				       GList *paths)
1152 {
1153 	return TRUE;
1154 }
1155 
1156 static gboolean
1157 rhythmdb_property_model_drag_data_delete (RbTreeDragSource *dragsource,
1158 					  GList *paths)
1159 {
1160 	/* not supported */
1161 	return TRUE;
1162 }
1163 
1164 /*Going through hoops to avoid nested functions*/
1165 struct QueryModelCbStruct {
1166 	RhythmDB *db;
1167 	GString *reply;
1168 	gint target;
1169 };
1170 
1171 static gboolean
1172 query_model_cb (GtkTreeModel *query_model,
1173  		GtkTreePath *path,
1174 		GtkTreeIter *iter,
1175  		struct QueryModelCbStruct *data)
1176 {
1177  	RhythmDBEntry *entry;
1178 
1179  	gtk_tree_model_get (query_model, iter, 0, &entry, -1);
1180 	if (data->target == TARGET_ENTRIES) {
1181 		g_string_append_printf (data->reply,
1182 					"%ld",
1183 					rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_ENTRY_ID));
1184 	} else if (data->target == TARGET_URIS) {
1185 		const char *uri;
1186 		uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
1187 		g_string_append (data->reply, uri);
1188 	}
1189  	g_string_append (data->reply, "\r\n");
1190 
1191  	rhythmdb_entry_unref (entry);
1192  	return FALSE;
1193 }
1194 
1195 static gboolean
1196 rhythmdb_property_model_drag_data_get (RbTreeDragSource *dragsource,
1197 				       GList *paths,
1198 				       GtkSelectionData *selection_data)
1199 {
1200 	RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (dragsource);
1201 	guint target;
1202 	GtkTargetList *drag_target_list;
1203 	GdkAtom selection_data_target;
1204 
1205 	switch (model->priv->propid) {
1206 	case RHYTHMDB_PROP_GENRE:
1207 		drag_target_list = rhythmdb_property_model_genre_drag_target_list;
1208 		break;
1209 	case RHYTHMDB_PROP_ALBUM:
1210 		drag_target_list = rhythmdb_property_model_album_drag_target_list;
1211 		break;
1212 	case RHYTHMDB_PROP_ARTIST:
1213 		drag_target_list = rhythmdb_property_model_artist_drag_target_list;
1214 		break;
1215 	case RHYTHMDB_PROP_LOCATION:
1216 		drag_target_list = rhythmdb_property_model_location_drag_target_list;
1217 		break;
1218 	default:
1219 		g_assert_not_reached ();
1220 	}
1221 
1222 	selection_data_target = gtk_selection_data_get_target (selection_data);
1223 	if (!gtk_target_list_find (drag_target_list,
1224 				   selection_data_target,
1225 				   &target)) {
1226 		return FALSE;
1227 	}
1228 
1229 	if (target == TARGET_URIS || target == TARGET_ENTRIES) {
1230 		RhythmDB *db = model->priv->db;
1231  		RhythmDBQueryModel *query_model;
1232  		GString* reply = g_string_new ("");
1233  		GtkTreeIter iter;
1234  		gboolean is_all = FALSE;
1235  		struct QueryModelCbStruct tmp;
1236 		GtkTreePath *path;
1237 		GCompareDataFunc sort_func = NULL;
1238 		gpointer sort_data;
1239 		gboolean sort_reverse;
1240 
1241 		query_model = rhythmdb_query_model_new_empty (db);
1242 		/* FIXME the sort order on the query model at this point is usually
1243 		 * not the user's selected sort order.
1244 		 */
1245 		g_object_get (G_OBJECT (model->priv->query_model),
1246 			      "sort-func", &sort_func,
1247 			      "sort-data", &sort_data,
1248 			      "sort-reverse", &sort_reverse,
1249 			      NULL);
1250 		rhythmdb_query_model_set_sort_order (RHYTHMDB_QUERY_MODEL (query_model),
1251 						     sort_func, GUINT_TO_POINTER (sort_data), NULL, sort_reverse);
1252 
1253 		rb_debug ("getting drag data as uri list");
1254 		/* check if first selected row is 'All' */
1255 		path = gtk_tree_row_reference_get_path (paths->data);
1256 		if (path && gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path))
1257 			gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
1258 					    RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY,
1259 					    &is_all, -1);
1260 		gtk_tree_path_free (path);
1261 		if (is_all) {
1262 			g_object_set (query_model,
1263 				      "base-model", model->priv->query_model,
1264 				      NULL);
1265 		} else {
1266  			GList *row;
1267 			GPtrArray *subquery = g_ptr_array_new ();
1268 
1269  			for (row = paths; row; row = row->next) {
1270  				char* name;
1271 				path = gtk_tree_row_reference_get_path (row->data);
1272  				if (path && gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path)) {
1273 	 				gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
1274 	 						    RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE,
1275 							    &name, -1);
1276 	 				if (row == paths) {
1277 	 					rhythmdb_query_append (db, subquery,
1278 	 							       RHYTHMDB_QUERY_PROP_EQUALS,
1279 	 							       model->priv->propid, name,
1280 								       RHYTHMDB_QUERY_END);
1281 					} else {
1282 	 					rhythmdb_query_append (db, subquery,
1283 	 							       RHYTHMDB_QUERY_DISJUNCTION,
1284 	 							       RHYTHMDB_QUERY_PROP_EQUALS,
1285 	 							       model->priv->propid, name,
1286 	 							       RHYTHMDB_QUERY_END);
1287 	 				}
1288 				}
1289 
1290 				gtk_tree_path_free (path);
1291  				g_free (name);
1292  			}
1293 
1294 			g_object_set (query_model,
1295 				      "query", subquery,
1296 				      "base-model", model->priv->query_model,
1297 				      NULL);
1298 			rhythmdb_query_free (subquery);
1299 		}
1300 
1301 		tmp.db = db;
1302  		tmp.reply = reply;
1303 		tmp.target = target;
1304  		/* Too bad that we're on the main thread. Why doesn't gtk call us async?
1305  		 * How does file-roller manage? - it seems it refuses the drop when it isn't
1306 		 * done unpacking. In which case, we should tweak the drop acknowledgement,
1307 		 * and prepare the query using do_full_query_async. The query would be
1308 		 * hooked to the drag context.
1309 		 */
1310  		gtk_tree_model_foreach (GTK_TREE_MODEL (query_model),
1311  					(GtkTreeModelForeachFunc) query_model_cb,
1312  					&tmp);
1313 
1314 		g_object_unref (query_model);
1315 
1316  		gtk_selection_data_set (selection_data,
1317 					selection_data_target,
1318  		                        8, (guchar *)reply->str,
1319  		                        reply->len);
1320  		g_string_free (reply, TRUE);
1321 
1322 	} else {
1323 		char* title;
1324 		GList *p;
1325 		GString* reply = g_string_new ("");
1326 
1327 		rb_debug ("getting drag data as list of property values");
1328 
1329 		for (p = paths; p; p = p->next) {
1330 			GtkTreeIter iter;
1331 			GtkTreePath *path;
1332 
1333 			path = gtk_tree_row_reference_get_path (p->data);
1334 			if (path && gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path)) {
1335 				gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
1336 						    RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE, &title, -1);
1337 				g_string_append (reply, title);
1338 				if (p->next)
1339 					g_string_append (reply, "\r\n");
1340 				g_free (title);
1341 			}
1342 			gtk_tree_path_free (path);
1343 		}
1344 		gtk_selection_data_set (selection_data,
1345 					selection_data_target,
1346 					8, (guchar *)reply->str,
1347 					reply->len);
1348 		g_string_free (reply, TRUE);
1349 	}
1350 
1351 	return TRUE;
1352 }
1353 
1354 /**
1355  * rhythmdb_property_model_enable_drag:
1356  * @model: the #RhythmDBPropertyModel.
1357  * @view: the #GtkTreeView from which to enable drag and drop
1358  *
1359  * Enables drag and drop from a specified #GtkTreeView that is
1360  * backed by the #RhythmDBPropertyModel.  Drag targets are
1361  * determined by the indexed property.
1362  */
1363 void
1364 rhythmdb_property_model_enable_drag (RhythmDBPropertyModel *model,
1365 				     GtkTreeView *view)
1366 {
1367 	const GtkTargetEntry *targets;
1368 	gint n_elements;
1369 
1370 	switch (model->priv->propid) {
1371 	case RHYTHMDB_PROP_GENRE:
1372 		targets = targets_genre;
1373 		n_elements = G_N_ELEMENTS (targets_genre);
1374 		break;
1375 	case RHYTHMDB_PROP_ALBUM:
1376 		targets = targets_album;
1377 		n_elements = G_N_ELEMENTS (targets_album);
1378 		break;
1379 	case RHYTHMDB_PROP_ARTIST:
1380 		targets = targets_artist;
1381 		n_elements = G_N_ELEMENTS (targets_artist);
1382 		break;
1383 	case RHYTHMDB_PROP_LOCATION:
1384 	case RHYTHMDB_PROP_SUBTITLE:		/* more or less */
1385 		targets = targets_location;
1386 		n_elements = G_N_ELEMENTS (targets_location);
1387 		break;
1388 	default:
1389 		g_assert_not_reached ();
1390 	}
1391 
1392 	rb_tree_dnd_add_drag_source_support (view,
1393 					     GDK_BUTTON1_MASK,
1394 					     targets, n_elements,
1395 					     GDK_ACTION_COPY);
1396 }
1397 
1398 static gboolean
1399 rhythmdb_property_model_perform_sync (RhythmDBPropertyModel *model)
1400 {
1401 	GtkTreeIter iter;
1402 	GtkTreePath *path;
1403 
1404 	GDK_THREADS_ENTER ();
1405 
1406 	iter.stamp = model->priv->stamp;
1407 	iter.user_data = model->priv->all;
1408 	path = rhythmdb_property_model_get_path (GTK_TREE_MODEL (model), &iter);
1409 	gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
1410 	gtk_tree_path_free (path);
1411 
1412 	model->priv->syncing_id = 0;
1413 	GDK_THREADS_LEAVE ();
1414 	return FALSE;
1415 }
1416 
1417 static void
1418 rhythmdb_property_model_sync (RhythmDBPropertyModel *model)
1419 {
1420 	if (model->priv->syncing_id != 0)
1421 		return;
1422 
1423 	model->priv->syncing_id = g_idle_add ((GSourceFunc)rhythmdb_property_model_perform_sync, model);
1424 }
1425 
1426 /* This should really be standard. */
1427 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
1428 
1429 GType
1430 rhythmdb_property_model_column_get_type (void)
1431 {
1432 	static GType etype = 0;
1433 
1434 	if (etype == 0)	{
1435 		static const GEnumValue values[] = {
1436 			ENUM_ENTRY (RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE, "property-title"),
1437 			ENUM_ENTRY (RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY, "value-priority"),
1438 			ENUM_ENTRY (RHYTHMDB_PROPERTY_MODEL_COLUMN_NUMBER, "track-count"),
1439 			{ 0, 0, 0 }
1440 		};
1441 
1442 		etype = g_enum_register_static ("RhythmDBPropertyModelColumn", values);
1443 	}
1444 
1445 	return etype;
1446 }