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