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 <stdio.h>
35 #include <math.h>
36 #include <glib.h>
37 
38 #include <gtk/gtk.h>
39 
40 #include "rhythmdb-query-model.h"
41 #include "rb-debug.h"
42 #include "rb-tree-dnd.h"
43 #include "rb-util.h"
44 
45 struct ReverseSortData
46 {
47 	GCompareDataFunc	func;
48 	gpointer		data;
49 };
50 
51 static void rhythmdb_query_model_query_results_init (RhythmDBQueryResultsIface *iface);
52 static void rhythmdb_query_model_tree_model_init (GtkTreeModelIface *iface);
53 static void rhythmdb_query_model_drag_source_init (RbTreeDragSourceIface *iface);
54 static void rhythmdb_query_model_drag_dest_init (RbTreeDragDestIface *iface);
55 
56 G_DEFINE_TYPE_WITH_CODE(RhythmDBQueryModel, rhythmdb_query_model, G_TYPE_OBJECT,
57 			G_IMPLEMENT_INTERFACE(RHYTHMDB_TYPE_QUERY_RESULTS,
58 					      rhythmdb_query_model_query_results_init)
59 			G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL,
60 					      rhythmdb_query_model_tree_model_init)
61 			G_IMPLEMENT_INTERFACE(RB_TYPE_TREE_DRAG_SOURCE,
62 					      rhythmdb_query_model_drag_source_init)
63 			G_IMPLEMENT_INTERFACE(RB_TYPE_TREE_DRAG_DEST,
64 					      rhythmdb_query_model_drag_dest_init))
65 
66 static void rhythmdb_query_model_init (RhythmDBQueryModel *shell_player);
67 static void rhythmdb_query_model_constructed (GObject *object);
68 static void rhythmdb_query_model_dispose (GObject *object);
69 static void rhythmdb_query_model_finalize (GObject *object);
70 static void rhythmdb_query_model_set_property (GObject *object,
71 					       guint prop_id,
72 					       const GValue *value,
73 					       GParamSpec *pspec);
74 static void rhythmdb_query_model_get_property (GObject *object,
75 					       guint prop_id,
76 					       GValue *value,
77 					       GParamSpec *pspec);
78 static void rhythmdb_query_model_do_insert (RhythmDBQueryModel *model,
79 					    RhythmDBEntry *entry,
80 					    gint index);
81 static void rhythmdb_query_model_entry_added_cb (RhythmDB *db, RhythmDBEntry *entry,
82 						 RhythmDBQueryModel *model);
83 static void rhythmdb_query_model_entry_changed_cb (RhythmDB *db, RhythmDBEntry *entry,
84 						   GPtrArray *changes, RhythmDBQueryModel *model);
85 static void rhythmdb_query_model_entry_deleted_cb (RhythmDB *db, RhythmDBEntry *entry,
86 						   RhythmDBQueryModel *model);
87 
88 static void rhythmdb_query_model_filter_out_entry (RhythmDBQueryModel *model,
89 						   RhythmDBEntry *entry);
90 static gboolean rhythmdb_query_model_do_reorder (RhythmDBQueryModel *model, RhythmDBEntry *entry);
91 static gboolean rhythmdb_query_model_emit_reorder (RhythmDBQueryModel *model, gint old_pos, gint new_pos);
92 static gboolean rhythmdb_query_model_drag_data_get (RbTreeDragSource *dragsource,
93 							  GList *paths,
94 							  GtkSelectionData *selection_data);
95 static gboolean rhythmdb_query_model_drag_data_delete (RbTreeDragSource *dragsource,
96 							     GList *paths);
97 static gboolean rhythmdb_query_model_row_draggable (RbTreeDragSource *dragsource,
98 							  GList *paths);
99 static gboolean rhythmdb_query_model_drag_data_received (RbTreeDragDest *drag_dest,
100 							 GtkTreePath *dest,
101 							 GtkTreeViewDropPosition pos,
102 							 GtkSelectionData  *selection_data);
103 static gboolean rhythmdb_query_model_row_drop_possible (RbTreeDragDest *drag_dest,
104 							GtkTreePath *dest,
105 							GtkTreeViewDropPosition pos,
106 							GtkSelectionData  *selection_data);
107 static gboolean rhythmdb_query_model_row_drop_position (RbTreeDragDest   *drag_dest,
108 							GtkTreePath       *dest_path,
109 							GList *targets,
110 							GtkTreeViewDropPosition *pos);
111 
112 static void rhythmdb_query_model_set_query (RhythmDBQueryResults *results, GPtrArray *query);
113 static void rhythmdb_query_model_add_results (RhythmDBQueryResults *results, GPtrArray *entries);
114 static void rhythmdb_query_model_query_complete (RhythmDBQueryResults *results);
115 
116 static GtkTreeModelFlags rhythmdb_query_model_get_flags (GtkTreeModel *model);
117 static gint rhythmdb_query_model_get_n_columns (GtkTreeModel *tree_model);
118 static GType rhythmdb_query_model_get_column_type (GtkTreeModel *tree_model, int index);
119 static gboolean rhythmdb_query_model_get_iter (GtkTreeModel *tree_model, GtkTreeIter *iter,
120 					       GtkTreePath  *path);
121 static GtkTreePath * rhythmdb_query_model_get_path (GtkTreeModel *tree_model,
122 						    GtkTreeIter  *iter);
123 static void rhythmdb_query_model_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter,
124 					    gint column, GValue *value);
125 static gboolean rhythmdb_query_model_iter_next (GtkTreeModel  *tree_model,
126 						GtkTreeIter   *iter);
127 static gboolean rhythmdb_query_model_iter_children (GtkTreeModel *tree_model,
128 						    GtkTreeIter  *iter,
129 						    GtkTreeIter  *parent);
130 static gboolean rhythmdb_query_model_iter_has_child (GtkTreeModel *tree_model,
131 						     GtkTreeIter  *iter);
132 static gint rhythmdb_query_model_iter_n_children (GtkTreeModel *tree_model,
133 						  GtkTreeIter  *iter);
134 static gboolean rhythmdb_query_model_iter_nth_child (GtkTreeModel *tree_model,
135 						     GtkTreeIter *iter, GtkTreeIter *parent,
136 						     gint n);
137 static gboolean rhythmdb_query_model_iter_parent (GtkTreeModel *tree_model,
138 						  GtkTreeIter  *iter,
139 						  GtkTreeIter  *child);
140 
141 static void rhythmdb_query_model_base_row_inserted (GtkTreeModel *base_model,
142 						    GtkTreePath *path,
143 						    GtkTreeIter *iter,
144 						    RhythmDBQueryModel *model);
145 static void rhythmdb_query_model_base_row_deleted (GtkTreeModel *base_model,
146 						   GtkTreePath *path,
147 						   RhythmDBQueryModel *model);
148 static void rhythmdb_query_model_base_non_entry_dropped (GtkTreeModel *base_model,
149 							 const char *location,
150 							 int position,
151 							 RhythmDBQueryModel *model);
152 static void rhythmdb_query_model_base_complete (GtkTreeModel *base_model,
153 						RhythmDBQueryModel *model);
154 static void rhythmdb_query_model_base_rows_reordered (GtkTreeModel *base_model,
155 						      GtkTreePath *arg1,
156 						      GtkTreeIter *arg2,
157 						      gint *order_map,
158 						      RhythmDBQueryModel *model);
159 static void rhythmdb_query_model_base_entry_removed (RhythmDBQueryModel *base_model,
160 						     RhythmDBEntry *entry,
161 						     RhythmDBQueryModel *model);
162 static void rhythmdb_query_model_base_entry_prop_changed (RhythmDBQueryModel *base_model,
163 							  RhythmDBEntry *entry,
164 							  RhythmDBPropType prop,
165 							  const GValue *old,
166 							  const GValue *new_value,
167 							  RhythmDBQueryModel *model);
168 static int rhythmdb_query_model_child_index_to_base_index (RhythmDBQueryModel *model, int index);
169 
170 static gint _reverse_sorting_func (gpointer a, gpointer b, struct ReverseSortData *model);
171 static gboolean rhythmdb_query_model_within_limit (RhythmDBQueryModel *model,
172 						   RhythmDBEntry *entry);
173 static gboolean rhythmdb_query_model_reapply_query_cb (RhythmDBQueryModel *model);
174 
175 struct RhythmDBQueryModelUpdate
176 {
177 	RhythmDBQueryModel *model;
178 	enum {
179 		RHYTHMDB_QUERY_MODEL_UPDATE_ROWS_INSERTED,
180 		RHYTHMDB_QUERY_MODEL_UPDATE_ROW_INSERTED_INDEX,
181 		RHYTHMDB_QUERY_MODEL_UPDATE_QUERY_COMPLETE,
182 	} type;
183 
184 	union {
185 		struct {
186 			RhythmDBEntry *entry;
187 			gint index;
188 		} data;
189 		GPtrArray *entries;
190 	} entrydata;
191 };
192 
193 static void rhythmdb_query_model_process_update (struct RhythmDBQueryModelUpdate *update);
194 
195 static void idle_process_update (struct RhythmDBQueryModelUpdate *update);
196 
197 enum {
198 	TARGET_ENTRIES,
199 	TARGET_URIS
200 };
201 
202 static const GtkTargetEntry rhythmdb_query_model_drag_types[] = {
203 	{ "application/x-rhythmbox-entry", 0, TARGET_ENTRIES },
204 	{ "text/uri-list", 0, TARGET_URIS },
205 };
206 
207 static GtkTargetList *rhythmdb_query_model_drag_target_list = NULL;
208 
209 struct _RhythmDBQueryModelPrivate
210 {
211 	RhythmDB *db;
212 
213 	RhythmDBQueryModel *base_model;
214 
215 	GCompareDataFunc sort_func;
216 	gpointer sort_data;
217 	GDestroyNotify sort_data_destroy;
218 	gboolean sort_reverse;
219 
220 	GPtrArray *query;
221 	GPtrArray *original_query;
222 
223 	guint stamp;
224 
225 	RhythmDBQueryModelLimitType limit_type;
226 	GVariant *limit_value;
227 
228 	glong total_duration;
229 	guint64 total_size;
230 
231 	GSequence *entries;
232 	GHashTable *reverse_map;
233 	GSequence *limited_entries;
234 	GHashTable *limited_reverse_map;
235 	GHashTable *hidden_entry_map;
236 
237 	gint pending_update_count;
238 
239 	gboolean reorder_drag_and_drop;
240 	gboolean show_hidden;
241 
242 	gint query_reapply_timeout_id;
243 };
244 
245 #define RHYTHMDB_QUERY_MODEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE_QUERY_MODEL, RhythmDBQueryModelPrivate))
246 
247 enum
248 {
249 	PROP_0,
250 	PROP_RHYTHMDB,
251 	PROP_QUERY,
252 	PROP_SORT_FUNC,
253 	PROP_SORT_DATA,
254 	PROP_SORT_DATA_DESTROY,
255 	PROP_SORT_REVERSE,
256 	PROP_LIMIT_TYPE,
257 	PROP_LIMIT_VALUE,
258 	PROP_SHOW_HIDDEN,
259 	PROP_BASE_MODEL,
260 };
261 
262 enum
263 {
264 	COMPLETE,
265 	ENTRY_PROP_CHANGED,
266 	ENTRY_REMOVED,
267 	NON_ENTRY_DROPPED,
268 	POST_ENTRY_DELETE,
269 	FILTER_ENTRY_DROP,
270 	LAST_SIGNAL
271 };
272 
273 static guint rhythmdb_query_model_signals[LAST_SIGNAL] = { 0 };
274 
275 /**
276  * SECTION:rhythmdb-query-model
277  * @short_description: a #GtkTreeModel containing #RhythmDBEntry items
278  *
279  * A RhythmDBQueryModel contains an ordered set of #RhythmDBEntry items,
280  * either generated by running a query against the database, or populated
281  * by adding individual entries.
282  *
283  * All sources use a #RhythmDBQueryModel to provide their track listing.
284  * Since most sources provide a search or filtering capacity, there is
285  * usually a distinction between the source's base query model, which contains
286  * all entries for the source, and its current query model, which reflects
287  * the current search terms and filter selections.
288  *
289  * A RhythmDBQueryModel can be populated directly from the #RhythmDB, or it
290  * can be chained to another query model.  Chained query models inherit the
291  * set of entries from the query model they chain to and then restrict the
292  * set using a query.
293  *
294  * The query model consists of a #GSequence, which provides ordering of entries,
295  * and a #GHashTable, which allows efficient checks to see if a given entry
296  * is in the model.  A side effect of this is that an entry can only be placed
297  * into a query model in one location.
298  *
299  * In addition to the query, a #RhythmDBQueryModel can have a sort order and
300  * a limit, specified in terms of file size, duration, or number of entries.
301  * A query model can only have a limit if it also has a sort order, as the
302  * sort order is required to determine which entries fall inside the limit.
303  * When a limit is applied, entries that match the query but fall outside the
304  * limit are maintained in a separate #GSequence and #GHashTable inside the
305  * query model.
306  */
307 
308 static void
rhythmdb_query_model_class_init(RhythmDBQueryModelClass * klass)309 rhythmdb_query_model_class_init (RhythmDBQueryModelClass *klass)
310 {
311 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
312 
313 	object_class->set_property = rhythmdb_query_model_set_property;
314 	object_class->get_property = rhythmdb_query_model_get_property;
315 
316 	object_class->dispose = rhythmdb_query_model_dispose;
317 	object_class->finalize = rhythmdb_query_model_finalize;
318 	object_class->constructed = rhythmdb_query_model_constructed;
319 
320 	g_object_class_install_property (object_class,
321 					 PROP_RHYTHMDB,
322 					 g_param_spec_object ("db",
323 							      "RhythmDB",
324 							      "RhythmDB object",
325 							      RHYTHMDB_TYPE,
326 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
327 	g_object_class_install_property (object_class,
328 					 PROP_QUERY,
329 					 g_param_spec_pointer ("query",
330 							      "Query",
331 							      "RhythmDBQuery",
332 							      G_PARAM_READWRITE));
333 	g_object_class_install_property (object_class,
334 					 PROP_SORT_FUNC,
335 					 g_param_spec_pointer ("sort-func",
336 							      "SortFunc",
337 							      "Sort function",
338 							       G_PARAM_READWRITE));
339 	g_object_class_install_property (object_class,
340 					 PROP_SORT_DATA,
341 					 g_param_spec_pointer ("sort-data",
342 							       "Sort data",
343 							       "Sort data",
344 							       G_PARAM_READWRITE));
345 	g_object_class_install_property (object_class,
346 					 PROP_SORT_DATA_DESTROY,
347 					 g_param_spec_pointer ("sort-data-destroy",
348 							       "Sort data destroy",
349 							       "Sort data destroy function",
350 							       G_PARAM_READWRITE));
351 	g_object_class_install_property (object_class,
352 					 PROP_SORT_REVERSE,
353 					 g_param_spec_boolean ("sort-reverse",
354 							      "sort-reverse",
355 							      "Reverse sort order flag",
356 							      FALSE,
357 							      G_PARAM_READWRITE));
358 	g_object_class_install_property (object_class,
359 					 PROP_LIMIT_TYPE,
360 					 g_param_spec_enum ("limit-type",
361 							    "limit-type",
362 							    "type of limit",
363 							    RHYTHMDB_TYPE_QUERY_MODEL_LIMIT_TYPE,
364 							    RHYTHMDB_QUERY_MODEL_LIMIT_NONE,
365 							    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
366 	g_object_class_install_property (object_class,
367 					 PROP_LIMIT_VALUE,
368 					 g_param_spec_variant ("limit-value",
369 							       "limit-value",
370 							       "value of limit",
371 							       G_VARIANT_TYPE_UINT64,
372 							       NULL,
373 							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
374 	g_object_class_install_property (object_class,
375 					 PROP_SHOW_HIDDEN,
376 					 g_param_spec_boolean ("show-hidden",
377 							       "show hidden",
378 							       "if TRUE, include entries that are ordinarily hidden",
379 							       FALSE,
380 							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
381 	g_object_class_install_property (object_class,
382 					 PROP_BASE_MODEL,
383 					 g_param_spec_object ("base-model",
384 						 	      "base-model",
385 							      "base RhythmDBQueryModel",
386 							      RHYTHMDB_TYPE_QUERY_MODEL,
387 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
388 
389 	/**
390 	 * RhythmDBQueryModel::entry-prop-changed:
391 	 * @model: the #RhythmDBQueryModel
392 	 * @entry: the #RhythmDBEntry that changed
393 	 * @prop: the #RhythmDBPropType that was changed
394 	 * @old: the previous value for the property
395 	 * @new_value: the new value for the property
396 	 *
397 	 * Emitted when an entry in the query model is changed.  When multiple
398 	 * properties are changed, the entry-prop-changed signals will be emitted
399 	 * in the order that the changes were made.  At the point that the
400 	 * signal is emitted, all changes have already been applied to the
401 	 * #RhythmDBEntry.
402 	 */
403 	rhythmdb_query_model_signals[ENTRY_PROP_CHANGED] =
404 		g_signal_new ("entry-prop-changed",
405 			      RHYTHMDB_TYPE_QUERY_MODEL,
406 			      G_SIGNAL_RUN_LAST,
407 			      G_STRUCT_OFFSET (RhythmDBQueryModelClass, entry_prop_changed),
408 			      NULL, NULL,
409 			      NULL,
410 			      G_TYPE_NONE,
411 			      4, RHYTHMDB_TYPE_ENTRY, G_TYPE_INT, G_TYPE_POINTER, G_TYPE_POINTER);
412 	/**
413 	 * RhythmDBQueryModel::entry-removed:
414 	 * @model: the #RhythmDBQueryModel
415 	 * @entry: the #RhythmDBEntry that was removed
416 	 *
417 	 * Emitted when an entry is removed from the model.  There is some
418 	 * difference between this and the #GtkTreeModel row-removed signal
419 	 * but I don't know what it is right now.
420 	 */
421 	rhythmdb_query_model_signals[ENTRY_REMOVED] =
422 		g_signal_new ("entry-removed",
423 			      RHYTHMDB_TYPE_QUERY_MODEL,
424 			      G_SIGNAL_RUN_LAST,
425 			      G_STRUCT_OFFSET (RhythmDBQueryModelClass, entry_removed),
426 			      NULL, NULL,
427 			      NULL,
428 			      G_TYPE_NONE,
429 			      1, RHYTHMDB_TYPE_ENTRY);
430 	/**
431 	 * RhythmDBQueryModel::non-entry-dropped:
432 	 * @model: the #RhythmDBQueryModel
433 	 * @uri: the URI that was dropped
434 	 * @position: the position in the query model at which it was dropped
435 	 *
436 	 * Emitted when a URI that does not match an existing #RhythmDBEntry
437 	 * is dropped into the query model.
438 	 */
439 	rhythmdb_query_model_signals[NON_ENTRY_DROPPED] =
440 		g_signal_new ("non-entry-dropped",
441 			      RHYTHMDB_TYPE_QUERY_MODEL,
442 			      G_SIGNAL_RUN_LAST,
443 			      G_STRUCT_OFFSET (RhythmDBQueryModelClass, non_entry_dropped),
444 			      NULL, NULL,
445 			      NULL,
446 			      G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_INT);
447 	/**
448 	 * RhythmDBQueryModel::complete:
449 	 * @model: the #RhythmDBQueryModel
450 	 *
451 	 * Emitted when the database query populating the model is complete.
452 	 */
453 	rhythmdb_query_model_signals[COMPLETE] =
454 		g_signal_new ("complete",
455 			      RHYTHMDB_TYPE_QUERY_MODEL,
456 			      G_SIGNAL_RUN_LAST,
457 			      G_STRUCT_OFFSET (RhythmDBQueryModelClass, complete),
458 			      NULL, NULL,
459 			      NULL,
460 			      G_TYPE_NONE, 0);
461 	/**
462 	 * RhythmDBQueryModel::post-entry-delete:
463 	 * @model: the #RhythmDBQueryModel
464 	 * @entry: the #RhythmDBEntry that was removed
465 	 *
466 	 * Emitted after an entry has been removed from the model.
467 	 */
468 	rhythmdb_query_model_signals[POST_ENTRY_DELETE] =
469 		g_signal_new ("post-entry-delete",
470 			      RHYTHMDB_TYPE_QUERY_MODEL,
471 			      G_SIGNAL_RUN_LAST,
472 			      G_STRUCT_OFFSET (RhythmDBQueryModelClass, post_entry_delete),
473 			      NULL, NULL,
474 			      NULL,
475 			      G_TYPE_NONE,
476 			      1, RHYTHMDB_TYPE_ENTRY);
477 	/**
478 	 * RhythmDBQueryModel::filter-entry-drop:
479 	 * @model: the #RhythmDBQueryModel
480 	 * @entry: the #RhythmDBEntry being dropped
481 	 *
482 	 * Emitted when an entry is being added to a model by drag-and-drop.
483 	 * This allows the owner of the model to filter out entries that should
484 	 * not be added to the model (based on entry type, for instance).
485 	 * If the signal handler returns %FALSE, the entry will not be added.
486 	 */
487 	rhythmdb_query_model_signals[FILTER_ENTRY_DROP] =
488 		g_signal_new ("filter-entry-drop",
489 			      RHYTHMDB_TYPE_QUERY_MODEL,
490 			      G_SIGNAL_RUN_LAST,
491 			      G_STRUCT_OFFSET (RhythmDBQueryModelClass, filter_entry_drop),
492 			      NULL, NULL,
493 			      NULL,
494 			      G_TYPE_BOOLEAN,
495 			      1, RHYTHMDB_TYPE_ENTRY);
496 
497 	g_type_class_add_private (klass, sizeof (RhythmDBQueryModelPrivate));
498 }
499 
500 static void
rhythmdb_query_model_query_results_init(RhythmDBQueryResultsIface * iface)501 rhythmdb_query_model_query_results_init (RhythmDBQueryResultsIface *iface)
502 {
503 	iface->set_query = rhythmdb_query_model_set_query;
504 	iface->add_results = rhythmdb_query_model_add_results;
505 	iface->query_complete = rhythmdb_query_model_query_complete;
506 }
507 
508 static void
rhythmdb_query_model_tree_model_init(GtkTreeModelIface * iface)509 rhythmdb_query_model_tree_model_init (GtkTreeModelIface *iface)
510 {
511 	iface->get_flags = rhythmdb_query_model_get_flags;
512 	iface->get_n_columns = rhythmdb_query_model_get_n_columns;
513 	iface->get_column_type = rhythmdb_query_model_get_column_type;
514 	iface->get_iter = rhythmdb_query_model_get_iter;
515 	iface->get_path = rhythmdb_query_model_get_path;
516 	iface->get_value = rhythmdb_query_model_get_value;
517 	iface->iter_next = rhythmdb_query_model_iter_next;
518 	iface->iter_children = rhythmdb_query_model_iter_children;
519 	iface->iter_has_child = rhythmdb_query_model_iter_has_child;
520 	iface->iter_n_children = rhythmdb_query_model_iter_n_children;
521 	iface->iter_nth_child = rhythmdb_query_model_iter_nth_child;
522 	iface->iter_parent = rhythmdb_query_model_iter_parent;
523 }
524 
525 static void
rhythmdb_query_model_drag_source_init(RbTreeDragSourceIface * iface)526 rhythmdb_query_model_drag_source_init (RbTreeDragSourceIface *iface)
527 {
528 	iface->rb_row_draggable = rhythmdb_query_model_row_draggable;
529 	iface->rb_drag_data_delete = rhythmdb_query_model_drag_data_delete;
530 	iface->rb_drag_data_get = rhythmdb_query_model_drag_data_get;
531 }
532 
533 static void
rhythmdb_query_model_drag_dest_init(RbTreeDragDestIface * iface)534 rhythmdb_query_model_drag_dest_init (RbTreeDragDestIface *iface)
535 {
536 	iface->rb_drag_data_received = rhythmdb_query_model_drag_data_received;
537 	iface->rb_row_drop_possible = rhythmdb_query_model_row_drop_possible;
538 	iface->rb_row_drop_position = rhythmdb_query_model_row_drop_position;
539 }
540 
541 static void
rhythmdb_query_model_set_query_internal(RhythmDBQueryModel * model,GPtrArray * query)542 rhythmdb_query_model_set_query_internal (RhythmDBQueryModel *model,
543 					GPtrArray          *query)
544 {
545 	if (query == model->priv->original_query)
546 		return;
547 
548 	rhythmdb_query_free (model->priv->query);
549 	rhythmdb_query_free (model->priv->original_query);
550 
551 	model->priv->query = rhythmdb_query_copy (query);
552 	model->priv->original_query = rhythmdb_query_copy (model->priv->query);
553 	rhythmdb_query_preprocess (model->priv->db, model->priv->query);
554 
555 	/* if the query contains time-relative criteria, re-run it periodically.
556 	 * currently it's just every minute, but perhaps it could be smarter.
557 	 */
558 	if (rhythmdb_query_is_time_relative (model->priv->db, model->priv->query)) {
559 		if (model->priv->query_reapply_timeout_id == 0) {
560 			model->priv->query_reapply_timeout_id =
561 				g_timeout_add_seconds (60, (GSourceFunc) rhythmdb_query_model_reapply_query_cb, model);
562 		}
563 	} else if (model->priv->query_reapply_timeout_id) {
564 		g_source_remove (model->priv->query_reapply_timeout_id);
565 		model->priv->query_reapply_timeout_id = 0;
566 	}
567 }
568 
569 static void
rhythmdb_query_model_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)570 rhythmdb_query_model_set_property (GObject *object,
571 				   guint prop_id,
572 				   const GValue *value,
573 				   GParamSpec *pspec)
574 {
575 	RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (object);
576 
577 	switch (prop_id) {
578 	case PROP_RHYTHMDB:
579 		model->priv->db = g_value_get_object (value);
580 		break;
581 	case PROP_QUERY:
582 		rhythmdb_query_model_set_query_internal (model, g_value_get_pointer (value));
583 		break;
584 	case PROP_SORT_FUNC:
585 		model->priv->sort_func = g_value_get_pointer (value);
586 		break;
587 	case PROP_SORT_DATA:
588 		if (model->priv->sort_data_destroy && model->priv->sort_data)
589 			model->priv->sort_data_destroy (model->priv->sort_data);
590 		model->priv->sort_data = g_value_get_pointer (value);
591 		break;
592 	case PROP_SORT_DATA_DESTROY:
593 		model->priv->sort_data_destroy = g_value_get_pointer (value);
594 		break;
595 	case PROP_SORT_REVERSE:
596 		model->priv->sort_reverse  = g_value_get_boolean (value);
597 		break;
598 	case PROP_LIMIT_TYPE:
599 		model->priv->limit_type = g_value_get_enum (value);
600 		break;
601 	case PROP_LIMIT_VALUE:
602 		if (model->priv->limit_value)
603 			g_variant_unref (model->priv->limit_value);
604 		model->priv->limit_value = g_value_dup_variant (value);
605 		break;
606 	case PROP_SHOW_HIDDEN:
607 		model->priv->show_hidden = g_value_get_boolean (value);
608 		/* FIXME: this will have funky issues if this is changed after entries are added */
609 		break;
610 	case PROP_BASE_MODEL:
611 		rhythmdb_query_model_chain (model, g_value_get_object (value), TRUE);
612 		break;
613 	default:
614 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
615 		break;
616 	}
617 }
618 
619 static void
rhythmdb_query_model_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)620 rhythmdb_query_model_get_property (GObject *object,
621 				   guint prop_id,
622 				   GValue *value,
623 				   GParamSpec *pspec)
624 {
625 	RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (object);
626 
627 	switch (prop_id) {
628 	case PROP_RHYTHMDB:
629 		g_value_set_object (value, model->priv->db);
630 		break;
631 	case PROP_QUERY:
632 		g_value_set_pointer (value, model->priv->original_query);
633 		break;
634 	case PROP_SORT_FUNC:
635 		g_value_set_pointer (value, model->priv->sort_func);
636 		break;
637 	case PROP_SORT_DATA:
638 		g_value_set_pointer (value, model->priv->sort_data);
639 		break;
640 	case PROP_SORT_DATA_DESTROY:
641 		g_value_set_pointer (value, model->priv->sort_data_destroy);
642 		break;
643 	case PROP_SORT_REVERSE:
644 		g_value_set_boolean (value, model->priv->sort_reverse);
645 		break;
646 	case PROP_LIMIT_TYPE:
647 		g_value_set_enum (value, model->priv->limit_type);
648 		break;
649 	case PROP_LIMIT_VALUE:
650 		g_value_set_variant (value, model->priv->limit_value);
651 		break;
652 	case PROP_SHOW_HIDDEN:
653 		g_value_set_boolean (value, model->priv->show_hidden);
654 		break;
655 	case PROP_BASE_MODEL:
656 		g_value_set_object (value, model->priv->base_model);
657 		break;
658 	default:
659 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
660 		break;
661 	}
662 }
663 
664 static void
rhythmdb_query_model_init(RhythmDBQueryModel * model)665 rhythmdb_query_model_init (RhythmDBQueryModel *model)
666 {
667 	if (!rhythmdb_query_model_drag_target_list)
668 		rhythmdb_query_model_drag_target_list
669 			= gtk_target_list_new (rhythmdb_query_model_drag_types,
670 					       G_N_ELEMENTS (rhythmdb_query_model_drag_types));
671 
672 	model->priv = RHYTHMDB_QUERY_MODEL_GET_PRIVATE (model);
673 
674 	model->priv->stamp = g_random_int ();
675 
676 	model->priv->entries = g_sequence_new (NULL);
677 	model->priv->reverse_map = g_hash_table_new_full (g_direct_hash,
678 							  g_direct_equal,
679 							  (GDestroyNotify)rhythmdb_entry_unref,
680 							  NULL);
681 
682 	model->priv->limited_entries = g_sequence_new (NULL);
683 	model->priv->limited_reverse_map = g_hash_table_new_full (g_direct_hash,
684 								  g_direct_equal,
685 								  (GDestroyNotify)rhythmdb_entry_unref,
686 								  NULL);
687 
688 	model->priv->hidden_entry_map = g_hash_table_new_full (g_direct_hash,
689 							       g_direct_equal,
690 							       (GDestroyNotify)rhythmdb_entry_unref,
691 							       NULL);
692 
693 	model->priv->reorder_drag_and_drop = FALSE;
694 }
695 
696 static void
rhythmdb_query_model_constructed(GObject * object)697 rhythmdb_query_model_constructed (GObject *object)
698 {
699 	RhythmDBQueryModel *model;
700 
701 	RB_CHAIN_GOBJECT_METHOD (rhythmdb_query_model_parent_class, constructed, object);
702 	model = RHYTHMDB_QUERY_MODEL (object);
703 
704 	g_signal_connect_object (G_OBJECT (model->priv->db),
705 				 "entry_added",
706 				 G_CALLBACK (rhythmdb_query_model_entry_added_cb),
707 				 model, 0);
708 	g_signal_connect_object (G_OBJECT (model->priv->db),
709 				 "entry_changed",
710 				 G_CALLBACK (rhythmdb_query_model_entry_changed_cb),
711 				 model, 0);
712 	g_signal_connect_object (G_OBJECT (model->priv->db),
713 				 "entry_deleted",
714 				 G_CALLBACK (rhythmdb_query_model_entry_deleted_cb),
715 				 model, 0);
716 }
717 
718 static void
rhythmdb_query_model_dispose(GObject * object)719 rhythmdb_query_model_dispose (GObject *object)
720 {
721 	RhythmDBQueryModel *model;
722 
723 	g_return_if_fail (object != NULL);
724 	g_return_if_fail (RHYTHMDB_IS_QUERY_MODEL (object));
725 
726 	model = RHYTHMDB_QUERY_MODEL (object);
727 
728 	g_return_if_fail (model->priv != NULL);
729 
730 	rb_debug ("disposing query model %p", object);
731 
732 	if (model->priv->base_model) {
733 		g_signal_handlers_disconnect_by_func (G_OBJECT (model->priv->base_model),
734 						      G_CALLBACK (rhythmdb_query_model_base_row_inserted),
735 						      model);
736 		g_signal_handlers_disconnect_by_func (G_OBJECT (model->priv->base_model),
737 						      G_CALLBACK (rhythmdb_query_model_base_row_deleted),
738 						      model);
739 		g_signal_handlers_disconnect_by_func (G_OBJECT (model->priv->base_model),
740 						      G_CALLBACK (rhythmdb_query_model_base_non_entry_dropped),
741 						      model);
742 		g_signal_handlers_disconnect_by_func (G_OBJECT (model->priv->base_model),
743 						      G_CALLBACK (rhythmdb_query_model_base_complete),
744 						      model);
745 		g_signal_handlers_disconnect_by_func (G_OBJECT (model->priv->base_model),
746 						      G_CALLBACK (rhythmdb_query_model_base_rows_reordered),
747 						      model);
748 		g_signal_handlers_disconnect_by_func (G_OBJECT (model->priv->base_model),
749 						      G_CALLBACK (rhythmdb_query_model_base_entry_removed),
750 						      model);
751 		g_signal_handlers_disconnect_by_func (G_OBJECT (model->priv->base_model),
752 						      G_CALLBACK (rhythmdb_query_model_base_entry_prop_changed),
753 						      model);
754 		g_object_unref (model->priv->base_model);
755 		model->priv->base_model = NULL;
756 	}
757 
758 	if (model->priv->query_reapply_timeout_id != 0) {
759 		g_source_remove (model->priv->query_reapply_timeout_id);
760 		model->priv->query_reapply_timeout_id = 0;
761 	}
762 
763 	G_OBJECT_CLASS (rhythmdb_query_model_parent_class)->dispose (object);
764 }
765 
766 static void
rhythmdb_query_model_finalize(GObject * object)767 rhythmdb_query_model_finalize (GObject *object)
768 {
769 	RhythmDBQueryModel *model;
770 
771 	g_return_if_fail (object != NULL);
772 	g_return_if_fail (RHYTHMDB_IS_QUERY_MODEL (object));
773 
774 	model = RHYTHMDB_QUERY_MODEL (object);
775 
776 	g_return_if_fail (model->priv != NULL);
777 
778 	rb_debug ("finalizing query model %p", object);
779 
780 	g_hash_table_destroy (model->priv->reverse_map);
781 	g_sequence_free (model->priv->entries);
782 
783 	g_hash_table_destroy (model->priv->limited_reverse_map);
784 	g_sequence_free (model->priv->limited_entries);
785 
786 	g_hash_table_destroy (model->priv->hidden_entry_map);
787 
788 	if (model->priv->query)
789 		rhythmdb_query_free (model->priv->query);
790 	if (model->priv->original_query)
791 		rhythmdb_query_free (model->priv->original_query);
792 
793 	if (model->priv->sort_data_destroy && model->priv->sort_data)
794 		model->priv->sort_data_destroy (model->priv->sort_data);
795 
796 	if (model->priv->limit_value)
797 		g_variant_unref (model->priv->limit_value);
798 
799 	G_OBJECT_CLASS (rhythmdb_query_model_parent_class)->finalize (object);
800 }
801 
802 /**
803  * rhythmdb_query_model_new: (skip)
804  * @db: the #RhythmDB
805  * @query: the query for the new model
806  * @sort_func: the sort function for the new model
807  * @sort_data: data to pass to the sort function
808  * @sort_data_destroy: function to call when destroying the sort data
809  * @sort_reverse: if %TRUE, reverse the sort order
810  *
811  * Constructs a new #RhythmDBQueryModel with the specified query and sorting
812  * parameters.
813  *
814  * Return value: the newly constructed query model
815  */
816 RhythmDBQueryModel *
rhythmdb_query_model_new(RhythmDB * db,GPtrArray * query,GCompareDataFunc sort_func,gpointer sort_data,GDestroyNotify sort_data_destroy,gboolean sort_reverse)817 rhythmdb_query_model_new (RhythmDB *db,
818 			  GPtrArray *query,
819 			  GCompareDataFunc sort_func,
820 			  gpointer sort_data,
821 			  GDestroyNotify sort_data_destroy,
822 			  gboolean sort_reverse)
823 {
824 	RhythmDBQueryModel *model = g_object_new (RHYTHMDB_TYPE_QUERY_MODEL,
825 						  "db", db, "query", query,
826 						  "sort-func", sort_func,
827 						  "sort-data", sort_data,
828 						  "sort-data-destroy", sort_data_destroy,
829 						  "sort-reverse", sort_reverse,
830 						  NULL);
831 
832 	g_return_val_if_fail (model->priv != NULL, NULL);
833 
834 	return model;
835 }
836 
837 /**
838  * rhythmdb_query_model_new_empty:
839  * @db: the #RhythmDB
840  *
841  * Constructs a new empty query model with no query or sorting parameters.
842  * This should only be used when the query model will be populated by
843  * explicitly adding entries.
844  *
845  * Return value: the newly constructed query model
846  */
847 RhythmDBQueryModel *
rhythmdb_query_model_new_empty(RhythmDB * db)848 rhythmdb_query_model_new_empty (RhythmDB *db)
849 {
850 	return g_object_new (RHYTHMDB_TYPE_QUERY_MODEL,
851 			     "db", db, NULL);
852 }
853 
854 /**
855  * rhythmdb_query_model_new_for_entry_type:
856  * @db: the #RhythmDB
857  * @entry_type: the #RhythmDBEntryType to display
858  * @show_hidden: if %TRUE, show hidden entries
859  *
860  * Return value: the newly constructed query model
861  */
862 RhythmDBQueryModel *
rhythmdb_query_model_new_for_entry_type(RhythmDB * db,RhythmDBEntryType * entry_type,gboolean show_hidden)863 rhythmdb_query_model_new_for_entry_type (RhythmDB *db,
864 					 RhythmDBEntryType *entry_type,
865 					 gboolean show_hidden)
866 {
867 	RhythmDBQueryModel *model;
868 	model = rhythmdb_query_model_new_empty (db);
869 	g_object_set (model, "show-hidden", show_hidden, NULL);
870 	rhythmdb_do_full_query_async (db,
871 				      RHYTHMDB_QUERY_RESULTS (model),
872 				      RHYTHMDB_QUERY_PROP_EQUALS, RHYTHMDB_PROP_TYPE, entry_type,
873 				      RHYTHMDB_QUERY_END);
874 	return model;
875 }
876 
877 
878 static void
_copy_contents_foreach_cb(RhythmDBEntry * entry,RhythmDBQueryModel * dest)879 _copy_contents_foreach_cb (RhythmDBEntry *entry, RhythmDBQueryModel *dest)
880 {
881 	if (dest->priv->query == NULL ||
882 	    rhythmdb_evaluate_query (dest->priv->db, dest->priv->query, entry)) {
883 		if (dest->priv->show_hidden || (rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN) == FALSE))
884 			rhythmdb_query_model_do_insert (dest, entry, -1);
885 	}
886 }
887 
888 /**
889  * rhythmdb_query_model_copy_contents:
890  * @dest: destination #RhythmDBQueryModel
891  * @src: source #RhythmDBQueryModel
892  *
893  * Copies all entries from @src to @dest.
894  */
895 void
rhythmdb_query_model_copy_contents(RhythmDBQueryModel * dest,RhythmDBQueryModel * src)896 rhythmdb_query_model_copy_contents (RhythmDBQueryModel *dest,
897 				    RhythmDBQueryModel *src)
898 {
899 	if (src->priv->entries == NULL)
900 		return;
901 
902 	g_sequence_foreach (src->priv->entries, (GFunc)_copy_contents_foreach_cb, dest);
903 }
904 
905 /**
906  * rhythmdb_query_model_chain:
907  * @model: the #RhythmDBQueryModel to chain
908  * @base: the #RhythmDBQueryModel to chain it to
909  * @import_entries: if %TRUE, copy all existing entries from @base to @model
910  *
911  * Chains @model to @base.  All changes made to the base model will be reflected in
912  * the child model, and all changes made to the child model will be passed on to the
913  * base model.
914  */
915 void
rhythmdb_query_model_chain(RhythmDBQueryModel * model,RhythmDBQueryModel * base,gboolean import_entries)916 rhythmdb_query_model_chain (RhythmDBQueryModel *model,
917 			    RhythmDBQueryModel *base,
918 			    gboolean import_entries)
919 {
920 	rb_debug ("query model %p chaining to base model %p", model, base);
921 
922 	if (model->priv->base_model != NULL) {
923 		g_signal_handlers_disconnect_by_func (model->priv->base_model,
924 						      G_CALLBACK (rhythmdb_query_model_base_row_inserted),
925 						      model);
926 		g_signal_handlers_disconnect_by_func (model->priv->base_model,
927 						      G_CALLBACK (rhythmdb_query_model_base_row_deleted),
928 						      model);
929 		g_signal_handlers_disconnect_by_func (model->priv->base_model,
930 						      G_CALLBACK (rhythmdb_query_model_base_non_entry_dropped),
931 						      model);
932 		g_signal_handlers_disconnect_by_func (model->priv->base_model,
933 						      G_CALLBACK (rhythmdb_query_model_base_complete),
934 						      model);
935 		g_signal_handlers_disconnect_by_func (model->priv->base_model,
936 						      G_CALLBACK (rhythmdb_query_model_base_rows_reordered),
937 						      model);
938 		g_signal_handlers_disconnect_by_func (model->priv->base_model,
939 						      G_CALLBACK (rhythmdb_query_model_base_entry_removed),
940 						      model);
941 		g_signal_handlers_disconnect_by_func (model->priv->base_model,
942 						      G_CALLBACK (rhythmdb_query_model_base_entry_prop_changed),
943 						      model);
944 		g_object_unref (model->priv->base_model);
945 	}
946 
947 	model->priv->base_model = base;
948 
949 	if (model->priv->base_model != NULL) {
950 		g_object_ref (model->priv->base_model);
951 
952 		g_assert (model->priv->base_model->priv->db == model->priv->db);
953 
954 		g_signal_connect_object (model->priv->base_model,
955 					 "row-inserted",
956 					 G_CALLBACK (rhythmdb_query_model_base_row_inserted),
957 					 model, 0);
958 		g_signal_connect_object (model->priv->base_model,
959 					 "row-deleted",
960 					 G_CALLBACK (rhythmdb_query_model_base_row_deleted),
961 					 model, 0);
962 		g_signal_connect_object (model->priv->base_model,
963 					 "non-entry-dropped",
964 					 G_CALLBACK (rhythmdb_query_model_base_non_entry_dropped),
965 					 model, 0);
966 		g_signal_connect_object (model->priv->base_model,
967 					 "complete",
968 					 G_CALLBACK (rhythmdb_query_model_base_complete),
969 					 model, 0);
970 		g_signal_connect_object (model->priv->base_model,
971 					 "rows-reordered",
972 					 G_CALLBACK (rhythmdb_query_model_base_rows_reordered),
973 					 model, 0);
974 		g_signal_connect_object (model->priv->base_model,
975 					 "entry-removed",
976 					 G_CALLBACK (rhythmdb_query_model_base_entry_removed),
977 					 model, 0);
978 		g_signal_connect_object (model->priv->base_model,
979 					 "entry-prop-changed",
980 					 G_CALLBACK (rhythmdb_query_model_base_entry_prop_changed),
981 					 model, 0);
982 
983 		if (import_entries)
984 			rhythmdb_query_model_copy_contents (model, model->priv->base_model);
985 	}
986 }
987 
988 /**
989  * rhythmdb_query_model_has_pending_changes:
990  * @model: a #RhythmDBQueryModel
991  *
992  * Checks if a #RhythmDBQueryModel has any outstanding changes that are
993  * yet to be processed.  This is not very useful.
994  *
995  * Return value: %TRUE if there are outstanding changes to the model
996  */
997 gboolean
rhythmdb_query_model_has_pending_changes(RhythmDBQueryModel * model)998 rhythmdb_query_model_has_pending_changes (RhythmDBQueryModel *model)
999 {
1000 	gboolean result;
1001 
1002 	result = g_atomic_int_get (&model->priv->pending_update_count) > 0;
1003 	if (model->priv->base_model)
1004 		result |= rhythmdb_query_model_has_pending_changes (model->priv->base_model);
1005 
1006 	return result;
1007 }
1008 
1009 static void
rhythmdb_query_model_entry_added_cb(RhythmDB * db,RhythmDBEntry * entry,RhythmDBQueryModel * model)1010 rhythmdb_query_model_entry_added_cb (RhythmDB *db,
1011 				     RhythmDBEntry *entry,
1012 				     RhythmDBQueryModel *model)
1013 {
1014 	int index = -1;
1015 	gboolean insert = FALSE;
1016 	if (!model->priv->show_hidden && rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN)) {
1017 		return;
1018 	}
1019 
1020 	/* check if it's in the base model */
1021 	if (model->priv->base_model) {
1022 	       if (g_hash_table_lookup (model->priv->base_model->priv->reverse_map, entry) == NULL) {
1023 		       return;
1024 	       }
1025 	}
1026 
1027 	if (model->priv->query != NULL) {
1028 		insert = rhythmdb_evaluate_query (db, model->priv->query, entry);
1029 	} else {
1030 		index = GPOINTER_TO_INT (g_hash_table_lookup (model->priv->hidden_entry_map, entry));
1031 		insert = g_hash_table_remove (model->priv->hidden_entry_map, entry);
1032 		if (insert)
1033 			rb_debug ("adding unhidden entry at index %d", index);
1034 	}
1035 
1036 	if (insert) {
1037 		rhythmdb_query_model_do_insert (model, entry, index);
1038 	}
1039 }
1040 
1041 static void
rhythmdb_query_model_entry_changed_cb(RhythmDB * db,RhythmDBEntry * entry,GPtrArray * changes,RhythmDBQueryModel * model)1042 rhythmdb_query_model_entry_changed_cb (RhythmDB *db,
1043 				       RhythmDBEntry *entry,
1044 				       GPtrArray *changes,
1045 				       RhythmDBQueryModel *model)
1046 {
1047 	gboolean hidden = FALSE;
1048 	int i;
1049 
1050 	hidden = (!model->priv->show_hidden && rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN));
1051 
1052 	if (g_hash_table_lookup (model->priv->reverse_map, entry) == NULL) {
1053 		if (hidden == FALSE) {
1054 			/* the changed entry may now satisfy the query
1055 			 * so we test it */
1056 			rhythmdb_query_model_entry_added_cb (db, entry, model);
1057 		}
1058 		return;
1059 	}
1060 
1061 	if (hidden) {
1062 		/* emit an entry-prop-changed signal so property models
1063 		 * can be updated correctly.  if we have a base model,
1064 		 * we'll propagate the parent's signal instead.
1065 		 */
1066 		if (model->priv->base_model == NULL) {
1067 			GValue true_val = { 0, };
1068 			GValue false_val = { 0, };
1069 
1070 			g_value_init (&true_val, G_TYPE_BOOLEAN);
1071 			g_value_set_boolean (&true_val, TRUE);
1072 			g_value_init (&false_val, G_TYPE_BOOLEAN);
1073 			g_value_set_boolean (&false_val, FALSE);
1074 
1075 			rb_debug ("emitting hidden-removal notification for %s",
1076 				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
1077 			g_signal_emit (G_OBJECT (model),
1078 				       rhythmdb_query_model_signals[ENTRY_PROP_CHANGED], 0,
1079 				       entry, RHYTHMDB_PROP_HIDDEN, &false_val, &true_val);
1080 			g_value_unset (&true_val);
1081 			g_value_unset (&false_val);
1082 		}
1083 
1084 		/* if we don't have a query to help us decide, we need to
1085 		 * track hidden entries that were in this query model,
1086 		 * so we can add them back in if they become visible again.
1087 		 * if we have a query, any entry that matches will be added.
1088 		 */
1089 		if (model->priv->query == NULL) {
1090 			GtkTreeIter iter;
1091 			GtkTreePath *path;
1092 			gint index;
1093 
1094 			/* find the entry's position so we can restore it there
1095 			 * if it reappears
1096 			 */
1097 			g_assert (rhythmdb_query_model_entry_to_iter (model, entry, &iter));
1098 			path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
1099 			index = gtk_tree_path_get_indices (path)[0];
1100 			gtk_tree_path_free (path);
1101 			rb_debug ("adding hidden entry to map with index %d", index);
1102 
1103 			g_hash_table_insert (model->priv->hidden_entry_map,
1104 					     rhythmdb_entry_ref (entry),
1105 					     GINT_TO_POINTER (index));
1106 		}
1107 
1108 		rhythmdb_query_model_filter_out_entry (model, entry);
1109 		return;
1110 	}
1111 
1112 	/* emit separate change signals for each property
1113 	 * unless this is a chained query model, in which
1114 	 * case we propagate the parent model's signals instead.
1115 	 */
1116 	for (i = 0; i < changes->len; i++) {
1117 		RhythmDBEntryChange *change = g_ptr_array_index (changes, i);
1118 
1119 		if (model->priv->base_model == NULL) {
1120 			g_signal_emit (G_OBJECT (model),
1121 				       rhythmdb_query_model_signals[ENTRY_PROP_CHANGED], 0,
1122 				       entry, change->prop, &change->old, &change->new);
1123 		}
1124 
1125 		if (change->prop == RHYTHMDB_PROP_DURATION) {
1126 			model->priv->total_duration -= g_value_get_ulong (&change->old);
1127 			model->priv->total_duration += g_value_get_ulong (&change->new);
1128 		} else if (change->prop == RHYTHMDB_PROP_FILE_SIZE) {
1129 			model->priv->total_size -= g_value_get_uint64 (&change->old);
1130 			model->priv->total_size += g_value_get_uint64 (&change->new);
1131 		}
1132 	}
1133 
1134 	if (model->priv->query &&
1135 	    !rhythmdb_evaluate_query (db, model->priv->query, entry)) {
1136 		rhythmdb_query_model_filter_out_entry (model, entry);
1137 		return;
1138 	}
1139 
1140 	/* it may have moved, so we can't just emit a changed entry */
1141 	if (!rhythmdb_query_model_do_reorder (model, entry)) {
1142 		/* but if it didn't, we can */
1143 		GtkTreeIter iter;
1144 		GtkTreePath *path;
1145 
1146 		if (rhythmdb_query_model_entry_to_iter (model, entry, &iter)) {
1147 			path = rhythmdb_query_model_get_path (GTK_TREE_MODEL (model),
1148 							      &iter);
1149 			gtk_tree_model_row_changed (GTK_TREE_MODEL (model),
1150 						     path, &iter);
1151 			gtk_tree_path_free (path);
1152 		}
1153 	}
1154 }
1155 
1156 static void
rhythmdb_query_model_base_entry_prop_changed(RhythmDBQueryModel * base_model,RhythmDBEntry * entry,RhythmDBPropType prop,const GValue * old,const GValue * new_value,RhythmDBQueryModel * model)1157 rhythmdb_query_model_base_entry_prop_changed (RhythmDBQueryModel *base_model,
1158 					      RhythmDBEntry *entry,
1159 					      RhythmDBPropType prop,
1160 					      const GValue *old,
1161 					      const GValue *new_value,
1162 					      RhythmDBQueryModel *model)
1163 {
1164 	if (g_hash_table_lookup (model->priv->reverse_map, entry)) {
1165 		/* propagate the signal */
1166 		g_signal_emit (G_OBJECT (model),
1167 			       rhythmdb_query_model_signals[ENTRY_PROP_CHANGED], 0,
1168 			       entry, prop, old, new_value);
1169 	}
1170 }
1171 
1172 static void
rhythmdb_query_model_entry_deleted_cb(RhythmDB * db,RhythmDBEntry * entry,RhythmDBQueryModel * model)1173 rhythmdb_query_model_entry_deleted_cb (RhythmDB *db,
1174 				       RhythmDBEntry *entry,
1175 				       RhythmDBQueryModel *model)
1176 {
1177 
1178 	if (g_hash_table_lookup (model->priv->reverse_map, entry) ||
1179 	    g_hash_table_lookup (model->priv->limited_reverse_map, entry))
1180 		rhythmdb_query_model_remove_entry (model, entry);
1181 }
1182 
1183 static gboolean
idle_process_update_idle(struct RhythmDBQueryModelUpdate * update)1184 idle_process_update_idle (struct RhythmDBQueryModelUpdate *update)
1185 {
1186 	idle_process_update (update);
1187 	return FALSE;
1188 }
1189 
1190 static void
rhythmdb_query_model_process_update(struct RhythmDBQueryModelUpdate * update)1191 rhythmdb_query_model_process_update (struct RhythmDBQueryModelUpdate *update)
1192 {
1193 	g_atomic_int_inc (&update->model->priv->pending_update_count);
1194 	if (rb_is_main_thread ())
1195 		idle_process_update (update);
1196 	else
1197 		g_idle_add ((GSourceFunc) idle_process_update_idle, update);
1198 }
1199 
1200 static void
idle_process_update(struct RhythmDBQueryModelUpdate * update)1201 idle_process_update (struct RhythmDBQueryModelUpdate *update)
1202 {
1203 	switch (update->type) {
1204 	case RHYTHMDB_QUERY_MODEL_UPDATE_ROWS_INSERTED:
1205 	{
1206 		guint i;
1207 
1208 		rb_debug ("inserting %d rows", update->entrydata.entries->len);
1209 
1210 		for (i = 0; i < update->entrydata.entries->len; i++ ) {
1211 			RhythmDBEntry *entry = g_ptr_array_index (update->entrydata.entries, i);
1212 
1213 			if (update->model->priv->show_hidden || !rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN)) {
1214 				RhythmDBQueryModel *base_model = update->model->priv->base_model;
1215 				if (base_model &&
1216 				    g_hash_table_lookup (base_model->priv->reverse_map, entry) == NULL)
1217 					       continue;
1218 
1219 				rhythmdb_query_model_do_insert (update->model, entry, -1);
1220 			}
1221 
1222 			rhythmdb_entry_unref (entry);
1223 		}
1224 
1225 		g_ptr_array_free (update->entrydata.entries, TRUE);
1226 
1227 		break;
1228 	}
1229 	case RHYTHMDB_QUERY_MODEL_UPDATE_ROW_INSERTED_INDEX:
1230 	{
1231 		rb_debug ("inserting row at index %d", update->entrydata.data.index);
1232 		rhythmdb_query_model_do_insert (update->model, update->entrydata.data.entry, update->entrydata.data.index);
1233 		rhythmdb_entry_unref (update->entrydata.data.entry);
1234 		break;
1235 	}
1236 	case RHYTHMDB_QUERY_MODEL_UPDATE_QUERY_COMPLETE:
1237 		g_signal_emit (G_OBJECT (update->model), rhythmdb_query_model_signals[COMPLETE], 0);
1238 		break;
1239 	}
1240 
1241 	g_atomic_int_add (&update->model->priv->pending_update_count, -1);
1242 	g_object_unref (update->model);
1243 	g_free (update);
1244 }
1245 
1246 /**
1247  * rhythmdb_query_model_add_entry:
1248  * @model: a #RhythmDBQueryModel
1249  * @entry: a #RhythmDBEntry to add
1250  * @index: position at which to add it, or -1 to add at the end
1251  *
1252  * Adds an entry to the query model at the specified position.  Does not check
1253  * if the entry matches the query (if any).
1254  */
1255 void
rhythmdb_query_model_add_entry(RhythmDBQueryModel * model,RhythmDBEntry * entry,gint index)1256 rhythmdb_query_model_add_entry (RhythmDBQueryModel *model,
1257 				RhythmDBEntry *entry,
1258 				gint index)
1259 {
1260 	struct RhythmDBQueryModelUpdate *update;
1261 
1262 	if (!model->priv->show_hidden && rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN)) {
1263 		rb_debug ("attempting to add hidden entry");
1264 		return;
1265 	}
1266 
1267 	if (model->priv->base_model) {
1268 		/* add it to the base model, which will cause it to be added to this one */
1269 		rhythmdb_query_model_add_entry (model->priv->base_model, entry,
1270 						rhythmdb_query_model_child_index_to_base_index (model, index));
1271 		return;
1272 	}
1273 
1274 	rb_debug ("inserting entry %p at index %d", entry, index);
1275 
1276 	update = g_new (struct RhythmDBQueryModelUpdate, 1);
1277 	update->type = RHYTHMDB_QUERY_MODEL_UPDATE_ROW_INSERTED_INDEX;
1278 	update->entrydata.data.entry = entry;
1279 	update->entrydata.data.index = index;
1280 	update->model = model;
1281 
1282 	/* take references; released in update idle */
1283 	g_object_ref (model);
1284 
1285 	rhythmdb_entry_ref (entry);
1286 
1287 	rhythmdb_query_model_process_update (update);
1288 }
1289 
1290 /**
1291  * rhythmdb_query_model_get_size:
1292  * @model: the #RhythmDBQueryModel
1293  *
1294  * Returns the total size of all entries in the model.
1295  *
1296  * Return value: the total size (in bytes) of all entries in the model
1297  */
1298 guint64
rhythmdb_query_model_get_size(RhythmDBQueryModel * model)1299 rhythmdb_query_model_get_size (RhythmDBQueryModel *model)
1300 {
1301 	return model->priv->total_size;
1302 }
1303 
1304 /**
1305  * rhythmdb_query_model_get_duration:
1306  * @model: the #RhythmDBQueryModel
1307  *
1308  * Returns the total duration of all entries in the model.
1309  *
1310  * Return value: the total duration (in seconds) of all entries in the model
1311  */
1312 long
rhythmdb_query_model_get_duration(RhythmDBQueryModel * model)1313 rhythmdb_query_model_get_duration (RhythmDBQueryModel *model)
1314 {
1315 	return model->priv->total_duration;
1316 }
1317 
1318 static void
rhythmdb_query_model_insert_into_main_list(RhythmDBQueryModel * model,RhythmDBEntry * entry,gint index)1319 rhythmdb_query_model_insert_into_main_list (RhythmDBQueryModel *model,
1320 					    RhythmDBEntry *entry,
1321 					    gint index)
1322 {
1323 	GSequenceIter *ptr;
1324 
1325 	/* take reference; released when removed from hash */
1326 	rhythmdb_entry_ref (entry);
1327 
1328 	if (model->priv->sort_func) {
1329 		GCompareDataFunc sort_func;
1330 		gpointer sort_data;
1331 		struct ReverseSortData reverse_data;
1332 
1333 		if (model->priv->sort_reverse) {
1334 			sort_func = (GCompareDataFunc) _reverse_sorting_func;
1335 			sort_data = &reverse_data;
1336 			reverse_data.func = model->priv->sort_func;
1337 			reverse_data.data = model->priv->sort_data;
1338 		} else {
1339 			sort_func = model->priv->sort_func;
1340 			sort_data = model->priv->sort_data;
1341 		}
1342 
1343 		ptr = g_sequence_insert_sorted (model->priv->entries,
1344 						entry,
1345 						sort_func,
1346 						sort_data);
1347 	} else {
1348 		if (index == -1) {
1349 			ptr = g_sequence_get_end_iter (model->priv->entries);
1350 		} else {
1351 			ptr = g_sequence_get_iter_at_pos (model->priv->entries, index);
1352 		}
1353 
1354 		g_sequence_insert_before (ptr, entry);
1355 		ptr = g_sequence_iter_prev (ptr);
1356 	}
1357 
1358 	/* the hash now owns this reference to the entry */
1359 	g_hash_table_insert (model->priv->reverse_map, entry, ptr);
1360 
1361 	model->priv->total_duration += rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
1362 	model->priv->total_size += rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
1363 }
1364 
1365 static void
rhythmdb_query_model_insert_into_limited_list(RhythmDBQueryModel * model,RhythmDBEntry * entry)1366 rhythmdb_query_model_insert_into_limited_list (RhythmDBQueryModel *model,
1367 					       RhythmDBEntry *entry)
1368 {
1369 	GSequenceIter *ptr;
1370 
1371 	/* take reference; released when removed from hash */
1372 	rhythmdb_entry_ref (entry);
1373 
1374 	if (model->priv->sort_func) {
1375 		GCompareDataFunc sort_func;
1376 		gpointer sort_data;
1377 		struct ReverseSortData reverse_data;
1378 
1379 		if (model->priv->sort_reverse) {
1380 			sort_func = (GCompareDataFunc) _reverse_sorting_func;
1381 			sort_data = &reverse_data;
1382 			reverse_data.func = model->priv->sort_func;
1383 			reverse_data.data = model->priv->sort_data;
1384 		} else {
1385 			sort_func = model->priv->sort_func;
1386 			sort_data = model->priv->sort_data;
1387 		}
1388 
1389 		ptr = g_sequence_insert_sorted (model->priv->limited_entries, entry,
1390 						sort_func,
1391 						sort_data);
1392 	} else {
1393 		ptr = g_sequence_get_end_iter (model->priv->limited_entries);
1394 		g_sequence_insert_before (ptr, entry);
1395 		ptr = g_sequence_iter_prev (ptr);
1396 	}
1397 
1398 	/* the hash now owns this reference to the entry */
1399 	g_hash_table_insert (model->priv->limited_reverse_map, entry, ptr);
1400 }
1401 
1402 static void
rhythmdb_query_model_remove_from_main_list(RhythmDBQueryModel * model,RhythmDBEntry * entry)1403 rhythmdb_query_model_remove_from_main_list (RhythmDBQueryModel *model,
1404 					    RhythmDBEntry *entry)
1405 {
1406 	GSequenceIter *ptr;
1407 	int index;
1408 	GtkTreePath *path;
1409 
1410 	ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
1411 	index = g_sequence_iter_get_position (ptr);
1412 
1413 	path = gtk_tree_path_new ();
1414 	gtk_tree_path_append_index (path, index);
1415 
1416 	gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
1417 	gtk_tree_path_free (path);
1418 
1419 	model->priv->total_duration -= rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
1420 	model->priv->total_size -= rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
1421 
1422 	/* take temporary ref */
1423 	rhythmdb_entry_ref (entry);
1424 
1425 	/* find the sequence pointer again in case a row-deleted
1426 	 * signal handler moved it.
1427 	 */
1428 	ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
1429 	g_sequence_remove (ptr);
1430 	g_assert (g_hash_table_remove (model->priv->reverse_map, entry));
1431 
1432 	g_signal_emit (G_OBJECT (model), rhythmdb_query_model_signals[POST_ENTRY_DELETE], 0, entry);
1433 
1434 	/* release temporary ref */
1435 	rhythmdb_entry_unref (entry);
1436 }
1437 
1438 static void
rhythmdb_query_model_remove_from_limited_list(RhythmDBQueryModel * model,RhythmDBEntry * entry)1439 rhythmdb_query_model_remove_from_limited_list (RhythmDBQueryModel *model,
1440 					       RhythmDBEntry *entry)
1441 {
1442 	GSequenceIter *ptr = g_hash_table_lookup (model->priv->limited_reverse_map, entry);
1443 
1444 	/* take temporary ref */
1445 	rhythmdb_entry_ref (entry);
1446 	g_sequence_remove (ptr);
1447 	g_hash_table_remove (model->priv->limited_reverse_map, entry);
1448 	/* release temporary ref */
1449 	rhythmdb_entry_unref (entry);
1450 }
1451 
1452 static void
rhythmdb_query_model_update_limited_entries(RhythmDBQueryModel * model)1453 rhythmdb_query_model_update_limited_entries (RhythmDBQueryModel *model)
1454 {
1455 	RhythmDBEntry *entry;
1456 	GSequenceIter *ptr;
1457 
1458 	/* make it fit inside the limits */
1459 	while (!rhythmdb_query_model_within_limit (model, NULL)) {
1460 		ptr = g_sequence_iter_prev (g_sequence_get_end_iter (model->priv->entries));
1461 		entry = (RhythmDBEntry*) g_sequence_get (ptr);
1462 
1463 		/* take temporary ref */
1464 		rhythmdb_entry_ref (entry);
1465 		rhythmdb_query_model_remove_from_main_list (model, entry);
1466 		rhythmdb_query_model_insert_into_limited_list (model, entry);
1467 		/* release temporary ref */
1468 		rhythmdb_entry_unref (entry);
1469 	}
1470 
1471 	/* move entries that were previously limited, back to the main list */
1472 	while (TRUE) {
1473 		GtkTreePath *path;
1474 		GtkTreeIter iter;
1475 
1476 		ptr = g_sequence_get_begin_iter (model->priv->limited_entries);
1477 		if (!ptr || ptr == g_sequence_get_end_iter (model->priv->limited_entries))
1478 			break;
1479 		entry = (RhythmDBEntry*) g_sequence_get (ptr);
1480 		if (!entry)
1481 			break;
1482 
1483 		if (!rhythmdb_query_model_within_limit (model, entry))
1484 			break;
1485 
1486 		/* take temporary ref */
1487 		rhythmdb_entry_ref (entry);
1488 		rhythmdb_query_model_remove_from_limited_list (model, entry);
1489 		rhythmdb_query_model_insert_into_main_list (model, entry, -1);
1490 		/* release temporary ref */
1491 		rhythmdb_entry_unref (entry);
1492 
1493 		ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
1494 		iter.stamp = model->priv->stamp;
1495 		iter.user_data = ptr;
1496 		path = rhythmdb_query_model_get_path (GTK_TREE_MODEL (model),
1497 						      &iter);
1498 		gtk_tree_model_row_inserted (GTK_TREE_MODEL (model),
1499 					     path, &iter);
1500 		gtk_tree_path_free (path);
1501 	}
1502 }
1503 
1504 static gboolean
rhythmdb_query_model_emit_reorder(RhythmDBQueryModel * model,gint old_pos,gint new_pos)1505 rhythmdb_query_model_emit_reorder (RhythmDBQueryModel *model,
1506 				   gint old_pos,
1507 				   gint new_pos)
1508 {
1509 	int length, i;
1510 	gint *reorder_map;
1511 	GtkTreePath *path;
1512 	GtkTreeIter iter;
1513 
1514 	if (new_pos == old_pos) {
1515 		/* it hasn't moved, don't emit a re-order */
1516 		return FALSE;
1517 	}
1518 
1519 	length = g_sequence_get_length (model->priv->entries);
1520 	reorder_map = g_malloc (length * sizeof(gint));
1521 
1522 	if (new_pos > old_pos) {
1523 		/* it has mover further down the list */
1524 		for (i = 0; i < old_pos; i++)
1525 			reorder_map[i] = i;
1526 		for (i = old_pos; i < new_pos; i++)
1527 			reorder_map[i] = i + 1;
1528 		reorder_map[new_pos] = old_pos;
1529 		for (i = new_pos + 1; i < length; i++)
1530 			reorder_map[i] = i;
1531 	} else {
1532 		/* it has moved up the list */
1533 		for (i = 0; i < new_pos; i++)
1534 			reorder_map[i] = i;
1535 		reorder_map[new_pos] = old_pos;
1536 		for (i = new_pos + 1; i < old_pos + 1; i++)
1537 			reorder_map[i] = i - 1;
1538 		for (i = old_pos + 1; i < length; i++)
1539 			reorder_map[i] = i;
1540 	}
1541 
1542 	gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter);
1543 	path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
1544 
1545 	gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model),
1546 				       path, &iter,
1547 				       reorder_map);
1548 	gtk_tree_path_free (path);
1549 	g_free (reorder_map);
1550 	return TRUE;
1551 }
1552 
1553 static gboolean
rhythmdb_query_model_do_reorder(RhythmDBQueryModel * model,RhythmDBEntry * entry)1554 rhythmdb_query_model_do_reorder (RhythmDBQueryModel *model,
1555 				 RhythmDBEntry *entry)
1556 {
1557 	GSequenceIter *ptr;
1558 	int old_pos, new_pos;
1559 	GtkTreePath *path;
1560 	GtkTreeIter iter;
1561 	GCompareDataFunc sort_func;
1562 	gpointer sort_data;
1563 	struct ReverseSortData reverse_data;
1564 
1565 	if (model->priv->sort_func == NULL)
1566 		return FALSE;
1567 
1568 	if (model->priv->sort_reverse) {
1569 		sort_func = (GCompareDataFunc) _reverse_sorting_func;
1570 		sort_data = &reverse_data;
1571 		reverse_data.func = model->priv->sort_func;
1572 		reverse_data.data = model->priv->sort_data;
1573 	} else {
1574 		sort_func = model->priv->sort_func;
1575 		sort_data = model->priv->sort_data;
1576 	}
1577 
1578 	ptr = g_sequence_get_begin_iter (model->priv->limited_entries);
1579 
1580 	if (ptr != NULL && !g_sequence_iter_is_end (ptr)) {
1581 		RhythmDBEntry *first_limited = g_sequence_get (ptr);
1582 		int cmp = (sort_func) (entry, first_limited, sort_data);
1583 
1584 		if (cmp > 0) {
1585 			/* the entry belongs in the limited list, so we don't need a re-order */
1586 			/* take temporary ref */
1587 			rhythmdb_entry_ref (entry);
1588 			rhythmdb_query_model_remove_entry (model, entry);
1589 			rhythmdb_query_model_do_insert (model, entry, -1);
1590 			/* release temporary ref */
1591 			rhythmdb_entry_unref (entry);
1592 			return TRUE;
1593 		}
1594 	}
1595 
1596 	ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
1597 	iter.stamp = model->priv->stamp;
1598 	iter.user_data = ptr;
1599 	path = rhythmdb_query_model_get_path (GTK_TREE_MODEL (model),
1600 					      &iter);
1601 	gtk_tree_model_row_changed (GTK_TREE_MODEL (model),
1602 				     path, &iter);
1603 	gtk_tree_path_free (path);
1604 
1605 	/* take a temporary ref */
1606 	rhythmdb_entry_ref (entry);
1607 
1608 	/* it may have moved, check for a re-order */
1609 	g_hash_table_remove (model->priv->reverse_map, entry);
1610 	old_pos = g_sequence_iter_get_position (ptr);
1611 	g_sequence_remove (ptr);
1612 
1613 	ptr = g_sequence_insert_sorted (model->priv->entries, entry,
1614 					sort_func,
1615 					sort_data);
1616 	new_pos = g_sequence_iter_get_position (ptr);
1617 
1618 	/* the hash now owns this reference to the entry */
1619 	g_hash_table_insert (model->priv->reverse_map, entry, ptr);
1620 
1621 	return rhythmdb_query_model_emit_reorder (model, old_pos, new_pos);
1622 }
1623 
1624 static void
rhythmdb_query_model_do_insert(RhythmDBQueryModel * model,RhythmDBEntry * entry,gint index)1625 rhythmdb_query_model_do_insert (RhythmDBQueryModel *model,
1626 				RhythmDBEntry *entry,
1627 				gint index)
1628 {
1629 	GSequenceIter *ptr;
1630 	GtkTreePath *path;
1631 	GtkTreeIter iter;
1632 
1633 	g_assert (model->priv->show_hidden || !rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN));
1634 
1635 	/* we check again if the entry already exists in the hash table */
1636 	if (g_hash_table_lookup (model->priv->reverse_map, entry) != NULL)
1637 		return;
1638 
1639 	/* take temporary ref */
1640 	rhythmdb_entry_ref (entry);
1641 
1642 	ptr = g_hash_table_lookup (model->priv->limited_reverse_map, entry);
1643 	if (ptr != NULL) {
1644 		rhythmdb_query_model_remove_from_limited_list (model, entry);
1645 	}
1646 
1647 	rhythmdb_query_model_insert_into_main_list (model, entry, index);
1648 
1649 	/* release temporary ref */
1650 	rhythmdb_entry_unref (entry);
1651 
1652 	/* it was added to the main list, send out the inserted signal */
1653 	ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
1654 	iter.stamp = model->priv->stamp;
1655 	iter.user_data = ptr;
1656 	path = rhythmdb_query_model_get_path (GTK_TREE_MODEL (model),
1657 					      &iter);
1658 	gtk_tree_model_row_inserted (GTK_TREE_MODEL (model),
1659 				     path, &iter);
1660 	gtk_tree_path_free (path);
1661 
1662 	rhythmdb_query_model_update_limited_entries (model);
1663 }
1664 
1665 static void
rhythmdb_query_model_filter_out_entry(RhythmDBQueryModel * model,RhythmDBEntry * entry)1666 rhythmdb_query_model_filter_out_entry (RhythmDBQueryModel *model,
1667 				       RhythmDBEntry *entry)
1668 {
1669 	GSequenceIter *ptr;
1670 
1671 	ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
1672 	if (ptr != NULL) {
1673 		rhythmdb_query_model_remove_from_main_list (model, entry);
1674 		rhythmdb_query_model_update_limited_entries (model);
1675 		return;
1676 	}
1677 
1678 	ptr = g_hash_table_lookup (model->priv->limited_reverse_map, entry);
1679 	if (ptr != NULL) {
1680 		rhythmdb_query_model_remove_from_limited_list (model, entry);
1681 		rhythmdb_query_model_update_limited_entries (model);
1682 		return;
1683 	}
1684 }
1685 
1686 /**
1687  * rhythmdb_query_model_shuffle_entries:
1688  * @model: a #RhythmDBQueryModel
1689  *
1690  * Shuffles the entries in the model into a new random order.
1691  */
1692 void
rhythmdb_query_model_shuffle_entries(RhythmDBQueryModel * model)1693 rhythmdb_query_model_shuffle_entries (RhythmDBQueryModel *model)
1694 {
1695 	RhythmDBEntry **entries;
1696 	int listsize;
1697 	int i;
1698 	int swapwith;
1699 	RhythmDBEntry *entry;
1700 	GSequenceIter *iter;
1701 	GtkTreePath *path;
1702 	GtkTreeIter tree_iter;
1703 	int *map_new_old;
1704 
1705 	/* Convert the entries list to an array, for fast lookups. */
1706 	listsize = g_sequence_get_length (model->priv->entries);
1707 	entries = (RhythmDBEntry **)g_malloc (sizeof(RhythmDBEntry *) * listsize);
1708 	map_new_old = (int *)g_malloc (sizeof(int) * listsize);
1709 
1710 	iter = g_sequence_get_begin_iter (model->priv->entries);
1711 	i = 0;
1712 	while (!g_sequence_iter_is_end (iter)) {
1713 		entries[i++] = g_sequence_get (iter);
1714 		iter = g_sequence_iter_next (iter);
1715 	}
1716 
1717 	/* Shuffle the array. */
1718 	for (i=0; i<listsize; i++) {
1719 		swapwith = g_random_int_range (i, listsize);
1720 		map_new_old[swapwith] = i;
1721 		entry = entries[swapwith];
1722 		entries[swapwith] = entries[i];
1723 		entries[i] = entry;
1724 	}
1725 
1726 	/* Convert the array back into a sequence, rebuilding the reverse map. */
1727 	iter = g_sequence_get_begin_iter (model->priv->entries);
1728 	i = 0;
1729 	while (!g_sequence_iter_is_end (iter)) {
1730 		g_sequence_set (iter, (gpointer)entries[i]);
1731 		rhythmdb_entry_ref (entries[i]);
1732 		g_hash_table_remove (model->priv->reverse_map, entries[i]);
1733 		g_hash_table_insert (model->priv->reverse_map, entries[i], iter);
1734 
1735 		iter = g_sequence_iter_next (iter);
1736 		i++;
1737 	}
1738 
1739 	/* Emit reorder signals. */
1740 	gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &tree_iter);
1741 	path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &tree_iter);
1742 	gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model), path, &tree_iter, map_new_old);
1743 	gtk_tree_path_free (path);
1744 
1745 	g_free (map_new_old);
1746 	g_free (entries);
1747 }
1748 
1749 /**
1750  * rhythmdb_query_model_move_entry:
1751  * @model: a #RhythmDBQueryModel
1752  * @entry: the #RhythmDBEntry to move
1753  * @index: position to move to
1754  *
1755  * Moves an entry to a new position in the query model.
1756  * The position must be a between 0 and the number of entries in the model.
1757  * Specifying -1 does not move the entry to the end of the model.
1758  */
1759 void
rhythmdb_query_model_move_entry(RhythmDBQueryModel * model,RhythmDBEntry * entry,gint index)1760 rhythmdb_query_model_move_entry (RhythmDBQueryModel *model,
1761 				 RhythmDBEntry *entry,
1762 				 gint index)
1763 {
1764 	GSequenceIter *ptr;
1765 	GSequenceIter *nptr;
1766 	gint old_pos;
1767 
1768 	ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
1769 	if (ptr == NULL)
1770 		return;
1771 
1772 	nptr = g_sequence_get_iter_at_pos (model->priv->entries, index);
1773 	if ((nptr == NULL) || (ptr == nptr))
1774 		return;
1775 
1776 	/* take temporary ref */
1777 	rhythmdb_entry_ref (entry);
1778 
1779 	/* remove from old position */
1780 	old_pos = g_sequence_iter_get_position (ptr);
1781 	g_sequence_remove (ptr);
1782 	g_hash_table_remove (model->priv->reverse_map, entry);
1783 
1784 	/* insert into new position */
1785 	g_sequence_insert_before (nptr, entry);
1786 	ptr = g_sequence_iter_prev (nptr);
1787 
1788 	/* the hash now owns this reference to the entry */
1789 	g_hash_table_insert (model->priv->reverse_map, entry, ptr);
1790 
1791 	rhythmdb_query_model_emit_reorder (model, old_pos, index);
1792 }
1793 
1794 /**
1795  * rhythmdb_query_model_remove_entry:
1796  * @model: a #RhythmDBQueryModel
1797  * @entry: the #RhythmDBEntry to remove
1798  *
1799  * Removes an entry from the query model.
1800  *
1801  * Return value: %TRUE if the entry was removed
1802  */
1803 gboolean
rhythmdb_query_model_remove_entry(RhythmDBQueryModel * model,RhythmDBEntry * entry)1804 rhythmdb_query_model_remove_entry (RhythmDBQueryModel *model,
1805 				   RhythmDBEntry *entry)
1806 {
1807 	gboolean present = (g_hash_table_lookup (model->priv->reverse_map, entry) == NULL) ||
1808 			    (g_hash_table_lookup (model->priv->limited_reverse_map, entry) == NULL);
1809 	g_return_val_if_fail (present, FALSE);
1810 
1811 	if (model->priv->base_model != NULL)
1812 		return rhythmdb_query_model_remove_entry (model->priv->base_model, entry);
1813 
1814 	/* emit entry-removed, so listeners know the
1815 	 * entry has actually been removed, rather than filtered
1816 	 * out.
1817 	 */
1818 	g_signal_emit (G_OBJECT (model),
1819 		       rhythmdb_query_model_signals[ENTRY_REMOVED], 0,
1820 		       entry);
1821 	rhythmdb_query_model_filter_out_entry (model, entry);
1822 
1823 	return TRUE;
1824 }
1825 
1826 /**
1827  * rhythmdb_query_model_entry_to_iter:
1828  * @model: a #RhythmDBQueryModel
1829  * @entry: the #RhythmDBEntry to look up
1830  * @iter: holds the returned #GtkTreeIter
1831  *
1832  * Creates a #GtkTreeIter pointing to the specified entry in the model.
1833  *
1834  * Return value: %TRUE if the iterator now points to the entry
1835  */
1836 gboolean
rhythmdb_query_model_entry_to_iter(RhythmDBQueryModel * model,RhythmDBEntry * entry,GtkTreeIter * iter)1837 rhythmdb_query_model_entry_to_iter (RhythmDBQueryModel *model,
1838 				    RhythmDBEntry *entry,
1839 				    GtkTreeIter *iter)
1840 {
1841 	GSequenceIter *ptr;
1842 
1843 	ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
1844 
1845 	if (G_UNLIKELY (ptr == NULL)) {
1846 		/* Invalidate iterator so future uses break quickly. */
1847 		iter->stamp = !(model->priv->stamp);
1848 		return FALSE;
1849 	}
1850 
1851 	iter->stamp = model->priv->stamp;
1852 	iter->user_data = ptr;
1853 	return TRUE;
1854 }
1855 
1856 /**
1857  * rhythmdb_query_model_tree_path_to_entry:
1858  * @model: a #RhythmDBQueryModel
1859  * @path: a #GtkTreePath
1860  *
1861  * Locates the #RhythmDBEntry identified by the specified path in the model.
1862  * The caller owns a reference to the returned entry.
1863  *
1864  * Return value: the #RhythmDBEntry, if any
1865  */
1866 RhythmDBEntry *
rhythmdb_query_model_tree_path_to_entry(RhythmDBQueryModel * model,GtkTreePath * path)1867 rhythmdb_query_model_tree_path_to_entry (RhythmDBQueryModel *model,
1868 					 GtkTreePath *path)
1869 {
1870 	GtkTreeIter entry_iter;
1871 
1872 	g_assert (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &entry_iter, path));
1873 	return rhythmdb_query_model_iter_to_entry (model, &entry_iter);
1874 }
1875 
1876 /**
1877  * rhythmdb_query_model_iter_to_entry:
1878  * @model: a #RhythmDBQueryModel
1879  * @entry_iter: a #GtkTreeIter to dereference
1880  *
1881  * Locates and returns the #RhythmDBEntry pointed to by the specified iterator
1882  * in the model.  The caller owns a reference to the returned entry.
1883  *
1884  * Return value: the #RhythmDBEntry, if any
1885  */
1886 RhythmDBEntry *
rhythmdb_query_model_iter_to_entry(RhythmDBQueryModel * model,GtkTreeIter * entry_iter)1887 rhythmdb_query_model_iter_to_entry (RhythmDBQueryModel *model,
1888 				    GtkTreeIter *entry_iter)
1889 {
1890 	RhythmDBEntry *entry;
1891 	gtk_tree_model_get (GTK_TREE_MODEL (model), entry_iter, 0, &entry, -1);
1892 	return entry;
1893 }
1894 
1895 /**
1896  * rhythmdb_query_model_get_next_from_entry:
1897  * @model: a #RhythmDBQueryModel
1898  * @entry: a #RhythmDBEntry
1899  *
1900  * Locates and returns the next #RhythmDBEntry in the model after the specified
1901  * entry.  The caller owns a reference to the returned entry.
1902  *
1903  * Return value: the next #RhythmDBEntry in the model, if any
1904  */
1905 RhythmDBEntry *
rhythmdb_query_model_get_next_from_entry(RhythmDBQueryModel * model,RhythmDBEntry * entry)1906 rhythmdb_query_model_get_next_from_entry (RhythmDBQueryModel *model,
1907 					  RhythmDBEntry *entry)
1908 {
1909 	GtkTreeIter iter;
1910 
1911 	g_return_val_if_fail (entry != NULL, NULL);
1912 
1913 	if (entry && rhythmdb_query_model_entry_to_iter (model, entry, &iter)) {
1914 		if (!gtk_tree_model_iter_next (GTK_TREE_MODEL (model), &iter))
1915 			return NULL;
1916 	} else {
1917 		/* If the entry isn't in the model, the "next" entry is the first. */
1918 		if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter))
1919 			return NULL;
1920 	}
1921 
1922 	return rhythmdb_query_model_iter_to_entry (model, &iter);
1923 }
1924 
1925 /**
1926  * rhythmdb_query_model_get_previous_from_entry:
1927  * @model: a #RhythmDBQueryModel
1928  * @entry: a #RhythmDBEntry
1929  *
1930  * Locates and returns the  #RhythmDBEntry in the model before the specified
1931  * entry.  The caller owns a reference to the returned entry.
1932  *
1933  * Return value: the previous #RhythmDBEntry in the model, if any
1934  */
1935 RhythmDBEntry *
rhythmdb_query_model_get_previous_from_entry(RhythmDBQueryModel * model,RhythmDBEntry * entry)1936 rhythmdb_query_model_get_previous_from_entry (RhythmDBQueryModel *model,
1937 					      RhythmDBEntry *entry)
1938 {
1939 	GtkTreeIter iter;
1940 	GtkTreePath *path;
1941 
1942 	g_return_val_if_fail (entry != NULL, NULL);
1943 
1944 	if (!rhythmdb_query_model_entry_to_iter (model, entry, &iter))
1945 		return NULL;
1946 
1947 	path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
1948 	g_assert (path);
1949 	if (!gtk_tree_path_prev (path)) {
1950 		gtk_tree_path_free (path);
1951 		return NULL;
1952 	}
1953 
1954 	g_assert (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path));
1955 	gtk_tree_path_free (path);
1956 
1957 	return rhythmdb_query_model_iter_to_entry (model, &iter);
1958 }
1959 
1960 static gboolean
rhythmdb_query_model_row_draggable(RbTreeDragSource * dragsource,GList * paths)1961 rhythmdb_query_model_row_draggable (RbTreeDragSource *dragsource,
1962 				    GList *paths)
1963 {
1964 	return TRUE;
1965 }
1966 
1967 static gboolean
rhythmdb_query_model_drag_data_delete(RbTreeDragSource * dragsource,GList * paths)1968 rhythmdb_query_model_drag_data_delete (RbTreeDragSource *dragsource,
1969 				       GList *paths)
1970 {
1971 	RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (dragsource);
1972 	GtkTreeModel *treemodel = GTK_TREE_MODEL (model);
1973 
1974 	/* we don't delete if it is a reorder drag and drop because the deletion already
1975 	   occured in rhythmdb_query_model_drag_data_received */
1976 	if (model->priv->sort_func == NULL && !model->priv->reorder_drag_and_drop) {
1977 
1978 		RhythmDBEntry *entry;
1979 		GtkTreeIter iter;
1980 		GtkTreePath *path;
1981 
1982 		for (; paths; paths = paths->next) {
1983 
1984 			path = gtk_tree_row_reference_get_path (paths->data);
1985 
1986 			if (path) {
1987 				if (rhythmdb_query_model_get_iter (treemodel, &iter, path)) {
1988 					entry = g_sequence_get (iter.user_data);
1989 					rhythmdb_query_model_remove_entry (model, entry);
1990 				}
1991 				gtk_tree_path_free (path);
1992 			}
1993 		}
1994 	}
1995 
1996 	model->priv->reorder_drag_and_drop = FALSE;
1997 	return TRUE;
1998 
1999 }
2000 
2001 static gboolean
rhythmdb_query_model_drag_data_get(RbTreeDragSource * dragsource,GList * paths,GtkSelectionData * selection_data)2002 rhythmdb_query_model_drag_data_get (RbTreeDragSource *dragsource,
2003 				    GList *paths,
2004 				    GtkSelectionData *selection_data)
2005 {
2006 	RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (dragsource);
2007 	RhythmDBEntry *entry;
2008 	GdkAtom selection_data_target;
2009 	GString *data;
2010 	guint target;
2011 	GList *tem;
2012 	gboolean need_newline = FALSE;
2013 
2014 	rb_debug ("getting drag data");
2015 
2016 	selection_data_target = gtk_selection_data_get_target (selection_data);
2017 	if (!gtk_target_list_find (rhythmdb_query_model_drag_target_list,
2018 				   selection_data_target, &target)) {
2019 		return FALSE;
2020 	}
2021 
2022 
2023 	data = g_string_new ("");
2024 
2025 	for (tem = paths; tem; tem = tem->next) {
2026 		GtkTreeIter iter;
2027 		GtkTreePath *path;
2028 
2029 		path = gtk_tree_row_reference_get_path (tem->data);
2030 
2031 		gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path);
2032 
2033 		entry = g_sequence_get (iter.user_data);
2034 
2035 		if (need_newline)
2036 			g_string_append (data, "\r\n");
2037 
2038 		if (target == TARGET_URIS) {
2039 			char *location;
2040 
2041 			location = rhythmdb_entry_get_playback_uri (entry);
2042 			if (location == NULL) {
2043 				need_newline = FALSE;
2044 				continue;
2045 			}
2046 
2047 			g_string_append (data, location);
2048 			g_free (location);
2049 		} else if (target == TARGET_ENTRIES) {
2050 			g_string_append_printf (data,
2051 						"%lu",
2052 						rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_ENTRY_ID));
2053 		}
2054 		need_newline = TRUE;
2055 	}
2056 
2057 	gtk_selection_data_set (selection_data,
2058 				selection_data_target,
2059 				8, (guchar *) data->str,
2060 				data->len);
2061 
2062 	g_string_free (data, TRUE);
2063 
2064 	return TRUE;
2065 }
2066 
2067 static gboolean
rhythmdb_query_model_drag_data_received(RbTreeDragDest * drag_dest,GtkTreePath * dest,GtkTreeViewDropPosition pos,GtkSelectionData * selection_data)2068 rhythmdb_query_model_drag_data_received (RbTreeDragDest *drag_dest,
2069 					 GtkTreePath *dest,
2070 					 GtkTreeViewDropPosition pos,
2071 					 GtkSelectionData *selection_data)
2072 {
2073 	RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (drag_dest);
2074 
2075 	if (model->priv->base_model) {
2076 		GtkTreeIter base_iter;
2077 		GtkTreePath *base_dest;
2078 		RhythmDBEntry *entry;
2079 		gboolean result;
2080 
2081 		if (dest) {
2082 			entry = rhythmdb_query_model_tree_path_to_entry (model, dest);
2083 			g_assert (entry);
2084 			rhythmdb_query_model_entry_to_iter (model->priv->base_model, entry, &base_iter);
2085 			base_dest = gtk_tree_model_get_path (GTK_TREE_MODEL (model->priv->base_model), &base_iter);
2086 			rhythmdb_entry_unref (entry);
2087 		} else {
2088 			base_dest = NULL;
2089 		}
2090 
2091 		result = rhythmdb_query_model_drag_data_received ((RbTreeDragDest*)model->priv->base_model,
2092 								  base_dest, pos, selection_data);
2093 		if (base_dest)
2094 			gtk_tree_path_free (base_dest);
2095 
2096 		return result;
2097 	}
2098 
2099 	rb_debug ("drag received");
2100 
2101 	if (model->priv->sort_func != NULL)
2102 		return FALSE;
2103 
2104 	if ((gtk_selection_data_get_format (selection_data) == 8) &&
2105 	    (gtk_selection_data_get_length (selection_data) >= 0)) {
2106 		GtkTreeIter iter;
2107 		GSequenceIter *ptr;
2108 		char **strv;
2109 		RhythmDBEntry *entry;
2110 		gboolean uri_list;
2111 		int i = 0;
2112 
2113 		uri_list = (gtk_selection_data_get_data_type (selection_data) == gdk_atom_intern ("text/uri-list", TRUE));
2114 
2115 		strv = g_strsplit ((char *) gtk_selection_data_get_data (selection_data), "\r\n", -1);
2116 
2117 		if (dest == NULL || !rhythmdb_query_model_get_iter (GTK_TREE_MODEL (model), &iter, dest))
2118 			ptr = g_sequence_get_end_iter (model->priv->entries);
2119 		else
2120 			ptr = iter.user_data;
2121 
2122 		if (pos == GTK_TREE_VIEW_DROP_AFTER)
2123 			ptr = g_sequence_iter_next (ptr);
2124 
2125 		for (; strv[i]; i++) {
2126 			GSequenceIter *tem_ptr;
2127 			GtkTreeIter tem_iter;
2128 
2129 			if (g_utf8_strlen (strv[i], -1) == 0)
2130 				continue;
2131 
2132 			entry = rhythmdb_entry_lookup_from_string (model->priv->db, strv[i], !uri_list);
2133 			if (entry == NULL) {
2134 				int pos;
2135 
2136 				if (uri_list) {
2137 					if (g_sequence_iter_is_end (ptr))
2138 						pos = -1;
2139 					else
2140 						pos = g_sequence_iter_get_position (ptr);
2141 
2142 					g_signal_emit (G_OBJECT (model),
2143 						       rhythmdb_query_model_signals[NON_ENTRY_DROPPED],
2144 						       0, strv[i], pos);
2145 				} else {
2146 					rb_debug ("got drop with entry id %s, but can't find the entry", strv[i]);
2147 				}
2148 			} else {
2149 				GSequenceIter *old_ptr;
2150 				GtkTreePath *tem_path;
2151 				gint old_pos = 0;
2152 				gint new_pos;
2153 
2154 				old_ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
2155 				/* trying to drag drop an entry on itself ! */
2156 				if (old_ptr == ptr) {
2157 					continue;
2158 				} else if (old_ptr == NULL) {
2159 					gboolean allow;
2160 
2161 					g_signal_emit (G_OBJECT (model),
2162 						       rhythmdb_query_model_signals[FILTER_ENTRY_DROP],
2163 						       0, entry, &allow);
2164 					if (allow == FALSE) {
2165 						rb_debug ("dropping of entry %s disallowed by filter",
2166 							  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
2167 						continue;
2168 					}
2169 				}
2170 
2171 				/* take temporary ref */
2172 				rhythmdb_entry_ref (entry);
2173 
2174 				/* the entry already exists it is either a reorder drag and drop
2175 				   (or a drag and drop from another application), so we delete
2176 				   the existing one before adding it again. */
2177 				if (old_ptr) {
2178 					model->priv->reorder_drag_and_drop = TRUE;
2179 
2180 					old_pos = g_sequence_iter_get_position (old_ptr);
2181 					g_sequence_remove (old_ptr);
2182 					g_assert (g_hash_table_remove (model->priv->reverse_map, entry));
2183 				} else {
2184 					model->priv->reorder_drag_and_drop = FALSE;
2185 				}
2186 
2187 				g_sequence_insert_before (ptr, entry);
2188 
2189 				tem_ptr = g_sequence_iter_prev (ptr);
2190 				new_pos = g_sequence_iter_get_position (tem_ptr);
2191 
2192 				tem_iter.stamp = model->priv->stamp;
2193 				tem_iter.user_data = tem_ptr;
2194 				/* the hash now owns this reference to the entry */
2195 				g_hash_table_insert (model->priv->reverse_map, entry, tem_ptr);
2196 
2197 				if (old_ptr) {
2198 					rb_debug ("moving entry %p from %d to %d", entry, old_pos, new_pos);
2199 					rhythmdb_query_model_emit_reorder (model, old_pos, new_pos);
2200 				} else {
2201 					tem_path = rhythmdb_query_model_get_path (GTK_TREE_MODEL (model),
2202 										  &tem_iter);
2203 					gtk_tree_model_row_inserted (GTK_TREE_MODEL (model),
2204 								     tem_path, &tem_iter);
2205 					gtk_tree_path_free (tem_path);
2206 				}
2207 			}
2208 		}
2209 
2210 		g_strfreev (strv);
2211 		return TRUE;
2212 	}
2213 	return FALSE;
2214 }
2215 
2216 /*
2217  * determines whether reordering is possible by checking up
2218  * the chain for a model with a sort function set.
2219  */
2220 static gboolean
query_model_chain_can_reorder(RhythmDBQueryModel * model)2221 query_model_chain_can_reorder (RhythmDBQueryModel *model)
2222 {
2223 	while (model) {
2224 		if (model->priv->sort_func != NULL)
2225 			return FALSE;
2226 
2227 		model = model->priv->base_model;
2228 	}
2229 	return TRUE;
2230 }
2231 
2232 static gboolean
rhythmdb_query_model_row_drop_possible(RbTreeDragDest * drag_dest,GtkTreePath * dest,GtkTreeViewDropPosition pos,GtkSelectionData * selection_data)2233 rhythmdb_query_model_row_drop_possible (RbTreeDragDest *drag_dest,
2234 					GtkTreePath *dest,
2235 					GtkTreeViewDropPosition pos,
2236 					GtkSelectionData *selection_data)
2237 {
2238 	RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (drag_dest);
2239 	return query_model_chain_can_reorder (model);
2240 }
2241 
2242 static gboolean
rhythmdb_query_model_row_drop_position(RbTreeDragDest * drag_dest,GtkTreePath * dest_path,GList * targets,GtkTreeViewDropPosition * pos)2243 rhythmdb_query_model_row_drop_position (RbTreeDragDest *drag_dest,
2244 					GtkTreePath *dest_path,
2245 					GList *targets,
2246 					GtkTreeViewDropPosition *pos)
2247 {
2248 	RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (drag_dest);
2249 	return query_model_chain_can_reorder (model);
2250 }
2251 
2252 static void
rhythmdb_query_model_set_query(RhythmDBQueryResults * results,GPtrArray * query)2253 rhythmdb_query_model_set_query (RhythmDBQueryResults *results, GPtrArray *query)
2254 {
2255 	g_object_set (G_OBJECT (results), "query", query, NULL);
2256 }
2257 
2258 /* Threading: Called from the database query thread for async queries,
2259  *  from the main thread for synchronous queries.
2260  */
2261 static void
rhythmdb_query_model_add_results(RhythmDBQueryResults * results,GPtrArray * entries)2262 rhythmdb_query_model_add_results (RhythmDBQueryResults *results,
2263 				  GPtrArray *entries)
2264 {
2265 	RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (results);
2266 	struct RhythmDBQueryModelUpdate *update;
2267 	guint i;
2268 
2269 	rb_debug ("adding %d entries", entries->len);
2270 
2271 	update = g_new (struct RhythmDBQueryModelUpdate, 1);
2272 	update->type = RHYTHMDB_QUERY_MODEL_UPDATE_ROWS_INSERTED;
2273 	update->entrydata.entries = entries;
2274 	update->model = model;
2275 
2276 	/* take references; released in update idle */
2277 	g_object_ref (model);
2278 
2279 	for (i = 0; i < update->entrydata.entries->len; i++) {
2280 		rhythmdb_entry_ref (g_ptr_array_index (update->entrydata.entries, i));
2281 	}
2282 
2283 	rhythmdb_query_model_process_update (update);
2284 }
2285 
2286 static void
rhythmdb_query_model_query_complete(RhythmDBQueryResults * results)2287 rhythmdb_query_model_query_complete (RhythmDBQueryResults *results)
2288 {
2289 	RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (results);
2290 	struct RhythmDBQueryModelUpdate *update;
2291 
2292 	update = g_new0 (struct RhythmDBQueryModelUpdate, 1);
2293 	update->type = RHYTHMDB_QUERY_MODEL_UPDATE_QUERY_COMPLETE;
2294 	update->model = model;
2295 
2296 	/* take reference; released in update idle */
2297 	g_object_ref (model);
2298 
2299 	rhythmdb_query_model_process_update (update);
2300 }
2301 
2302 static GtkTreeModelFlags
rhythmdb_query_model_get_flags(GtkTreeModel * model)2303 rhythmdb_query_model_get_flags (GtkTreeModel *model)
2304 {
2305 	return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY;
2306 }
2307 
2308 static gint
rhythmdb_query_model_get_n_columns(GtkTreeModel * tree_model)2309 rhythmdb_query_model_get_n_columns (GtkTreeModel *tree_model)
2310 {
2311 	return 2;
2312 }
2313 
2314 static GType
rhythmdb_query_model_get_column_type(GtkTreeModel * tree_model,int index)2315 rhythmdb_query_model_get_column_type (GtkTreeModel *tree_model,
2316 				      int index)
2317 {
2318 	switch (index) {
2319 	case 0:
2320 		return RHYTHMDB_TYPE_ENTRY;
2321 	case 1:
2322 		return G_TYPE_INT;
2323 	default:
2324 		g_assert_not_reached ();
2325 		return G_TYPE_INVALID;
2326 	}
2327 }
2328 
2329 static gboolean
rhythmdb_query_model_get_iter(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreePath * path)2330 rhythmdb_query_model_get_iter (GtkTreeModel *tree_model,
2331 			       GtkTreeIter *iter,
2332 			       GtkTreePath *path)
2333 {
2334 	RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (tree_model);
2335 	guint index;
2336 	GSequenceIter *ptr;
2337 
2338 	index = gtk_tree_path_get_indices (path)[0];
2339 
2340 	if (index >= g_sequence_get_length (model->priv->entries))
2341 		return FALSE;
2342 
2343 	ptr = g_sequence_get_iter_at_pos (model->priv->entries, index);
2344 	g_assert (ptr);
2345 
2346 	iter->stamp = model->priv->stamp;
2347 	iter->user_data = ptr;
2348 
2349 	return TRUE;
2350 }
2351 
2352 static GtkTreePath *
rhythmdb_query_model_get_path(GtkTreeModel * tree_model,GtkTreeIter * iter)2353 rhythmdb_query_model_get_path (GtkTreeModel *tree_model,
2354 			       GtkTreeIter  *iter)
2355 {
2356 	RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (tree_model);
2357 	GtkTreePath *path;
2358 
2359 	g_return_val_if_fail (iter->stamp == model->priv->stamp, NULL);
2360 
2361 	if (g_sequence_iter_is_end (iter->user_data))
2362 		return NULL;
2363 
2364 	path = gtk_tree_path_new ();
2365 	gtk_tree_path_append_index (path, g_sequence_iter_get_position (iter->user_data));
2366 	return path;
2367 }
2368 
2369 static void
rhythmdb_query_model_get_value(GtkTreeModel * tree_model,GtkTreeIter * iter,gint column,GValue * value)2370 rhythmdb_query_model_get_value (GtkTreeModel *tree_model,
2371 				GtkTreeIter *iter,
2372 				gint column,
2373 				GValue *value)
2374 {
2375 	RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (tree_model);
2376 	RhythmDBEntry *entry;
2377 
2378 	/* this is done internally by eggsequence anyway
2379 	g_return_if_fail (!g_sequence_iter_is_end (iter->user_data));*/
2380 	g_return_if_fail (model->priv->stamp == iter->stamp);
2381 
2382 	entry = g_sequence_get (iter->user_data);
2383 
2384 	switch (column) {
2385 	case 0:
2386 		g_value_init (value, RHYTHMDB_TYPE_ENTRY);
2387 		g_value_set_boxed (value, entry);
2388 		break;
2389 	case 1:
2390 		g_value_init (value, G_TYPE_INT);
2391 		g_value_set_int (value, g_sequence_iter_get_position (iter->user_data)+1);
2392 		break;
2393 	default:
2394 		g_assert_not_reached ();
2395 	}
2396 }
2397 
2398 static gboolean
rhythmdb_query_model_iter_next(GtkTreeModel * tree_model,GtkTreeIter * iter)2399 rhythmdb_query_model_iter_next (GtkTreeModel  *tree_model,
2400 				GtkTreeIter   *iter)
2401 {
2402 	RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (tree_model);
2403 
2404 	g_return_val_if_fail (iter->stamp == model->priv->stamp, FALSE);
2405 
2406 	iter->user_data = g_sequence_iter_next (iter->user_data);
2407 
2408 	return !g_sequence_iter_is_end (iter->user_data);
2409 }
2410 
2411 static gboolean
rhythmdb_query_model_iter_children(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreeIter * parent)2412 rhythmdb_query_model_iter_children (GtkTreeModel *tree_model,
2413 				    GtkTreeIter  *iter,
2414 				    GtkTreeIter  *parent)
2415 {
2416 	RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (tree_model);
2417 
2418 	if (parent != NULL)
2419 		return FALSE;
2420 
2421 	if (g_sequence_get_length (model->priv->entries) == 0)
2422 		return FALSE;
2423 
2424 	iter->stamp = model->priv->stamp;
2425 	iter->user_data = g_sequence_get_begin_iter (model->priv->entries);
2426 
2427 	return TRUE;
2428 }
2429 
2430 static gboolean
rhythmdb_query_model_iter_has_child(GtkTreeModel * tree_model,GtkTreeIter * iter)2431 rhythmdb_query_model_iter_has_child (GtkTreeModel *tree_model,
2432 				     GtkTreeIter  *iter)
2433 {
2434 	return FALSE;
2435 }
2436 
2437 static gint
rhythmdb_query_model_iter_n_children(GtkTreeModel * tree_model,GtkTreeIter * iter)2438 rhythmdb_query_model_iter_n_children (GtkTreeModel *tree_model,
2439 				      GtkTreeIter  *iter)
2440 {
2441 	RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (tree_model);
2442 
2443 	if (iter == NULL)
2444 		return g_sequence_get_length (model->priv->entries);
2445 
2446 	g_return_val_if_fail (model->priv->stamp == iter->stamp, -1);
2447 
2448 	return 0;
2449 }
2450 
2451 static gboolean
rhythmdb_query_model_iter_nth_child(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreeIter * parent,gint n)2452 rhythmdb_query_model_iter_nth_child (GtkTreeModel *tree_model,
2453 				     GtkTreeIter *iter,
2454 				     GtkTreeIter *parent,
2455 				     gint n)
2456 {
2457 	RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (tree_model);
2458 	GSequenceIter *child;
2459 
2460 	if (parent)
2461 		return FALSE;
2462 
2463 	child = g_sequence_get_iter_at_pos (model->priv->entries, n);
2464 
2465 	if (g_sequence_iter_is_end (child))
2466 		return FALSE;
2467 
2468 	iter->stamp = model->priv->stamp;
2469 	iter->user_data = child;
2470 
2471 	return TRUE;
2472 }
2473 
2474 static gboolean
rhythmdb_query_model_iter_parent(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreeIter * child)2475 rhythmdb_query_model_iter_parent (GtkTreeModel *tree_model,
2476 				  GtkTreeIter  *iter,
2477 				  GtkTreeIter  *child)
2478 {
2479 	return FALSE;
2480 }
2481 
2482 /**
2483  * rhythmdb_query_model_compute_status_normal:
2484  * @model: a #RhythmDBQueryModel
2485  * @singular: singular form of the pattern describing the number of entries ("%d song")
2486  * @plural: plural form of the pattern describing the number of entries ("%d songs")
2487  *
2488  * Constructs a status string describing the contents of the model.
2489  *
2490  * Return value: allocated status string, to be freed by the caller.
2491  */
2492 char *
rhythmdb_query_model_compute_status_normal(RhythmDBQueryModel * model,const char * singular,const char * plural)2493 rhythmdb_query_model_compute_status_normal (RhythmDBQueryModel *model,
2494 					    const char *singular,
2495 					    const char *plural)
2496 {
2497 	return rhythmdb_compute_status_normal (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model), NULL),
2498 					       rhythmdb_query_model_get_duration (model),
2499 					       rhythmdb_query_model_get_size (model),
2500 					       singular,
2501 					       plural);
2502 }
2503 
2504 static void
apply_updated_entry_sequence(RhythmDBQueryModel * model,GSequence * new_entries)2505 apply_updated_entry_sequence (RhythmDBQueryModel *model,
2506 			      GSequence *new_entries)
2507 {
2508 	int *reorder_map;
2509 	int length, i;
2510 	GtkTreePath *path;
2511 	GtkTreeIter iter;
2512 	GSequenceIter *ptr;
2513 
2514 	length = g_sequence_get_length (new_entries);
2515 	/* generate resort map and rebuild reverse map */
2516 	reorder_map = g_malloc (length * sizeof(gint));
2517 
2518 	ptr = g_sequence_get_begin_iter (new_entries);
2519 	for (i = 0; i < length; i++) {
2520 		gpointer entry = g_sequence_get (ptr);
2521 		GSequenceIter *old_ptr;
2522 
2523 		old_ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
2524 		reorder_map[i] = g_sequence_iter_get_position (old_ptr);
2525 		g_hash_table_replace (model->priv->reverse_map, rhythmdb_entry_ref (entry), ptr);
2526 
2527 		ptr = g_sequence_iter_next (ptr);
2528 	}
2529 	g_sequence_free (model->priv->entries);
2530 	model->priv->entries = new_entries;
2531 
2532 	/* emit the re-order and clean up */
2533 	gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter);
2534 	path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
2535 	gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model),
2536 				       path, &iter,
2537 				       reorder_map);
2538 
2539 	gtk_tree_path_free (path);
2540 	g_free (reorder_map);
2541 }
2542 
2543 /**
2544  * rhythmdb_query_model_set_sort_order:
2545  * @model: a #RhythmDBQueryModel
2546  * @sort_func: new sort function
2547  * @sort_data: data to pass to the new sort function
2548  * @sort_data_destroy: function to call to free the sort data
2549  * @sort_reverse: if %TRUE, reverse the sort order
2550  *
2551  * Sets a new sort order on the model.  This reorders the entries
2552  * in the model to match the new sort order.
2553  */
2554 void
rhythmdb_query_model_set_sort_order(RhythmDBQueryModel * model,GCompareDataFunc sort_func,gpointer sort_data,GDestroyNotify sort_data_destroy,gboolean sort_reverse)2555 rhythmdb_query_model_set_sort_order (RhythmDBQueryModel *model,
2556 				     GCompareDataFunc sort_func,
2557 				     gpointer sort_data,
2558 				     GDestroyNotify sort_data_destroy,
2559 				     gboolean sort_reverse)
2560 {
2561 	GSequence *new_entries;
2562 	GSequenceIter *ptr;
2563 	int length, i;
2564 	struct ReverseSortData reverse_data;
2565 
2566 	if ((model->priv->sort_func == sort_func) &&
2567 	    (model->priv->sort_data == sort_data) &&
2568 	    (model->priv->sort_data_destroy == sort_data_destroy) &&
2569 	    (model->priv->sort_reverse == sort_reverse))
2570 		return;
2571 
2572 	g_return_if_fail ((model->priv->limit_type == RHYTHMDB_QUERY_MODEL_LIMIT_NONE) ||
2573 			  (model->priv->sort_func == NULL));
2574 	if (model->priv->sort_func == NULL)
2575 		g_assert (g_sequence_get_length (model->priv->limited_entries) == 0);
2576 
2577 	if (model->priv->sort_data_destroy && model->priv->sort_data)
2578 		model->priv->sort_data_destroy (model->priv->sort_data);
2579 
2580 	model->priv->sort_func = sort_func;
2581 	model->priv->sort_data = sort_data;
2582 	model->priv->sort_data_destroy = sort_data_destroy;
2583 	model->priv->sort_reverse = sort_reverse;
2584 
2585 	if (model->priv->sort_reverse) {
2586 		reverse_data.func = sort_func;
2587 		reverse_data.data = sort_data;
2588 		sort_func = (GCompareDataFunc) _reverse_sorting_func;
2589 		sort_data = &reverse_data;
2590 	}
2591 
2592 	/* create the new sorted entry sequence */
2593 	length = g_sequence_get_length (model->priv->entries);
2594 	if (length > 0) {
2595 		new_entries = g_sequence_new (NULL);
2596 		ptr = g_sequence_get_begin_iter (model->priv->entries);
2597 		for (i = 0; i < length; i++) {
2598 			gpointer entry = g_sequence_get (ptr);
2599 
2600 			g_sequence_insert_sorted (new_entries, entry,
2601 						  sort_func,
2602 						  sort_data);
2603 			ptr = g_sequence_iter_next (ptr);
2604 		}
2605 
2606 		apply_updated_entry_sequence (model, new_entries);
2607 	}
2608 }
2609 
2610 static int
rhythmdb_query_model_child_index_to_base_index(RhythmDBQueryModel * model,int index)2611 rhythmdb_query_model_child_index_to_base_index (RhythmDBQueryModel *model,
2612 						int index)
2613 {
2614 	GSequenceIter *ptr;
2615 	RhythmDBEntry *entry;
2616 	g_assert (model->priv->base_model);
2617 
2618 	ptr = g_sequence_get_iter_at_pos (model->priv->entries, index);
2619 	if (ptr == NULL || g_sequence_iter_is_end (ptr))
2620 		return -1;
2621 	entry = (RhythmDBEntry*)g_sequence_get (ptr);
2622 
2623 	ptr = g_hash_table_lookup (model->priv->base_model->priv->reverse_map, entry);
2624 	g_assert (ptr); /* all child model entries are in the base model */
2625 
2626 	return g_sequence_iter_get_position (ptr);
2627 }
2628 
2629 /*static int
2630 rhythmdb_query_model_base_index_to_child_index (RhythmDBQueryModel *model, int index)
2631 {
2632 	GSequenceIter *ptr;
2633 	RhythmDBEntry *entry;
2634 	int pos;
2635 
2636 	g_assert (model->priv->base_model);
2637 	if (index == -1)
2638 		return -1;
2639 
2640 	ptr = g_sequence_get_iter_at_pos (model->priv->base_model->priv->entries, index);
2641 	if (ptr == NULL || g_sequence_iter_is_end (ptr))
2642 		return -1;
2643 	entry = (RhythmDBEntry*)g_sequence_get (ptr);
2644 
2645 	ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
2646 	if (ptr == NULL)
2647 		return -1;
2648 
2649 	pos = g_sequence_iter_get_position (ptr);
2650 	return pos;
2651 }*/
2652 
2653 static int
rhythmdb_query_model_get_entry_index(RhythmDBQueryModel * model,RhythmDBEntry * entry)2654 rhythmdb_query_model_get_entry_index (RhythmDBQueryModel *model,
2655 				      RhythmDBEntry *entry)
2656 {
2657 	GSequenceIter *ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
2658 
2659 	if (ptr)
2660 		return g_sequence_iter_get_position (ptr);
2661 	else
2662 		return -1;
2663 }
2664 
2665 static void
rhythmdb_query_model_base_row_inserted(GtkTreeModel * tree_model,GtkTreePath * path,GtkTreeIter * iter,RhythmDBQueryModel * model)2666 rhythmdb_query_model_base_row_inserted (GtkTreeModel *tree_model,
2667 					GtkTreePath *path,
2668 					GtkTreeIter *iter,
2669 					RhythmDBQueryModel *model)
2670 {
2671 	RhythmDBQueryModel *base_model = RHYTHMDB_QUERY_MODEL (tree_model);
2672 	RhythmDBEntry *entry;
2673 	RhythmDBEntry *prev_entry;
2674 	int index;
2675 
2676 	g_assert (base_model == model->priv->base_model);
2677 
2678 	entry = rhythmdb_query_model_iter_to_entry (base_model, iter);
2679 
2680 	if (!model->priv->show_hidden && rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN))
2681 		goto out;
2682 
2683 	if (rhythmdb_evaluate_query (model->priv->db, model->priv->query, entry)) {
2684 		/* find the closest previous entry that is in the filter model, and it it after that */
2685 		prev_entry = rhythmdb_query_model_get_previous_from_entry (base_model, entry);
2686 		while (prev_entry && g_hash_table_lookup (model->priv->reverse_map, prev_entry) == NULL) {
2687 			rhythmdb_entry_unref (prev_entry);
2688 			prev_entry = rhythmdb_query_model_get_previous_from_entry (base_model, prev_entry);
2689 		}
2690 
2691 		if (entry != NULL) {
2692 			index = rhythmdb_query_model_get_entry_index (model, prev_entry) + 1;
2693 		} else {
2694 			index = 0;
2695 		}
2696 
2697 		if (prev_entry != NULL) {
2698 			rhythmdb_entry_unref (prev_entry);
2699 		}
2700 
2701 		rb_debug ("inserting entry %p from base model %p to model %p in position %d", entry, base_model, model, index);
2702 		rhythmdb_query_model_do_insert (model, entry, index);
2703 	}
2704  out:
2705 	rhythmdb_entry_unref (entry);
2706 }
2707 
2708 static void
rhythmdb_query_model_base_row_deleted(GtkTreeModel * base_model,GtkTreePath * path,RhythmDBQueryModel * model)2709 rhythmdb_query_model_base_row_deleted (GtkTreeModel *base_model,
2710 				       GtkTreePath *path,
2711 				       RhythmDBQueryModel *model)
2712 {
2713 	RhythmDBEntry *entry;
2714 
2715 	entry = rhythmdb_query_model_tree_path_to_entry (RHYTHMDB_QUERY_MODEL (base_model), path);
2716 	rb_debug ("deleting entry %p from base model %p to model %p", entry, base_model, model);
2717 
2718 	rhythmdb_query_model_filter_out_entry (model, entry);
2719 	rhythmdb_entry_unref (entry);
2720 }
2721 
2722 static void
rhythmdb_query_model_base_non_entry_dropped(GtkTreeModel * base_model,const char * location,int position,RhythmDBQueryModel * model)2723 rhythmdb_query_model_base_non_entry_dropped (GtkTreeModel *base_model,
2724 					     const char *location,
2725 					     int position,
2726 					     RhythmDBQueryModel *model)
2727 {
2728 	g_signal_emit (G_OBJECT (model), rhythmdb_query_model_signals[NON_ENTRY_DROPPED], 0,
2729 		       location, rhythmdb_query_model_child_index_to_base_index (model, position));
2730 }
2731 
2732 static void
rhythmdb_query_model_base_complete(GtkTreeModel * base_model,RhythmDBQueryModel * model)2733 rhythmdb_query_model_base_complete (GtkTreeModel *base_model,
2734 				    RhythmDBQueryModel *model)
2735 {
2736 	g_signal_emit (G_OBJECT (model), rhythmdb_query_model_signals[COMPLETE], 0);
2737 }
2738 
2739 typedef struct {
2740 	RhythmDBQueryModel *model;
2741 	GSequence *new_entries;
2742 } _BaseRowsReorderedData;
2743 
2744 static void
_base_rows_reordered_foreach_cb(RhythmDBEntry * entry,_BaseRowsReorderedData * data)2745 _base_rows_reordered_foreach_cb (RhythmDBEntry *entry, _BaseRowsReorderedData *data)
2746 {
2747 	if (g_hash_table_lookup (data->model->priv->reverse_map, entry))
2748 		g_sequence_append (data->new_entries, entry);
2749 }
2750 
2751 static void
rhythmdb_query_model_base_rows_reordered(GtkTreeModel * base_model,GtkTreePath * arg1,GtkTreeIter * arg2,gint * order_map,RhythmDBQueryModel * model)2752 rhythmdb_query_model_base_rows_reordered (GtkTreeModel *base_model,
2753 					  GtkTreePath *arg1,
2754 					  GtkTreeIter *arg2,
2755 					  gint *order_map,
2756 					  RhythmDBQueryModel *model)
2757 {
2758 	RhythmDBQueryModel *base_query_model = RHYTHMDB_QUERY_MODEL (base_model);
2759 	_BaseRowsReorderedData data;
2760 
2761 	/* ignore, if this model sorts */
2762 	if (model->priv->sort_func)
2763 		return;
2764 
2765 	data.new_entries = g_sequence_new (NULL);
2766 	data.model = model;
2767 	g_sequence_foreach (base_query_model->priv->entries, (GFunc)_base_rows_reordered_foreach_cb, &data);
2768 	apply_updated_entry_sequence (model, data.new_entries);
2769 }
2770 
2771 static void
rhythmdb_query_model_base_entry_removed(RhythmDBQueryModel * base_model,RhythmDBEntry * entry,RhythmDBQueryModel * model)2772 rhythmdb_query_model_base_entry_removed (RhythmDBQueryModel *base_model,
2773 					 RhythmDBEntry *entry,
2774 					 RhythmDBQueryModel *model)
2775 {
2776 	if (g_hash_table_lookup (model->priv->reverse_map, entry)) {
2777 		/* propagate the signal out to any attached property models */
2778 		g_signal_emit (G_OBJECT (model),
2779 			       rhythmdb_query_model_signals[ENTRY_REMOVED], 0,
2780 			       entry);
2781 	}
2782 }
2783 
2784 typedef struct {
2785 	RhythmDBQueryModel *model;
2786 	GList *remove;
2787 } _ReapplyQueryForeachData;
2788 
2789 static void
_reapply_query_foreach_cb(RhythmDBEntry * entry,_ReapplyQueryForeachData * data)2790 _reapply_query_foreach_cb (RhythmDBEntry *entry, _ReapplyQueryForeachData *data)
2791 {
2792 	if (!rhythmdb_evaluate_query (data->model->priv->db,
2793 				      data->model->priv->query,
2794 				      entry)) {
2795 		data->remove = g_list_prepend (data->remove, entry);
2796 	}
2797 }
2798 
2799 /**
2800  * rhythmdb_query_model_reapply_query:
2801  * @model: a #RhythmDBQueryModel
2802  * @filter: if %FALSE, emit entry-removed signals
2803  *
2804  * Reapplies the existing query to the entries in the model.  This
2805  * is mostly useful when the query contains relative time criteria
2806  * (such as 'not played in the last hour').  This will only remove
2807  * entries that are already in the model, it will not find entries
2808  * that previously did not match the query.
2809  *
2810  * The 'filter' parameter should be set to TRUE when the query is
2811  * being used as a filter, rather than to define a base set of entries.
2812  */
2813 void
rhythmdb_query_model_reapply_query(RhythmDBQueryModel * model,gboolean filter)2814 rhythmdb_query_model_reapply_query (RhythmDBQueryModel *model,
2815 				    gboolean filter)
2816 {
2817 	gboolean changed = FALSE;
2818 	_ReapplyQueryForeachData data;
2819 	GList *t;
2820 
2821 	data.model = model;
2822 	data.remove = NULL;
2823 
2824 	/* process limited list first, so entries that don't match can't sneak in
2825 	 * to the main list from there
2826 	 */
2827 	if (model->priv->limited_entries)
2828 		g_sequence_foreach (model->priv->limited_entries, (GFunc)_reapply_query_foreach_cb, &data);
2829 
2830 	for (t = data.remove; t; t = t->next)
2831 		rhythmdb_query_model_remove_from_limited_list (model, (RhythmDBEntry*)t->data);
2832 
2833 	changed |= (data.remove != NULL);
2834 	g_list_free (data.remove);
2835 	data.remove = NULL;
2836 
2837 
2838 	if (model->priv->entries)
2839 		g_sequence_foreach (model->priv->entries, (GFunc)_reapply_query_foreach_cb, &data);
2840 
2841 	for (t = data.remove; t; t = t->next) {
2842 		RhythmDBEntry *entry = t->data;
2843 		if (!filter) {
2844 			g_signal_emit (G_OBJECT (model),
2845 				       rhythmdb_query_model_signals[ENTRY_REMOVED], 0,
2846 				       entry);
2847 		}
2848 		rhythmdb_query_model_remove_from_main_list (model, entry);
2849 	}
2850 
2851 	changed |= (data.remove != NULL);
2852 	g_list_free (data.remove);
2853 	data.remove = NULL;
2854 
2855 	if (changed)
2856 		rhythmdb_query_model_update_limited_entries (model);
2857 }
2858 
2859 static gint
_reverse_sorting_func(gpointer a,gpointer b,struct ReverseSortData * reverse_data)2860 _reverse_sorting_func (gpointer a,
2861 		       gpointer b,
2862 		       struct ReverseSortData *reverse_data)
2863 {
2864 	return - reverse_data->func (a, b, reverse_data->data);
2865 }
2866 
2867 /**
2868  * rhythmdb_query_model_location_sort_func:
2869  * @a: a #RhythmDBEntry
2870  * @b: a #RhythmDBEntry
2871  * @data: nothing
2872  *
2873  * Sort function for sorting by location.
2874  *
2875  * Returns: result of sort comparison between a and b.
2876  */
2877 gint
rhythmdb_query_model_location_sort_func(RhythmDBEntry * a,RhythmDBEntry * b,gpointer data)2878 rhythmdb_query_model_location_sort_func (RhythmDBEntry *a,
2879 					 RhythmDBEntry *b,
2880 					 gpointer data)
2881 {
2882 	const char *a_val;
2883 	const char *b_val;
2884 
2885 	a_val = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_LOCATION);
2886 	b_val = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_LOCATION);
2887 
2888 	if (a_val == NULL) {
2889 		if (b_val == NULL)
2890 			return 0;
2891 		else
2892 			return -1;
2893 	} else if (b_val == NULL)
2894 		return 1;
2895 	else
2896 		return strcmp (a_val, b_val);
2897 }
2898 
2899 /**
2900  * rhythmdb_query_model_title_sort_func:
2901  * @a: a #RhythmDBEntry
2902  * @b: a #RhythmDBEntry
2903  * @data: nothing
2904  *
2905  * Sort function for sorting by title.  Falls back to sorting
2906  * by location if the titles are the same.
2907  *
2908  * Returns: result of sort comparison between a and b.
2909  */
2910 gint
rhythmdb_query_model_title_sort_func(RhythmDBEntry * a,RhythmDBEntry * b,gpointer data)2911 rhythmdb_query_model_title_sort_func (RhythmDBEntry *a,
2912 				      RhythmDBEntry *b,
2913 				      gpointer data)
2914 {
2915 	const char *a_val;
2916 	const char *b_val;
2917 	gint ret;
2918 
2919 	a_val = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_TITLE_SORT_KEY);
2920 	b_val = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_TITLE_SORT_KEY);
2921 
2922 	if (a_val == NULL) {
2923 		if (b_val == NULL)
2924 			ret = 0;
2925 		else
2926 			ret = -1;
2927 	} else if (b_val == NULL)
2928 		ret = 1;
2929 	else
2930 		ret = strcmp (a_val, b_val);
2931 
2932 	if (ret != 0)
2933 		return ret;
2934 	else
2935 		return rhythmdb_query_model_location_sort_func (a, b, data);
2936 }
2937 
2938 /**
2939  * rhythmdb_query_model_album_sort_func:
2940  * @a: a #RhythmDBEntry
2941  * @b: a #RhythmDBEntry
2942  * @data: nothing
2943  *
2944  * Sort function for sorting by album.  Sorts by album, then
2945  * disc number, then track number, then title.
2946  *
2947  * Returns: result of sort comparison between a and b.
2948  */
2949 gint
rhythmdb_query_model_album_sort_func(RhythmDBEntry * a,RhythmDBEntry * b,gpointer data)2950 rhythmdb_query_model_album_sort_func (RhythmDBEntry *a,
2951 				      RhythmDBEntry *b,
2952 				      gpointer data)
2953 {
2954 	const char *a_val;
2955 	const char *b_val;
2956 	gulong a_num;
2957 	gulong b_num;
2958 	gint ret;
2959 
2960 	/* Sort by album name */
2961 	a_val = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_ALBUM_SORTNAME_SORT_KEY);
2962 	if (a_val[0] == '\0') {
2963 		a_val = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_ALBUM_SORT_KEY);
2964 	}
2965 	b_val = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_ALBUM_SORTNAME_SORT_KEY);
2966 	if (b_val[0] == '\0') {
2967 		b_val = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_ALBUM_SORT_KEY);
2968 	}
2969 
2970 	if (a_val == NULL) {
2971 		if (b_val == NULL)
2972 			ret = 0;
2973 		else
2974 			ret = -1;
2975 	} else if (b_val == NULL)
2976 		ret = 1;
2977 	else
2978 		ret = strcmp (a_val, b_val);
2979 
2980 	if (ret != 0)
2981 		return ret;
2982 
2983 	/* Then by disc number (assume 1 if non-existent) */
2984 	a_num = rhythmdb_entry_get_ulong (a, RHYTHMDB_PROP_DISC_NUMBER);
2985 	b_num = rhythmdb_entry_get_ulong (b, RHYTHMDB_PROP_DISC_NUMBER);
2986 	a_num = (a_num ? a_num : 1);
2987 	b_num = (b_num ? b_num : 1);
2988 	if (a_num != b_num)
2989 		return (a_num < b_num ? -1 : 1);
2990 
2991 	/* by track number (assume 0 if non-existent) */
2992 	a_num = rhythmdb_entry_get_ulong (a, RHYTHMDB_PROP_TRACK_NUMBER);
2993 	b_num = rhythmdb_entry_get_ulong (b, RHYTHMDB_PROP_TRACK_NUMBER);
2994 	if (a_num != b_num)
2995 		return (a_num < b_num ? -1 : 1);
2996 
2997 	/*  by title */
2998 	a_val = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_TITLE_SORT_KEY);
2999 	b_val = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_TITLE_SORT_KEY);
3000 
3001 	if (a_val == NULL) {
3002 		if (b_val == NULL)
3003 			return 0;
3004 		else
3005 			return -1;
3006 	} else if (b_val == NULL)
3007 		return 1;
3008 	else
3009 		return rhythmdb_query_model_location_sort_func (a, b, data);
3010 }
3011 
3012 /**
3013  * rhythmdb_query_model_artist_sort_func:
3014  * @a: a #RhythmDBEntry
3015  * @b: a #RhythmDBEntry
3016  * @data: nothing
3017  *
3018  * Sort function for sorting by artist.  Sorts by artist, then
3019  * album, then disc number, then track number, then title.
3020  *
3021  * Returns: result of sort comparison between a and b.
3022  */
3023 gint
rhythmdb_query_model_artist_sort_func(RhythmDBEntry * a,RhythmDBEntry * b,gpointer data)3024 rhythmdb_query_model_artist_sort_func (RhythmDBEntry *a,
3025 				       RhythmDBEntry *b,
3026 				       gpointer data)
3027 {
3028 	const char *a_val;
3029 	const char *b_val;
3030 	gint ret;
3031 
3032 	a_val = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_ARTIST_SORTNAME_SORT_KEY);
3033 	if (a_val[0] == '\0') {
3034 		a_val = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_ARTIST_SORT_KEY);
3035 	}
3036 	b_val = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_ARTIST_SORTNAME_SORT_KEY);
3037 	if (b_val[0] == '\0') {
3038 		b_val = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_ARTIST_SORT_KEY);
3039 	}
3040 
3041 	if (a_val == NULL) {
3042 		if (b_val == NULL)
3043 			ret = 0;
3044 		else
3045 			ret = -1;
3046 	} else if (b_val == NULL)
3047 		ret = 1;
3048 	else
3049 		ret = strcmp (a_val, b_val);
3050 
3051 	if (ret != 0)
3052 		return ret;
3053 	else
3054 		return rhythmdb_query_model_album_sort_func (a, b, data);
3055 }
3056 
3057 /**
3058  * rhythmdb_query_model_composer_sort_func:
3059  * @a: a #RhythmDBEntry
3060  * @b: a #RhythmDBEntry
3061  * @data: nothing
3062  *
3063  * Sort function for sorting by composer.  Sorts by composer, then
3064  * album, then disc number, then track number, then title.
3065  *
3066  * Returns: result of sort comparison between a and b.
3067  */
3068 gint
rhythmdb_query_model_composer_sort_func(RhythmDBEntry * a,RhythmDBEntry * b,gpointer data)3069 rhythmdb_query_model_composer_sort_func (RhythmDBEntry *a,
3070 				       RhythmDBEntry *b,
3071 				       gpointer data)
3072 {
3073 	const char *a_val;
3074 	const char *b_val;
3075 	gint ret;
3076 
3077 	a_val = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_COMPOSER_SORTNAME_SORT_KEY);
3078 	if (a_val[0] == '\0') {
3079 		a_val = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_COMPOSER_SORT_KEY);
3080 	}
3081 	b_val = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_COMPOSER_SORTNAME_SORT_KEY);
3082 	if (b_val[0] == '\0') {
3083 		b_val = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_COMPOSER_SORT_KEY);
3084 	}
3085 
3086 	if (a_val == NULL) {
3087 		if (b_val == NULL)
3088 			ret = 0;
3089 		else
3090 			ret = -1;
3091 	} else if (b_val == NULL)
3092 		ret = 1;
3093 	else
3094 		ret = strcmp (a_val, b_val);
3095 
3096 	if (ret != 0)
3097 		return ret;
3098 	else
3099 		return rhythmdb_query_model_album_sort_func (a, b, data);
3100 }
3101 
3102 /**
3103  * rhythmdb_query_model_genre_sort_func:
3104  * @a: a #RhythmDBEntry
3105  * @b: a #RhythmDBEntry
3106  * @data: nothing
3107  *
3108  * Sort function for sorting by genre.  Sorts by genre, then artist,
3109  * then album, then disc number, then track number, then title.
3110  *
3111  * Returns: result of sort comparison between a and b.
3112  */
3113 gint
rhythmdb_query_model_genre_sort_func(RhythmDBEntry * a,RhythmDBEntry * b,gpointer data)3114 rhythmdb_query_model_genre_sort_func (RhythmDBEntry *a, RhythmDBEntry *b,
3115 				      gpointer data)
3116 {
3117 	const char *a_val;
3118 	const char *b_val;
3119 	gint ret;
3120 
3121 	a_val = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_GENRE_SORT_KEY);
3122 	b_val = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_GENRE_SORT_KEY);
3123 
3124 	if (a_val == NULL) {
3125 		if (b_val == NULL)
3126 			ret = 0;
3127 		else
3128 			ret = -1;
3129 	} else if (b_val == NULL)
3130 		ret = 1;
3131 	else
3132 		ret = strcmp (a_val, b_val);
3133 
3134 	if (ret != 0)
3135 		return ret;
3136 	else
3137 		return rhythmdb_query_model_artist_sort_func (a, b, data);
3138 }
3139 
3140 /**
3141  * rhythmdb_query_model_track_sort_func:
3142  * @a: a #RhythmDBEntry
3143  * @b: a #RhythmDBEntry
3144  * @data: nothing
3145  *
3146  * Sort function for sorting by track.  Sorts by artist,
3147  * then album, then disc number, then track number, then title.
3148  *
3149  * Returns: result of sort comparison between a and b.
3150  */
3151 gint
rhythmdb_query_model_track_sort_func(RhythmDBEntry * a,RhythmDBEntry * b,gpointer data)3152 rhythmdb_query_model_track_sort_func (RhythmDBEntry *a,
3153 				      RhythmDBEntry *b,
3154 				      gpointer data)
3155 {
3156 	return rhythmdb_query_model_album_sort_func (a, b, data);
3157 }
3158 
3159 /**
3160  * rhythmdb_query_model_double_ceiling_sort_func:
3161  * @a: a #RhythmDBEntry
3162  * @b: a #RhythmDBEntry
3163  * @data: property to sort on
3164  *
3165  * Sort function for sorting by a rounded floating point value.
3166  * The property value is rounded up to an integer value for sorting.
3167  * If the values are the same, falls back to sorting by location.
3168  *
3169  * Returns: result of sort comparison between a and b.
3170  */
3171 gint
rhythmdb_query_model_double_ceiling_sort_func(RhythmDBEntry * a,RhythmDBEntry * b,gpointer data)3172 rhythmdb_query_model_double_ceiling_sort_func (RhythmDBEntry *a,
3173 					       RhythmDBEntry *b,
3174 					       gpointer data)
3175 {
3176 	gdouble a_val, b_val;
3177 	RhythmDBPropType prop_id;
3178 
3179 	prop_id = (RhythmDBPropType) GPOINTER_TO_INT (data);
3180 
3181 	a_val = ceil (rhythmdb_entry_get_double (a, prop_id));
3182 	b_val = ceil (rhythmdb_entry_get_double (b, prop_id));
3183 
3184 	if (a_val != b_val)
3185 		return (a_val > b_val ? 1 : -1);
3186 	else
3187 		return rhythmdb_query_model_location_sort_func (a, b, data);
3188 }
3189 
3190 /**
3191  * rhythmdb_query_model_ulong_sort_func:
3192  * @a: a #RhythmDBEntry
3193  * @b: a #RhythmDBEntry
3194  * @data: property to sort on
3195  *
3196  * Sort function for sorting by an unsigned integer property value.
3197  * If the values are the same, falls back to sorting by location.
3198  *
3199  * Returns: result of sort comparison between a and b.
3200  */
3201 gint
rhythmdb_query_model_ulong_sort_func(RhythmDBEntry * a,RhythmDBEntry * b,gpointer data)3202 rhythmdb_query_model_ulong_sort_func (RhythmDBEntry *a,
3203 				      RhythmDBEntry *b,
3204 				      gpointer data)
3205 {
3206 	gulong a_val, b_val;
3207 	RhythmDBPropType prop_id;
3208 
3209 	prop_id = (RhythmDBPropType) GPOINTER_TO_INT (data);
3210 	a_val = rhythmdb_entry_get_ulong (a, prop_id);
3211 	b_val = rhythmdb_entry_get_ulong (b, prop_id);
3212 
3213 	if (a_val != b_val)
3214 		return (a_val > b_val ? 1 : -1);
3215 	else
3216 		return rhythmdb_query_model_location_sort_func (a, b, data);
3217 }
3218 
3219 /**
3220  * rhythmdb_query_model_bitrate_sort_func:
3221  * @a: a #RhythmDBEntry
3222  * @b: a #RhythmDBEntry
3223  * @data: nothing
3224  *
3225  * Sort function for sorting by bitrate.  Lossless encodings (as identified
3226  * by media type) are considered to have the highest possible bitrate.
3227  * Falls back to sorting by location for equal bitrates.
3228  *
3229  * Returns: result of sort comparison between a and b.
3230  */
3231 gint
rhythmdb_query_model_bitrate_sort_func(RhythmDBEntry * a,RhythmDBEntry * b,gpointer data)3232 rhythmdb_query_model_bitrate_sort_func (RhythmDBEntry *a,
3233 					RhythmDBEntry *b,
3234 					gpointer data)
3235 {
3236 	gulong a_val, b_val;
3237 
3238 	if (rhythmdb_entry_is_lossless (a)) {
3239 		if (rhythmdb_entry_is_lossless (b))
3240 			return rhythmdb_query_model_location_sort_func (a, b, data);
3241 		else
3242 			return 1;
3243 	} else {
3244 		if (rhythmdb_entry_is_lossless (b))
3245 			return -1;
3246 	}
3247 
3248 	a_val = rhythmdb_entry_get_ulong (a, RHYTHMDB_PROP_BITRATE);
3249 	b_val = rhythmdb_entry_get_ulong (b, RHYTHMDB_PROP_BITRATE);
3250 
3251 	if (a_val != b_val)
3252 		return (a_val > b_val ? 1 : -1);
3253 	else
3254 		return rhythmdb_query_model_location_sort_func (a, b, data);
3255 }
3256 
3257 /**
3258  * rhythmdb_query_model_date_sort_func:
3259  * @a: a #RhythmDBEntry
3260  * @b: a #RhythmDBEntry
3261  * @data: nothing
3262  *
3263  * Sort function for sorting by release date.
3264  * Falls back to album sort order for equal dates.
3265  *
3266  * Returns: result of sort comparison between a and b.
3267  */
3268 gint
rhythmdb_query_model_date_sort_func(RhythmDBEntry * a,RhythmDBEntry * b,gpointer data)3269 rhythmdb_query_model_date_sort_func (RhythmDBEntry *a,
3270 				     RhythmDBEntry *b,
3271 				     gpointer data)
3272 
3273 {
3274 	gulong a_val, b_val;
3275 
3276 	a_val = rhythmdb_entry_get_ulong (a, RHYTHMDB_PROP_DATE);
3277 	b_val = rhythmdb_entry_get_ulong (b, RHYTHMDB_PROP_DATE);
3278 
3279 	if (a_val > b_val)
3280 		return 1;
3281 	else if (a_val < b_val)
3282 		return -1;
3283 	else
3284 		return rhythmdb_query_model_album_sort_func (a, b, data);
3285 }
3286 
3287 /**
3288  * rhythmdb_query_model_string_sort_func:
3289  * @a: a #RhythmDBEntry
3290  * @b: a #RhythmDBEntry
3291  * @data: property to sort on
3292  *
3293  * Sort function for sorting by a single string property
3294  * Falls back to location sort order if the strings are equal.
3295  *
3296  * Returns: result of sort comparison between a and b.
3297  */
3298 gint
rhythmdb_query_model_string_sort_func(RhythmDBEntry * a,RhythmDBEntry * b,gpointer data)3299 rhythmdb_query_model_string_sort_func (RhythmDBEntry *a,
3300 				       RhythmDBEntry *b,
3301 				       gpointer data)
3302 {
3303 	const char *a_val;
3304 	const char *b_val;
3305 	gint ret;
3306 	RhythmDBPropType prop_id;
3307 
3308 	prop_id = (RhythmDBPropType) GPOINTER_TO_INT (data);
3309 	a_val = rhythmdb_entry_get_string (a, prop_id);
3310 	b_val = rhythmdb_entry_get_string (b, prop_id);
3311 
3312 	if (a_val == NULL) {
3313 		if (b_val == NULL)
3314 			ret = 0;
3315 		else
3316 			ret = -1;
3317 	} else if (b_val == NULL)
3318 		ret = 1;
3319 	else
3320 		ret = strcmp (a_val, b_val);
3321 
3322 	if (ret != 0)
3323 		return ret;
3324 	else
3325 		return rhythmdb_query_model_location_sort_func (a, b, data);
3326 }
3327 
3328 static gboolean
rhythmdb_query_model_within_limit(RhythmDBQueryModel * model,RhythmDBEntry * entry)3329 rhythmdb_query_model_within_limit (RhythmDBQueryModel *model,
3330 				   RhythmDBEntry *entry)
3331 {
3332 	gboolean result = TRUE;
3333 
3334 	switch (model->priv->limit_type) {
3335 	case RHYTHMDB_QUERY_MODEL_LIMIT_NONE:
3336 		result = TRUE;
3337 		break;
3338 
3339 	case RHYTHMDB_QUERY_MODEL_LIMIT_COUNT:
3340 		{
3341 			guint64 limit_count;
3342 			guint64 current_count;
3343 
3344 			limit_count = g_variant_get_uint64 (model->priv->limit_value);
3345 			current_count = g_hash_table_size (model->priv->reverse_map);
3346 
3347 			if (entry)
3348 				current_count++;
3349 
3350 			result = (current_count <= limit_count);
3351 			break;
3352 		}
3353 
3354 	case RHYTHMDB_QUERY_MODEL_LIMIT_SIZE:
3355 		{
3356 			guint64 limit_size;
3357 			guint64 current_size;
3358 
3359 			limit_size = g_variant_get_uint64 (model->priv->limit_value);
3360 			current_size = model->priv->total_size;
3361 
3362 			if (entry)
3363 				current_size += rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
3364 
3365 			/* the limit is in MB */
3366 			result = (current_size / (1024 * 1024) <= limit_size);
3367 			break;
3368 		}
3369 
3370 	case RHYTHMDB_QUERY_MODEL_LIMIT_TIME:
3371 		{
3372 			guint64 limit_time;
3373 			guint64 current_time;
3374 
3375 			limit_time = g_variant_get_uint64 (model->priv->limit_value);
3376 			current_time = model->priv->total_duration;
3377 
3378 			if (entry)
3379 				current_time += rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
3380 
3381 			result = (current_time <= limit_time);
3382 			break;
3383 		}
3384 	}
3385 
3386 	return result;
3387 }
3388 
3389 /* This should really be standard. */
3390 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
3391 
3392 GType
rhythmdb_query_model_limit_type_get_type(void)3393 rhythmdb_query_model_limit_type_get_type (void)
3394 {
3395 	static GType etype = 0;
3396 
3397 	if (etype == 0)
3398 	{
3399 		static const GEnumValue values[] =
3400 		{
3401 
3402 			ENUM_ENTRY (RHYTHMDB_QUERY_MODEL_LIMIT_NONE, "no-limit"),
3403 			ENUM_ENTRY (RHYTHMDB_QUERY_MODEL_LIMIT_COUNT, "limit-count"),
3404 			ENUM_ENTRY (RHYTHMDB_QUERY_MODEL_LIMIT_SIZE, "limit-size"),
3405 			ENUM_ENTRY (RHYTHMDB_QUERY_MODEL_LIMIT_TIME, "limit-duration"),
3406 			{ 0, 0, 0 }
3407 		};
3408 
3409 		etype = g_enum_register_static ("RhythmDBQueryModelLimitType", values);
3410 	}
3411 
3412 	return etype;
3413 }
3414 
3415 static gboolean
rhythmdb_query_model_reapply_query_cb(RhythmDBQueryModel * model)3416 rhythmdb_query_model_reapply_query_cb (RhythmDBQueryModel *model)
3417 {
3418 	rhythmdb_query_model_reapply_query (model, FALSE);
3419 	rhythmdb_do_full_query_async_parsed (model->priv->db,
3420 					     RHYTHMDB_QUERY_RESULTS (model),
3421 					     model->priv->original_query);
3422 	return TRUE;
3423 }
3424