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