udapted vi.po
[rhythmbox.git] / rhythmdb / rhythmdb-query-model.c
blobfe5389662718d7f7fbf90cb6000c51db7173b833
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * arch-tag: Implementation of RhythmDB query result GtkTreeModel
5 * Copyright (C) 2003 Colin Walters <walters@gnome.org>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "config.h"
25 #include <unistd.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <stdio.h>
29 #include <math.h>
31 #include <gtk/gtktreednd.h>
33 #include "rhythmdb-query-model.h"
34 #include "rb-debug.h"
35 #include "gsequence.h"
36 #include "rb-tree-dnd.h"
37 #include "rb-marshal.h"
38 #include "rb-util.h"
40 struct ReverseSortData
42 GCompareDataFunc func;
43 gpointer data;
46 static void rhythmdb_query_model_query_results_init (RhythmDBQueryResultsIface *iface);
47 static void rhythmdb_query_model_tree_model_init (GtkTreeModelIface *iface);
48 static void rhythmdb_query_model_drag_source_init (RbTreeDragSourceIface *iface);
49 static void rhythmdb_query_model_drag_dest_init (RbTreeDragDestIface *iface);
51 G_DEFINE_TYPE_WITH_CODE(RhythmDBQueryModel, rhythmdb_query_model, G_TYPE_OBJECT,
52 G_IMPLEMENT_INTERFACE(RHYTHMDB_TYPE_QUERY_RESULTS,
53 rhythmdb_query_model_query_results_init)
54 G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL,
55 rhythmdb_query_model_tree_model_init)
56 G_IMPLEMENT_INTERFACE(RB_TYPE_TREE_DRAG_SOURCE,
57 rhythmdb_query_model_drag_source_init)
58 G_IMPLEMENT_INTERFACE(RB_TYPE_TREE_DRAG_DEST,
59 rhythmdb_query_model_drag_dest_init))
61 static void rhythmdb_query_model_init (RhythmDBQueryModel *shell_player);
62 static GObject *rhythmdb_query_model_constructor (GType type, guint n_construct_properties,
63 GObjectConstructParam *construct_properties);
64 static void rhythmdb_query_model_dispose (GObject *object);
65 static void rhythmdb_query_model_finalize (GObject *object);
66 static void rhythmdb_query_model_set_property (GObject *object,
67 guint prop_id,
68 const GValue *value,
69 GParamSpec *pspec);
70 static void rhythmdb_query_model_get_property (GObject *object,
71 guint prop_id,
72 GValue *value,
73 GParamSpec *pspec);
74 static void rhythmdb_query_model_do_insert (RhythmDBQueryModel *model,
75 RhythmDBEntry *entry,
76 gint index);
77 static void rhythmdb_query_model_entry_added_cb (RhythmDB *db, RhythmDBEntry *entry,
78 RhythmDBQueryModel *model);
79 static void rhythmdb_query_model_entry_changed_cb (RhythmDB *db, RhythmDBEntry *entry,
80 GSList *changes, RhythmDBQueryModel *model);
81 static void rhythmdb_query_model_entry_deleted_cb (RhythmDB *db, RhythmDBEntry *entry,
82 RhythmDBQueryModel *model);
84 static void rhythmdb_query_model_filter_out_entry (RhythmDBQueryModel *model,
85 RhythmDBEntry *entry);
86 static gboolean rhythmdb_query_model_do_reorder (RhythmDBQueryModel *model, RhythmDBEntry *entry);
87 static gboolean rhythmdb_query_model_emit_reorder (RhythmDBQueryModel *model, gint old_pos, gint new_pos);
88 static gboolean rhythmdb_query_model_drag_data_get (RbTreeDragSource *dragsource,
89 GList *paths,
90 GtkSelectionData *selection_data);
91 static gboolean rhythmdb_query_model_drag_data_delete (RbTreeDragSource *dragsource,
92 GList *paths);
93 static gboolean rhythmdb_query_model_row_draggable (RbTreeDragSource *dragsource,
94 GList *paths);
95 static gboolean rhythmdb_query_model_drag_data_received (RbTreeDragDest *drag_dest,
96 GtkTreePath *dest,
97 GtkTreeViewDropPosition pos,
98 GtkSelectionData *selection_data);
99 static gboolean rhythmdb_query_model_row_drop_possible (RbTreeDragDest *drag_dest,
100 GtkTreePath *dest,
101 GtkTreeViewDropPosition pos,
102 GtkSelectionData *selection_data);
103 static gboolean rhythmdb_query_model_row_drop_position (RbTreeDragDest *drag_dest,
104 GtkTreePath *dest_path,
105 GList *targets,
106 GtkTreeViewDropPosition *pos);
108 static void rhythmdb_query_model_set_query (RhythmDBQueryResults *results, GPtrArray *query);
109 static void rhythmdb_query_model_add_results (RhythmDBQueryResults *results, GPtrArray *entries);
110 static void rhythmdb_query_model_query_complete (RhythmDBQueryResults *results);
112 static GtkTreeModelFlags rhythmdb_query_model_get_flags (GtkTreeModel *model);
113 static gint rhythmdb_query_model_get_n_columns (GtkTreeModel *tree_model);
114 static GType rhythmdb_query_model_get_column_type (GtkTreeModel *tree_model, int index);
115 static gboolean rhythmdb_query_model_get_iter (GtkTreeModel *tree_model, GtkTreeIter *iter,
116 GtkTreePath *path);
117 static GtkTreePath * rhythmdb_query_model_get_path (GtkTreeModel *tree_model,
118 GtkTreeIter *iter);
119 static void rhythmdb_query_model_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter,
120 gint column, GValue *value);
121 static gboolean rhythmdb_query_model_iter_next (GtkTreeModel *tree_model,
122 GtkTreeIter *iter);
123 static gboolean rhythmdb_query_model_iter_children (GtkTreeModel *tree_model,
124 GtkTreeIter *iter,
125 GtkTreeIter *parent);
126 static gboolean rhythmdb_query_model_iter_has_child (GtkTreeModel *tree_model,
127 GtkTreeIter *iter);
128 static gint rhythmdb_query_model_iter_n_children (GtkTreeModel *tree_model,
129 GtkTreeIter *iter);
130 static gboolean rhythmdb_query_model_iter_nth_child (GtkTreeModel *tree_model,
131 GtkTreeIter *iter, GtkTreeIter *parent,
132 gint n);
133 static gboolean rhythmdb_query_model_iter_parent (GtkTreeModel *tree_model,
134 GtkTreeIter *iter,
135 GtkTreeIter *child);
137 static void rhythmdb_query_model_base_row_inserted (GtkTreeModel *base_model,
138 GtkTreePath *path,
139 GtkTreeIter *iter,
140 RhythmDBQueryModel *model);
141 static void rhythmdb_query_model_base_row_deleted (GtkTreeModel *base_model,
142 GtkTreePath *path,
143 RhythmDBQueryModel *model);
144 static void rhythmdb_query_model_base_non_entry_dropped (GtkTreeModel *base_model,
145 const char *location,
146 int position,
147 RhythmDBQueryModel *model);
148 static void rhythmdb_query_model_base_complete (GtkTreeModel *base_model,
149 RhythmDBQueryModel *model);
150 static void rhythmdb_query_model_base_rows_reordered (GtkTreeModel *base_model,
151 GtkTreePath *arg1,
152 GtkTreeIter *arg2,
153 gint *order_map,
154 RhythmDBQueryModel *model);
155 static void rhythmdb_query_model_base_entry_removed (RhythmDBQueryModel *base_model,
156 RhythmDBEntry *entry,
157 RhythmDBQueryModel *model);
158 static void rhythmdb_query_model_base_entry_prop_changed (RhythmDBQueryModel *base_model,
159 RhythmDBEntry *entry,
160 RhythmDBPropType prop,
161 const GValue *old,
162 const GValue *new_value,
163 RhythmDBQueryModel *model);
164 static int rhythmdb_query_model_child_index_to_base_index (RhythmDBQueryModel *model, int index);
166 static gint _reverse_sorting_func (gpointer a, gpointer b, struct ReverseSortData *model);
167 static gboolean rhythmdb_query_model_within_limit (RhythmDBQueryModel *model,
168 RhythmDBEntry *entry);
169 static gboolean rhythmdb_query_model_reapply_query_cb (RhythmDBQueryModel *model);
171 struct RhythmDBQueryModelUpdate
173 RhythmDBQueryModel *model;
174 enum {
175 RHYTHMDB_QUERY_MODEL_UPDATE_ROWS_INSERTED,
176 RHYTHMDB_QUERY_MODEL_UPDATE_ROW_INSERTED_INDEX,
177 RHYTHMDB_QUERY_MODEL_UPDATE_QUERY_COMPLETE,
178 } type;
180 union {
181 struct {
182 RhythmDBEntry *entry;
183 gint index;
184 } data;
185 GPtrArray *entries;
186 } entrydata;
189 static void rhythmdb_query_model_process_update (struct RhythmDBQueryModelUpdate *update);
191 static void idle_process_update (struct RhythmDBQueryModelUpdate *update);
193 enum {
194 TARGET_ENTRIES,
195 TARGET_URIS
198 static const GtkTargetEntry rhythmdb_query_model_drag_types[] = {
199 { "application/x-rhythmbox-entry", 0, TARGET_ENTRIES },
200 { "text/uri-list", 0, TARGET_URIS },
203 static GtkTargetList *rhythmdb_query_model_drag_target_list = NULL;
205 struct RhythmDBQueryModelPrivate
207 RhythmDB *db;
209 RhythmDBQueryModel *base_model;
211 GCompareDataFunc sort_func;
212 gpointer sort_data;
213 GDestroyNotify sort_data_destroy;
214 gboolean sort_reverse;
216 GPtrArray *query;
217 GPtrArray *original_query;
219 guint stamp;
221 RhythmDBQueryModelLimitType limit_type;
222 GValueArray *limit_value;
224 glong total_duration;
225 guint64 total_size;
227 GSequence *entries;
228 GHashTable *reverse_map;
229 GSequence *limited_entries;
230 GHashTable *limited_reverse_map;
231 GHashTable *hidden_entry_map;
233 gint pending_update_count;
235 gboolean reorder_drag_and_drop;
236 gboolean show_hidden;
238 gint query_reapply_timeout_id;
241 #define RHYTHMDB_QUERY_MODEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE_QUERY_MODEL, RhythmDBQueryModelPrivate))
243 enum
245 PROP_0,
246 PROP_RHYTHMDB,
247 PROP_QUERY,
248 PROP_SORT_FUNC,
249 PROP_SORT_DATA,
250 PROP_SORT_DATA_DESTROY,
251 PROP_SORT_REVERSE,
252 PROP_LIMIT_TYPE,
253 PROP_LIMIT_VALUE,
254 PROP_SHOW_HIDDEN,
255 PROP_BASE_MODEL,
258 enum
260 COMPLETE,
261 ENTRY_PROP_CHANGED,
262 ENTRY_REMOVED,
263 NON_ENTRY_DROPPED,
264 POST_ENTRY_DELETE,
265 LAST_SIGNAL
268 static guint rhythmdb_query_model_signals[LAST_SIGNAL] = { 0 };
270 static void
271 rhythmdb_query_model_class_init (RhythmDBQueryModelClass *klass)
273 GObjectClass *object_class = G_OBJECT_CLASS (klass);
275 if (!rhythmdb_query_model_drag_target_list)
276 rhythmdb_query_model_drag_target_list
277 = gtk_target_list_new (rhythmdb_query_model_drag_types,
278 G_N_ELEMENTS (rhythmdb_query_model_drag_types));
280 object_class->set_property = rhythmdb_query_model_set_property;
281 object_class->get_property = rhythmdb_query_model_get_property;
283 object_class->dispose = rhythmdb_query_model_dispose;
284 object_class->finalize = rhythmdb_query_model_finalize;
285 object_class->constructor = rhythmdb_query_model_constructor;
287 g_object_class_install_property (object_class,
288 PROP_RHYTHMDB,
289 g_param_spec_object ("db",
290 "RhythmDB",
291 "RhythmDB object",
292 RHYTHMDB_TYPE,
293 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
294 g_object_class_install_property (object_class,
295 PROP_QUERY,
296 g_param_spec_pointer ("query",
297 "Query",
298 "RhythmDBQuery",
299 G_PARAM_READWRITE));
300 g_object_class_install_property (object_class,
301 PROP_SORT_FUNC,
302 g_param_spec_pointer ("sort-func",
303 "SortFunc",
304 "Sort function",
305 G_PARAM_READWRITE));
306 g_object_class_install_property (object_class,
307 PROP_SORT_DATA,
308 g_param_spec_pointer ("sort-data",
309 "Sort data",
310 "Sort data",
311 G_PARAM_READWRITE));
312 g_object_class_install_property (object_class,
313 PROP_SORT_DATA_DESTROY,
314 g_param_spec_pointer ("sort-data-destroy",
315 "Sort data destroy",
316 "Sort data destroy function",
317 G_PARAM_READWRITE));
318 g_object_class_install_property (object_class,
319 PROP_SORT_REVERSE,
320 g_param_spec_boolean ("sort-reverse",
321 "sort-reverse",
322 "Reverse sort order flag",
323 FALSE,
324 G_PARAM_READWRITE));
325 g_object_class_install_property (object_class,
326 PROP_LIMIT_TYPE,
327 g_param_spec_enum ("limit-type",
328 "limit-type",
329 "type of limit",
330 RHYTHMDB_TYPE_QUERY_MODEL_LIMIT_TYPE,
331 RHYTHMDB_QUERY_MODEL_LIMIT_NONE,
332 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
333 g_object_class_install_property (object_class,
334 PROP_LIMIT_VALUE,
335 g_param_spec_boxed ("limit-value",
336 "limit-value",
337 "value of limit",
338 G_TYPE_VALUE_ARRAY,
339 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
340 g_object_class_install_property (object_class,
341 PROP_SHOW_HIDDEN,
342 g_param_spec_boolean ("show-hidden",
343 "show hidden",
344 "maximum time (seconds)",
345 FALSE,
346 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
347 g_object_class_install_property (object_class,
348 PROP_BASE_MODEL,
349 g_param_spec_object ("base-model",
350 "base-model",
351 "base RhythmDBQueryModel",
352 RHYTHMDB_TYPE_QUERY_MODEL,
353 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
355 rhythmdb_query_model_signals[ENTRY_PROP_CHANGED] =
356 g_signal_new ("entry-prop-changed",
357 RHYTHMDB_TYPE_QUERY_MODEL,
358 G_SIGNAL_RUN_LAST,
359 G_STRUCT_OFFSET (RhythmDBQueryModelClass, entry_prop_changed),
360 NULL, NULL,
361 rb_marshal_VOID__BOXED_INT_POINTER_POINTER,
362 G_TYPE_NONE,
363 4, RHYTHMDB_TYPE_ENTRY, G_TYPE_INT, G_TYPE_POINTER, G_TYPE_POINTER);
364 rhythmdb_query_model_signals[ENTRY_REMOVED] =
365 g_signal_new ("entry-removed",
366 RHYTHMDB_TYPE_QUERY_MODEL,
367 G_SIGNAL_RUN_LAST,
368 G_STRUCT_OFFSET (RhythmDBQueryModelClass, entry_removed),
369 NULL, NULL,
370 g_cclosure_marshal_VOID__BOXED,
371 G_TYPE_NONE,
372 1, RHYTHMDB_TYPE_ENTRY);
373 rhythmdb_query_model_signals[NON_ENTRY_DROPPED] =
374 g_signal_new ("non-entry-dropped",
375 RHYTHMDB_TYPE_QUERY_MODEL,
376 G_SIGNAL_RUN_LAST,
377 G_STRUCT_OFFSET (RhythmDBQueryModelClass, non_entry_dropped),
378 NULL, NULL,
379 rb_marshal_VOID__POINTER_INT,
380 G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_INT);
381 rhythmdb_query_model_signals[COMPLETE] =
382 g_signal_new ("complete",
383 RHYTHMDB_TYPE_QUERY_MODEL,
384 G_SIGNAL_RUN_LAST,
385 G_STRUCT_OFFSET (RhythmDBQueryModelClass, complete),
386 NULL, NULL,
387 g_cclosure_marshal_VOID__VOID,
388 G_TYPE_NONE, 0);
389 rhythmdb_query_model_signals[POST_ENTRY_DELETE] =
390 g_signal_new ("post-entry-delete",
391 RHYTHMDB_TYPE_QUERY_MODEL,
392 G_SIGNAL_RUN_LAST,
393 G_STRUCT_OFFSET (RhythmDBQueryModelClass, post_entry_delete),
394 NULL, NULL,
395 g_cclosure_marshal_VOID__BOXED,
396 G_TYPE_NONE,
397 1, RHYTHMDB_TYPE_ENTRY);
399 g_type_class_add_private (klass, sizeof (RhythmDBQueryModelPrivate));
402 static void
403 rhythmdb_query_model_query_results_init (RhythmDBQueryResultsIface *iface)
405 iface->set_query = rhythmdb_query_model_set_query;
406 iface->add_results = rhythmdb_query_model_add_results;
407 iface->query_complete = rhythmdb_query_model_query_complete;
410 static void
411 rhythmdb_query_model_tree_model_init (GtkTreeModelIface *iface)
413 iface->get_flags = rhythmdb_query_model_get_flags;
414 iface->get_n_columns = rhythmdb_query_model_get_n_columns;
415 iface->get_column_type = rhythmdb_query_model_get_column_type;
416 iface->get_iter = rhythmdb_query_model_get_iter;
417 iface->get_path = rhythmdb_query_model_get_path;
418 iface->get_value = rhythmdb_query_model_get_value;
419 iface->iter_next = rhythmdb_query_model_iter_next;
420 iface->iter_children = rhythmdb_query_model_iter_children;
421 iface->iter_has_child = rhythmdb_query_model_iter_has_child;
422 iface->iter_n_children = rhythmdb_query_model_iter_n_children;
423 iface->iter_nth_child = rhythmdb_query_model_iter_nth_child;
424 iface->iter_parent = rhythmdb_query_model_iter_parent;
427 static void
428 rhythmdb_query_model_drag_source_init (RbTreeDragSourceIface *iface)
430 iface->rb_row_draggable = rhythmdb_query_model_row_draggable;
431 iface->rb_drag_data_delete = rhythmdb_query_model_drag_data_delete;
432 iface->rb_drag_data_get = rhythmdb_query_model_drag_data_get;
435 static void
436 rhythmdb_query_model_drag_dest_init (RbTreeDragDestIface *iface)
438 iface->rb_drag_data_received = rhythmdb_query_model_drag_data_received;
439 iface->rb_row_drop_possible = rhythmdb_query_model_row_drop_possible;
440 iface->rb_row_drop_position = rhythmdb_query_model_row_drop_position;
443 static void
444 rhythmdb_query_model_set_query_internal (RhythmDBQueryModel *model,
445 GPtrArray *query)
447 if (query == model->priv->original_query)
448 return;
450 rhythmdb_query_free (model->priv->query);
451 rhythmdb_query_free (model->priv->original_query);
453 model->priv->query = rhythmdb_query_copy (query);
454 model->priv->original_query = rhythmdb_query_copy (model->priv->query);
455 rhythmdb_query_preprocess (model->priv->db, model->priv->query);
457 /* if the query contains time-relative criteria, re-run it periodically.
458 * currently it's just every half hour, but perhaps it could be smarter.
460 if (rhythmdb_query_is_time_relative (model->priv->db, model->priv->query)) {
461 if (model->priv->query_reapply_timeout_id == 0) {
462 model->priv->query_reapply_timeout_id =
463 g_timeout_add (1 * 60 * 1000,
464 (GSourceFunc) rhythmdb_query_model_reapply_query_cb,
465 model);
467 } else if (model->priv->query_reapply_timeout_id) {
468 g_source_remove (model->priv->query_reapply_timeout_id);
469 model->priv->query_reapply_timeout_id = 0;
473 static void
474 rhythmdb_query_model_set_property (GObject *object,
475 guint prop_id,
476 const GValue *value,
477 GParamSpec *pspec)
479 RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (object);
481 switch (prop_id) {
482 case PROP_RHYTHMDB:
483 model->priv->db = g_value_get_object (value);
484 break;
485 case PROP_QUERY:
486 rhythmdb_query_model_set_query_internal (model, g_value_get_pointer (value));
487 break;
488 case PROP_SORT_FUNC:
489 model->priv->sort_func = g_value_get_pointer (value);
490 break;
491 case PROP_SORT_DATA:
492 if (model->priv->sort_data_destroy && model->priv->sort_data)
493 model->priv->sort_data_destroy (model->priv->sort_data);
494 model->priv->sort_data = g_value_get_pointer (value);
495 break;
496 case PROP_SORT_DATA_DESTROY:
497 model->priv->sort_data_destroy = g_value_get_pointer (value);
498 break;
499 case PROP_SORT_REVERSE:
500 model->priv->sort_reverse = g_value_get_boolean (value);
501 break;
502 case PROP_LIMIT_TYPE:
503 model->priv->limit_type = g_value_get_enum (value);
504 break;
505 case PROP_LIMIT_VALUE:
506 if (model->priv->limit_value)
507 g_value_array_free (model->priv->limit_value);
508 model->priv->limit_value = (GValueArray*)g_value_dup_boxed (value);
509 break;
510 case PROP_SHOW_HIDDEN:
511 model->priv->show_hidden = g_value_get_boolean (value);
512 /* FIXME: this will have funky issues if this is changed after entries are added */
513 break;
514 case PROP_BASE_MODEL:
515 rhythmdb_query_model_chain (model, g_value_get_object (value), TRUE);
516 break;
517 default:
518 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
519 break;
523 static void
524 rhythmdb_query_model_get_property (GObject *object,
525 guint prop_id,
526 GValue *value,
527 GParamSpec *pspec)
529 RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (object);
531 switch (prop_id) {
532 case PROP_RHYTHMDB:
533 g_value_set_object (value, model->priv->db);
534 break;
535 case PROP_QUERY:
536 g_value_set_pointer (value, model->priv->original_query);
537 break;
538 case PROP_SORT_FUNC:
539 g_value_set_pointer (value, model->priv->sort_func);
540 break;
541 case PROP_SORT_DATA:
542 g_value_set_pointer (value, model->priv->sort_data);
543 break;
544 case PROP_SORT_DATA_DESTROY:
545 g_value_set_pointer (value, model->priv->sort_data_destroy);
546 break;
547 case PROP_SORT_REVERSE:
548 g_value_set_boolean (value, model->priv->sort_reverse);
549 break;
550 case PROP_LIMIT_TYPE:
551 g_value_set_enum (value, model->priv->limit_type);
552 break;
553 case PROP_LIMIT_VALUE:
554 g_value_set_boxed (value, model->priv->limit_value);
555 break;
556 case PROP_SHOW_HIDDEN:
557 g_value_set_boolean (value, model->priv->show_hidden);
558 break;
559 case PROP_BASE_MODEL:
560 g_value_set_object (value, model->priv->base_model);
561 break;
562 default:
563 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
564 break;
568 static void
569 rhythmdb_query_model_init (RhythmDBQueryModel *model)
571 model->priv = RHYTHMDB_QUERY_MODEL_GET_PRIVATE (model);
573 model->priv->stamp = g_random_int ();
575 model->priv->entries = g_sequence_new (NULL);
576 model->priv->reverse_map = g_hash_table_new_full (g_direct_hash,
577 g_direct_equal,
578 (GDestroyNotify)rhythmdb_entry_unref,
579 NULL);
581 model->priv->limited_entries = g_sequence_new (NULL);
582 model->priv->limited_reverse_map = g_hash_table_new_full (g_direct_hash,
583 g_direct_equal,
584 (GDestroyNotify)rhythmdb_entry_unref,
585 NULL);
587 model->priv->hidden_entry_map = g_hash_table_new_full (g_direct_hash,
588 g_direct_equal,
589 (GDestroyNotify)rhythmdb_entry_unref,
590 NULL);
592 model->priv->reorder_drag_and_drop = FALSE;
595 static GObject *
596 rhythmdb_query_model_constructor (GType type,
597 guint n_construct_properties,
598 GObjectConstructParam *construct_properties)
600 RhythmDBQueryModel *model;
602 model = RHYTHMDB_QUERY_MODEL (G_OBJECT_CLASS (rhythmdb_query_model_parent_class)->
603 constructor (type, n_construct_properties, construct_properties));
605 g_signal_connect_object (G_OBJECT (model->priv->db),
606 "entry_added",
607 G_CALLBACK (rhythmdb_query_model_entry_added_cb),
608 model, 0);
609 g_signal_connect_object (G_OBJECT (model->priv->db),
610 "entry_changed",
611 G_CALLBACK (rhythmdb_query_model_entry_changed_cb),
612 model, 0);
613 g_signal_connect_object (G_OBJECT (model->priv->db),
614 "entry_deleted",
615 G_CALLBACK (rhythmdb_query_model_entry_deleted_cb),
616 model, 0);
618 return G_OBJECT (model);
621 static void
622 rhythmdb_query_model_dispose (GObject *object)
624 RhythmDBQueryModel *model;
626 g_return_if_fail (object != NULL);
627 g_return_if_fail (RHYTHMDB_IS_QUERY_MODEL (object));
629 model = RHYTHMDB_QUERY_MODEL (object);
631 g_return_if_fail (model->priv != NULL);
633 rb_debug ("disposing query model %p", object);
635 if (model->priv->base_model) {
636 g_signal_handlers_disconnect_by_func (G_OBJECT (model->priv->base_model),
637 G_CALLBACK (rhythmdb_query_model_base_row_inserted),
638 model);
639 g_signal_handlers_disconnect_by_func (G_OBJECT (model->priv->base_model),
640 G_CALLBACK (rhythmdb_query_model_base_row_deleted),
641 model);
642 g_signal_handlers_disconnect_by_func (G_OBJECT (model->priv->base_model),
643 G_CALLBACK (rhythmdb_query_model_base_non_entry_dropped),
644 model);
645 g_signal_handlers_disconnect_by_func (G_OBJECT (model->priv->base_model),
646 G_CALLBACK (rhythmdb_query_model_base_complete),
647 model);
648 g_signal_handlers_disconnect_by_func (G_OBJECT (model->priv->base_model),
649 G_CALLBACK (rhythmdb_query_model_base_rows_reordered),
650 model);
651 g_signal_handlers_disconnect_by_func (G_OBJECT (model->priv->base_model),
652 G_CALLBACK (rhythmdb_query_model_base_entry_removed),
653 model);
654 g_signal_handlers_disconnect_by_func (G_OBJECT (model->priv->base_model),
655 G_CALLBACK (rhythmdb_query_model_base_entry_prop_changed),
656 model);
657 g_object_unref (model->priv->base_model);
658 model->priv->base_model = NULL;
661 G_OBJECT_CLASS (rhythmdb_query_model_parent_class)->dispose (object);
664 static void
665 rhythmdb_query_model_finalize (GObject *object)
667 RhythmDBQueryModel *model;
669 g_return_if_fail (object != NULL);
670 g_return_if_fail (RHYTHMDB_IS_QUERY_MODEL (object));
672 model = RHYTHMDB_QUERY_MODEL (object);
674 g_return_if_fail (model->priv != NULL);
676 rb_debug ("finalizing query model %p", object);
678 g_hash_table_destroy (model->priv->reverse_map);
679 g_sequence_free (model->priv->entries);
681 g_hash_table_destroy (model->priv->limited_reverse_map);
682 g_sequence_free (model->priv->limited_entries);
684 if (model->priv->query)
685 rhythmdb_query_free (model->priv->query);
686 if (model->priv->original_query)
687 rhythmdb_query_free (model->priv->original_query);
689 if (model->priv->sort_data_destroy && model->priv->sort_data)
690 model->priv->sort_data_destroy (model->priv->sort_data);
692 if (model->priv->limit_value)
693 g_value_array_free (model->priv->limit_value);
695 if (model->priv->query_reapply_timeout_id)
696 g_source_remove (model->priv->query_reapply_timeout_id);
698 G_OBJECT_CLASS (rhythmdb_query_model_parent_class)->finalize (object);
701 RhythmDBQueryModel *
702 rhythmdb_query_model_new (RhythmDB *db,
703 GPtrArray *query,
704 GCompareDataFunc sort_func,
705 gpointer sort_data,
706 GDestroyNotify sort_data_destroy,
707 gboolean sort_reverse)
709 RhythmDBQueryModel *model = g_object_new (RHYTHMDB_TYPE_QUERY_MODEL,
710 "db", db, "query", query,
711 "sort-func", sort_func,
712 "sort-data", sort_data,
713 "sort-data-destroy", sort_data_destroy,
714 "sort-reverse", sort_reverse,
715 NULL);
717 g_return_val_if_fail (model->priv != NULL, NULL);
719 return model;
722 RhythmDBQueryModel *
723 rhythmdb_query_model_new_empty (RhythmDB *db)
725 return g_object_new (RHYTHMDB_TYPE_QUERY_MODEL,
726 "db", db, NULL);
729 void
730 rhythmdb_query_model_copy_contents (RhythmDBQueryModel *dest,
731 RhythmDBQueryModel *src)
733 GSequencePtr ptr, next;
734 RhythmDBEntry *entry;
736 if (src->priv->entries == NULL)
737 return;
739 ptr = g_sequence_get_begin_ptr (src->priv->entries);
740 while (!g_sequence_ptr_is_end (ptr)) {
741 next = g_sequence_ptr_next (ptr);
742 entry = (RhythmDBEntry *)g_sequence_ptr_get_data (ptr);
743 if (dest->priv->query == NULL ||
744 rhythmdb_evaluate_query (dest->priv->db, dest->priv->query, entry)) {
745 if (dest->priv->show_hidden || (rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN) == FALSE))
746 rhythmdb_query_model_do_insert (dest, entry, -1);
749 ptr = next;
753 void
754 rhythmdb_query_model_chain (RhythmDBQueryModel *model,
755 RhythmDBQueryModel *base,
756 gboolean import_entries)
758 rb_debug ("query model %p chaining to base model %p", model, base);
760 if (model->priv->base_model != NULL) {
761 g_signal_handlers_disconnect_by_func (model->priv->base_model,
762 G_CALLBACK (rhythmdb_query_model_base_row_inserted),
763 model);
764 g_signal_handlers_disconnect_by_func (model->priv->base_model,
765 G_CALLBACK (rhythmdb_query_model_base_row_deleted),
766 model);
767 g_signal_handlers_disconnect_by_func (model->priv->base_model,
768 G_CALLBACK (rhythmdb_query_model_base_non_entry_dropped),
769 model);
770 g_signal_handlers_disconnect_by_func (model->priv->base_model,
771 G_CALLBACK (rhythmdb_query_model_base_complete),
772 model);
773 g_signal_handlers_disconnect_by_func (model->priv->base_model,
774 G_CALLBACK (rhythmdb_query_model_base_rows_reordered),
775 model);
776 g_signal_handlers_disconnect_by_func (model->priv->base_model,
777 G_CALLBACK (rhythmdb_query_model_base_entry_removed),
778 model);
779 g_signal_handlers_disconnect_by_func (model->priv->base_model,
780 G_CALLBACK (rhythmdb_query_model_base_entry_prop_changed),
781 model);
782 g_object_unref (model->priv->base_model);
785 model->priv->base_model = base;
787 if (model->priv->base_model != NULL) {
788 g_object_ref (model->priv->base_model);
790 g_assert (model->priv->base_model->priv->db == model->priv->db);
792 g_signal_connect_object (model->priv->base_model,
793 "row-inserted",
794 G_CALLBACK (rhythmdb_query_model_base_row_inserted),
795 model, 0);
796 g_signal_connect_object (model->priv->base_model,
797 "row-deleted",
798 G_CALLBACK (rhythmdb_query_model_base_row_deleted),
799 model, 0);
800 g_signal_connect_object (model->priv->base_model,
801 "non-entry-dropped",
802 G_CALLBACK (rhythmdb_query_model_base_non_entry_dropped),
803 model, 0);
804 g_signal_connect_object (model->priv->base_model,
805 "complete",
806 G_CALLBACK (rhythmdb_query_model_base_complete),
807 model, 0);
808 g_signal_connect_object (model->priv->base_model,
809 "rows-reordered",
810 G_CALLBACK (rhythmdb_query_model_base_rows_reordered),
811 model, 0);
812 g_signal_connect_object (model->priv->base_model,
813 "entry-removed",
814 G_CALLBACK (rhythmdb_query_model_base_entry_removed),
815 model, 0);
816 g_signal_connect_object (model->priv->base_model,
817 "entry-prop-changed",
818 G_CALLBACK (rhythmdb_query_model_base_entry_prop_changed),
819 model, 0);
821 if (import_entries)
822 rhythmdb_query_model_copy_contents (model, model->priv->base_model);
826 gboolean
827 rhythmdb_query_model_has_pending_changes (RhythmDBQueryModel *model)
829 gboolean result;
831 result = g_atomic_int_get (&model->priv->pending_update_count) > 0;
832 if (model->priv->base_model)
833 result |= rhythmdb_query_model_has_pending_changes (model->priv->base_model);
835 return result;
838 static void
839 rhythmdb_query_model_entry_added_cb (RhythmDB *db,
840 RhythmDBEntry *entry,
841 RhythmDBQueryModel *model)
843 int index = -1;
844 gboolean insert = FALSE;
845 if (!model->priv->show_hidden && rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN)) {
846 return;
849 /* check if it's in the base model */
850 if (model->priv->base_model) {
851 if (g_hash_table_lookup (model->priv->base_model->priv->reverse_map, entry) == NULL) {
852 return;
856 if (model->priv->query != NULL) {
857 insert = rhythmdb_evaluate_query (db, model->priv->query, entry);
858 } else {
859 index = GPOINTER_TO_INT (g_hash_table_lookup (model->priv->hidden_entry_map, entry));
860 insert = g_hash_table_remove (model->priv->hidden_entry_map, entry);
861 if (insert)
862 rb_debug ("adding unhidden entry at index %d", index);
865 if (insert) {
866 rhythmdb_query_model_do_insert (model, entry, index);
870 static void
871 rhythmdb_query_model_entry_changed_cb (RhythmDB *db,
872 RhythmDBEntry *entry,
873 GSList *changes,
874 RhythmDBQueryModel *model)
876 gboolean hidden = FALSE;
877 GSList *t;
879 hidden = (!model->priv->show_hidden && rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN));
881 if (g_hash_table_lookup (model->priv->reverse_map, entry) == NULL) {
882 if (hidden == FALSE) {
883 /* the changed entry may now satisfy the query
884 * so we test it */
885 rhythmdb_query_model_entry_added_cb (db, entry, model);
887 return;
890 if (hidden) {
891 /* emit an entry-prop-changed signal so property models
892 * can be updated correctly. if we have a base model,
893 * we'll propagate the parent's signal instead.
895 if (model->priv->base_model == NULL) {
896 GValue true_val = { 0, };
897 GValue false_val = { 0, };
899 g_value_init (&true_val, G_TYPE_BOOLEAN);
900 g_value_set_boolean (&true_val, TRUE);
901 g_value_init (&false_val, G_TYPE_BOOLEAN);
902 g_value_set_boolean (&false_val, FALSE);
904 rb_debug ("emitting hidden-removal notification for %s",
905 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
906 g_signal_emit (G_OBJECT (model),
907 rhythmdb_query_model_signals[ENTRY_PROP_CHANGED], 0,
908 entry, RHYTHMDB_PROP_HIDDEN, &false_val, &true_val);
909 g_value_unset (&true_val);
910 g_value_unset (&false_val);
913 /* if we don't have a query to help us decide, we need to
914 * track hidden entries that were in this query model,
915 * so we can add them back in if they become visible again.
916 * if we have a query, any entry that matches will be added.
918 if (model->priv->query == NULL) {
919 GtkTreeIter iter;
920 GtkTreePath *path;
921 gint index;
923 /* find the entry's position so we can restore it there
924 * if it reappears
926 g_assert (rhythmdb_query_model_entry_to_iter (model, entry, &iter));
927 path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
928 index = gtk_tree_path_get_indices (path)[0];
929 rb_debug ("adding hidden entry to map with index %d", index);
931 g_hash_table_insert (model->priv->hidden_entry_map,
932 rhythmdb_entry_ref (entry),
933 GINT_TO_POINTER (index));
936 rhythmdb_query_model_filter_out_entry (model, entry);
937 return;
940 /* emit separate change signals for each property
941 * unless this is a chained query model, in which
942 * case we propagate the parent model's signals instead.
944 for (t = changes; t; t = t->next) {
945 RhythmDBEntryChange *change = t->data;
947 if (model->priv->base_model == NULL) {
948 g_signal_emit (G_OBJECT (model),
949 rhythmdb_query_model_signals[ENTRY_PROP_CHANGED], 0,
950 entry, change->prop, &change->old, &change->new);
953 if (change->prop == RHYTHMDB_PROP_DURATION) {
954 model->priv->total_duration -= g_value_get_ulong (&change->old);
955 model->priv->total_duration += g_value_get_ulong (&change->new);
956 } else if (change->prop == RHYTHMDB_PROP_FILE_SIZE) {
957 model->priv->total_size -= g_value_get_uint64 (&change->old);
958 model->priv->total_size += g_value_get_uint64 (&change->new);
962 if (model->priv->query &&
963 !rhythmdb_evaluate_query (db, model->priv->query, entry)) {
964 rhythmdb_query_model_filter_out_entry (model, entry);
965 return;
968 /* it may have moved, so we can't just emit a changed entry */
969 if (!rhythmdb_query_model_do_reorder (model, entry)) {
970 /* but if it didn't, we can */
971 GtkTreeIter iter;
972 GtkTreePath *path;
974 if (rhythmdb_query_model_entry_to_iter (model, entry, &iter)) {
975 path = rhythmdb_query_model_get_path (GTK_TREE_MODEL (model),
976 &iter);
977 gtk_tree_model_row_changed (GTK_TREE_MODEL (model),
978 path, &iter);
979 gtk_tree_path_free (path);
984 static void
985 rhythmdb_query_model_base_entry_prop_changed (RhythmDBQueryModel *base_model,
986 RhythmDBEntry *entry,
987 RhythmDBPropType prop,
988 const GValue *old,
989 const GValue *new_value,
990 RhythmDBQueryModel *model)
992 if (g_hash_table_lookup (model->priv->reverse_map, entry)) {
993 /* propagate the signal */
994 g_signal_emit (G_OBJECT (model),
995 rhythmdb_query_model_signals[ENTRY_PROP_CHANGED], 0,
996 entry, prop, old, new_value);
1000 static void
1001 rhythmdb_query_model_entry_deleted_cb (RhythmDB *db,
1002 RhythmDBEntry *entry,
1003 RhythmDBQueryModel *model)
1006 if (g_hash_table_lookup (model->priv->reverse_map, entry) ||
1007 g_hash_table_lookup (model->priv->limited_reverse_map, entry))
1008 rhythmdb_query_model_remove_entry (model, entry);
1011 static gboolean
1012 idle_process_update_idle (struct RhythmDBQueryModelUpdate *update)
1014 GDK_THREADS_ENTER ();
1015 idle_process_update (update);
1016 GDK_THREADS_LEAVE ();
1017 return FALSE;
1020 static void
1021 rhythmdb_query_model_process_update (struct RhythmDBQueryModelUpdate *update)
1023 g_atomic_int_inc (&update->model->priv->pending_update_count);
1024 if (rb_is_main_thread ())
1025 idle_process_update (update);
1026 else
1027 g_idle_add ((GSourceFunc) idle_process_update_idle, update);
1030 static void
1031 idle_process_update (struct RhythmDBQueryModelUpdate *update)
1033 switch (update->type) {
1034 case RHYTHMDB_QUERY_MODEL_UPDATE_ROWS_INSERTED:
1036 guint i;
1038 rb_debug ("inserting %d rows", update->entrydata.entries->len);
1040 for (i = 0; i < update->entrydata.entries->len; i++ ) {
1041 RhythmDBEntry *entry = g_ptr_array_index (update->entrydata.entries, i);
1043 if (update->model->priv->show_hidden || !rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN)) {
1044 RhythmDBQueryModel *base_model = update->model->priv->base_model;
1045 if (base_model &&
1046 g_hash_table_lookup (base_model->priv->reverse_map, entry) == NULL)
1047 continue;
1049 rhythmdb_query_model_do_insert (update->model, entry, -1);
1052 rhythmdb_entry_unref (entry);
1055 g_ptr_array_free (update->entrydata.entries, TRUE);
1057 break;
1059 case RHYTHMDB_QUERY_MODEL_UPDATE_ROW_INSERTED_INDEX:
1061 rb_debug ("inserting row at index %d", update->entrydata.data.index);
1062 rhythmdb_query_model_do_insert (update->model, update->entrydata.data.entry, update->entrydata.data.index);
1063 rhythmdb_entry_unref (update->entrydata.data.entry);
1064 break;
1066 case RHYTHMDB_QUERY_MODEL_UPDATE_QUERY_COMPLETE:
1067 g_signal_emit (G_OBJECT (update->model), rhythmdb_query_model_signals[COMPLETE], 0);
1068 break;
1071 g_atomic_int_add (&update->model->priv->pending_update_count, -1);
1072 g_object_unref (update->model);
1073 g_free (update);
1076 void
1077 rhythmdb_query_model_add_entry (RhythmDBQueryModel *model,
1078 RhythmDBEntry *entry,
1079 gint index)
1081 struct RhythmDBQueryModelUpdate *update;
1083 if (!model->priv->show_hidden && rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN)) {
1084 rb_debug ("attempting to add hidden entry");
1085 return;
1088 if (model->priv->base_model) {
1089 /* add it to the base model, which will cause it to be added to this one */
1090 rhythmdb_query_model_add_entry (model->priv->base_model, entry,
1091 rhythmdb_query_model_child_index_to_base_index (model, index));
1092 return;
1095 rb_debug ("inserting entry %p at index %d", entry, index);
1097 update = g_new (struct RhythmDBQueryModelUpdate, 1);
1098 update->type = RHYTHMDB_QUERY_MODEL_UPDATE_ROW_INSERTED_INDEX;
1099 update->entrydata.data.entry = entry;
1100 update->entrydata.data.index = index;
1101 update->model = model;
1103 /* take references; released in update idle */
1104 g_object_ref (model);
1106 rhythmdb_entry_ref (entry);
1108 rhythmdb_query_model_process_update (update);
1111 guint64
1112 rhythmdb_query_model_get_size (RhythmDBQueryModel *model)
1114 return model->priv->total_size;
1117 long
1118 rhythmdb_query_model_get_duration (RhythmDBQueryModel *model)
1120 return model->priv->total_duration;
1123 static void
1124 rhythmdb_query_model_insert_into_main_list (RhythmDBQueryModel *model,
1125 RhythmDBEntry *entry,
1126 gint index)
1128 GSequencePtr ptr;
1130 /* take reference; released when removed from hash */
1131 rhythmdb_entry_ref (entry);
1133 if (model->priv->sort_func) {
1134 GCompareDataFunc sort_func;
1135 gpointer sort_data;
1136 struct ReverseSortData reverse_data;
1138 if (model->priv->sort_reverse) {
1139 sort_func = (GCompareDataFunc) _reverse_sorting_func;
1140 sort_data = &reverse_data;
1141 reverse_data.func = model->priv->sort_func;
1142 reverse_data.data = model->priv->sort_data;
1143 } else {
1144 sort_func = model->priv->sort_func;
1145 sort_data = model->priv->sort_data;
1148 ptr = g_sequence_insert_sorted (model->priv->entries,
1149 entry,
1150 sort_func,
1151 sort_data);
1152 } else {
1153 if (index == -1) {
1154 ptr = g_sequence_get_end_ptr (model->priv->entries);
1155 } else {
1156 ptr = g_sequence_get_ptr_at_pos (model->priv->entries, index);
1159 g_sequence_insert (ptr, entry);
1160 ptr = g_sequence_ptr_prev (ptr);
1163 /* the hash now owns this reference to the entry */
1164 g_hash_table_insert (model->priv->reverse_map, entry, ptr);
1166 model->priv->total_duration += rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
1167 model->priv->total_size += rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
1170 static void
1171 rhythmdb_query_model_insert_into_limited_list (RhythmDBQueryModel *model,
1172 RhythmDBEntry *entry)
1174 GSequencePtr ptr;
1176 /* take reference; released when removed from hash */
1177 rhythmdb_entry_ref (entry);
1179 if (model->priv->sort_func) {
1180 GCompareDataFunc sort_func;
1181 gpointer sort_data;
1182 struct ReverseSortData reverse_data;
1184 if (model->priv->sort_reverse) {
1185 sort_func = (GCompareDataFunc) _reverse_sorting_func;
1186 sort_data = &reverse_data;
1187 reverse_data.func = model->priv->sort_func;
1188 reverse_data.data = model->priv->sort_data;
1189 } else {
1190 sort_func = model->priv->sort_func;
1191 sort_data = model->priv->sort_data;
1194 ptr = g_sequence_insert_sorted (model->priv->limited_entries, entry,
1195 sort_func,
1196 sort_data);
1197 } else {
1198 ptr = g_sequence_get_end_ptr (model->priv->limited_entries);
1199 g_sequence_insert (ptr, entry);
1200 ptr = g_sequence_ptr_prev (ptr);
1203 /* the hash now owns this reference to the entry */
1204 g_hash_table_insert (model->priv->limited_reverse_map, entry, ptr);
1207 static void
1208 rhythmdb_query_model_remove_from_main_list (RhythmDBQueryModel *model,
1209 RhythmDBEntry *entry)
1211 GSequencePtr ptr;
1212 int index;
1213 GtkTreePath *path;
1215 ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
1216 index = g_sequence_ptr_get_position (ptr);
1218 path = gtk_tree_path_new ();
1219 gtk_tree_path_append_index (path, index);
1221 gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
1222 gtk_tree_path_free (path);
1224 model->priv->total_duration -= rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
1225 model->priv->total_size -= rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
1227 /* take temporary ref */
1228 rhythmdb_entry_ref (entry);
1230 /* find the sequence pointer again in case a row-deleted
1231 * signal handler moved it.
1233 ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
1234 g_sequence_remove (ptr);
1235 g_assert (g_hash_table_remove (model->priv->reverse_map, entry));
1237 g_signal_emit (G_OBJECT (model), rhythmdb_query_model_signals[POST_ENTRY_DELETE], 0, entry);
1239 /* release temporary ref */
1240 rhythmdb_entry_unref (entry);
1243 static void
1244 rhythmdb_query_model_remove_from_limited_list (RhythmDBQueryModel *model,
1245 RhythmDBEntry *entry)
1247 GSequencePtr ptr = g_hash_table_lookup (model->priv->limited_reverse_map, entry);
1249 /* take temporary ref */
1250 rhythmdb_entry_ref (entry);
1251 g_sequence_remove (ptr);
1252 g_hash_table_remove (model->priv->limited_reverse_map, entry);
1253 /* release temporary ref */
1254 rhythmdb_entry_unref (entry);
1257 static void
1258 rhythmdb_query_model_update_limited_entries (RhythmDBQueryModel *model)
1260 RhythmDBEntry *entry;
1261 GSequencePtr ptr;
1263 /* make it fit inside the limits */
1264 while (!rhythmdb_query_model_within_limit (model, NULL)) {
1265 ptr = g_sequence_ptr_prev (g_sequence_get_end_ptr (model->priv->entries));
1266 entry = (RhythmDBEntry*) g_sequence_ptr_get_data (ptr);
1268 /* take temporary ref */
1269 rhythmdb_entry_ref (entry);
1270 rhythmdb_query_model_remove_from_main_list (model, entry);
1271 rhythmdb_query_model_insert_into_limited_list (model, entry);
1272 /* release temporary ref */
1273 rhythmdb_entry_unref (entry);
1276 /* move entries that were previously limited, back to the main list */
1277 while (TRUE) {
1278 GtkTreePath *path;
1279 GtkTreeIter iter;
1281 ptr = g_sequence_get_begin_ptr (model->priv->limited_entries);
1282 if (!ptr || ptr == g_sequence_get_end_ptr (model->priv->limited_entries))
1283 break;
1284 entry = (RhythmDBEntry*) g_sequence_ptr_get_data (ptr);
1285 if (!entry)
1286 break;
1288 if (!rhythmdb_query_model_within_limit (model, entry))
1289 break;
1291 /* take temporary ref */
1292 rhythmdb_entry_ref (entry);
1293 rhythmdb_query_model_remove_from_limited_list (model, entry);
1294 rhythmdb_query_model_insert_into_main_list (model, entry, -1);
1295 /* release temporary ref */
1296 rhythmdb_entry_unref (entry);
1298 ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
1299 iter.stamp = model->priv->stamp;
1300 iter.user_data = ptr;
1301 path = rhythmdb_query_model_get_path (GTK_TREE_MODEL (model),
1302 &iter);
1303 gtk_tree_model_row_inserted (GTK_TREE_MODEL (model),
1304 path, &iter);
1305 gtk_tree_path_free (path);
1309 static gboolean
1310 rhythmdb_query_model_emit_reorder (RhythmDBQueryModel *model,
1311 gint old_pos,
1312 gint new_pos)
1314 int length, i;
1315 gint *reorder_map;
1316 GtkTreePath *path;
1317 GtkTreeIter iter;
1319 if (new_pos == old_pos) {
1320 /* it hasn't moved, don't emit a re-order */
1321 return FALSE;
1324 length = g_sequence_get_length (model->priv->entries);
1325 reorder_map = malloc (length * sizeof(gint));
1327 if (new_pos > old_pos) {
1328 /* it has mover further down the list */
1329 for (i = 0; i < old_pos; i++)
1330 reorder_map[i] = i;
1331 for (i = old_pos; i < new_pos; i++)
1332 reorder_map[i] = i + 1;
1333 reorder_map[new_pos] = old_pos;
1334 for (i = new_pos + 1; i < length; i++)
1335 reorder_map[i] = i;
1336 } else {
1337 /* it has moved up the list */
1338 for (i = 0; i < new_pos; i++)
1339 reorder_map[i] = i;
1340 reorder_map[new_pos] = old_pos;
1341 for (i = new_pos + 1; i < old_pos + 1; i++)
1342 reorder_map[i] = i - 1;
1343 for (i = old_pos + 1; i < length; i++)
1344 reorder_map[i] = i;
1347 gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter);
1348 path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
1350 gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model),
1351 path, &iter,
1352 reorder_map);
1353 gtk_tree_path_free (path);
1354 free (reorder_map);
1355 return TRUE;
1358 static gboolean
1359 rhythmdb_query_model_do_reorder (RhythmDBQueryModel *model,
1360 RhythmDBEntry *entry)
1362 GSequencePtr ptr;
1363 int old_pos, new_pos;
1364 GtkTreePath *path;
1365 GtkTreeIter iter;
1366 GCompareDataFunc sort_func;
1367 gpointer sort_data;
1368 struct ReverseSortData reverse_data;
1370 if (model->priv->sort_func == NULL)
1371 return FALSE;
1373 if (model->priv->sort_reverse) {
1374 sort_func = (GCompareDataFunc) _reverse_sorting_func;
1375 sort_data = &reverse_data;
1376 reverse_data.func = model->priv->sort_func;
1377 reverse_data.data = model->priv->sort_data;
1378 } else {
1379 sort_func = model->priv->sort_func;
1380 sort_data = model->priv->sort_data;
1383 ptr = g_sequence_get_begin_ptr (model->priv->limited_entries);
1385 if (ptr != NULL && !g_sequence_ptr_is_end (ptr)) {
1386 RhythmDBEntry *first_limited = g_sequence_ptr_get_data (ptr);
1387 int cmp = (sort_func) (entry, first_limited, sort_data);
1389 if (cmp > 0) {
1390 /* the entry belongs in the limited list, so we don't need a re-order */
1391 /* take temporary ref */
1392 rhythmdb_entry_ref (entry);
1393 rhythmdb_query_model_remove_entry (model, entry);
1394 rhythmdb_query_model_do_insert (model, entry, -1);
1395 /* release temporary ref */
1396 rhythmdb_entry_unref (entry);
1397 return TRUE;
1401 ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
1402 iter.stamp = model->priv->stamp;
1403 iter.user_data = ptr;
1404 path = rhythmdb_query_model_get_path (GTK_TREE_MODEL (model),
1405 &iter);
1406 gtk_tree_model_row_changed (GTK_TREE_MODEL (model),
1407 path, &iter);
1408 gtk_tree_path_free (path);
1410 /* take a temporary ref */
1411 rhythmdb_entry_ref (entry);
1413 /* it may have moved, check for a re-order */
1414 g_hash_table_remove (model->priv->reverse_map, entry);
1415 old_pos = g_sequence_ptr_get_position (ptr);
1416 g_sequence_remove (ptr);
1418 ptr = g_sequence_insert_sorted (model->priv->entries, entry,
1419 sort_func,
1420 sort_data);
1421 new_pos = g_sequence_ptr_get_position (ptr);
1423 /* the hash now owns this reference to the entry */
1424 g_hash_table_insert (model->priv->reverse_map, entry, ptr);
1426 return rhythmdb_query_model_emit_reorder (model, old_pos, new_pos);
1429 static void
1430 rhythmdb_query_model_do_insert (RhythmDBQueryModel *model,
1431 RhythmDBEntry *entry,
1432 gint index)
1434 GSequencePtr ptr;
1435 GtkTreePath *path;
1436 GtkTreeIter iter;
1438 g_assert (model->priv->show_hidden || !rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN));
1440 /* we check again if the entry already exists in the hash table */
1441 if (g_hash_table_lookup (model->priv->reverse_map, entry) != NULL)
1442 return;
1444 /* take temporary ref */
1445 rhythmdb_entry_ref (entry);
1447 ptr = g_hash_table_lookup (model->priv->limited_reverse_map, entry);
1448 if (ptr != NULL) {
1449 rhythmdb_query_model_remove_from_limited_list (model, entry);
1452 rhythmdb_query_model_insert_into_main_list (model, entry, index);
1454 /* release temporary ref */
1455 rhythmdb_entry_unref (entry);
1457 /* it was added to the main list, send out the inserted signal */
1458 ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
1459 iter.stamp = model->priv->stamp;
1460 iter.user_data = ptr;
1461 path = rhythmdb_query_model_get_path (GTK_TREE_MODEL (model),
1462 &iter);
1463 gtk_tree_model_row_inserted (GTK_TREE_MODEL (model),
1464 path, &iter);
1465 gtk_tree_path_free (path);
1467 rhythmdb_query_model_update_limited_entries (model);
1470 static void
1471 rhythmdb_query_model_filter_out_entry (RhythmDBQueryModel *model,
1472 RhythmDBEntry *entry)
1474 GSequencePtr ptr;
1476 ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
1477 if (ptr != NULL) {
1478 rhythmdb_query_model_remove_from_main_list (model, entry);
1479 rhythmdb_query_model_update_limited_entries (model);
1480 return;
1483 ptr = g_hash_table_lookup (model->priv->limited_reverse_map, entry);
1484 if (ptr != NULL) {
1485 rhythmdb_query_model_remove_from_limited_list (model, entry);
1486 rhythmdb_query_model_update_limited_entries (model);
1487 return;
1491 void
1492 rhythmdb_query_model_move_entry (RhythmDBQueryModel *model,
1493 RhythmDBEntry *entry,
1494 gint index)
1496 GSequencePtr ptr;
1497 GSequencePtr nptr;
1498 gint old_pos;
1500 ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
1501 if (ptr == NULL)
1502 return;
1504 nptr = g_sequence_get_ptr_at_pos (model->priv->entries, index);
1505 if ((nptr == NULL) || (ptr == nptr))
1506 return;
1508 /* take temporary ref */
1509 rhythmdb_entry_ref (entry);
1511 /* remove from old position */
1512 old_pos = g_sequence_ptr_get_position (ptr);
1513 g_sequence_remove (ptr);
1514 g_hash_table_remove (model->priv->reverse_map, entry);
1516 /* insert into new position */
1517 g_sequence_insert (nptr, entry);
1518 ptr = g_sequence_ptr_prev (nptr);
1520 /* the hash now owns this reference to the entry */
1521 g_hash_table_insert (model->priv->reverse_map, entry, ptr);
1523 rhythmdb_query_model_emit_reorder (model, old_pos, index);
1526 gboolean
1527 rhythmdb_query_model_remove_entry (RhythmDBQueryModel *model,
1528 RhythmDBEntry *entry)
1530 gboolean present = (g_hash_table_lookup (model->priv->reverse_map, entry) == NULL) ||
1531 (g_hash_table_lookup (model->priv->limited_reverse_map, entry) == NULL);
1532 g_return_val_if_fail (present, FALSE);
1534 if (model->priv->base_model != NULL)
1535 return rhythmdb_query_model_remove_entry (model->priv->base_model, entry);
1537 /* emit entry-removed, so listeners know the
1538 * entry has actually been removed, rather than filtered
1539 * out.
1541 g_signal_emit (G_OBJECT (model),
1542 rhythmdb_query_model_signals[ENTRY_REMOVED], 0,
1543 entry);
1544 rhythmdb_query_model_filter_out_entry (model, entry);
1546 return TRUE;
1549 gboolean
1550 rhythmdb_query_model_entry_to_iter (RhythmDBQueryModel *model,
1551 RhythmDBEntry *entry,
1552 GtkTreeIter *iter)
1554 GSequencePtr ptr;
1556 ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
1558 if (G_UNLIKELY (ptr == NULL)) {
1559 /* Invalidate iterator so future uses break quickly. */
1560 iter->stamp = !(model->priv->stamp);
1561 return FALSE;
1564 iter->stamp = model->priv->stamp;
1565 iter->user_data = ptr;
1566 return TRUE;
1569 RhythmDBEntry *
1570 rhythmdb_query_model_tree_path_to_entry (RhythmDBQueryModel *model,
1571 GtkTreePath *path)
1573 GtkTreeIter entry_iter;
1575 g_assert (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &entry_iter, path));
1576 return rhythmdb_query_model_iter_to_entry (model, &entry_iter);
1579 RhythmDBEntry *
1580 rhythmdb_query_model_iter_to_entry (RhythmDBQueryModel *model,
1581 GtkTreeIter *entry_iter)
1583 RhythmDBEntry *entry;
1584 gtk_tree_model_get (GTK_TREE_MODEL (model), entry_iter, 0, &entry, -1);
1585 return entry;
1588 RhythmDBEntry *
1589 rhythmdb_query_model_get_next_from_entry (RhythmDBQueryModel *model,
1590 RhythmDBEntry *entry)
1592 GtkTreeIter iter;
1594 g_return_val_if_fail (entry != NULL, NULL);
1596 if (entry && rhythmdb_query_model_entry_to_iter (model, entry, &iter)) {
1597 if (!gtk_tree_model_iter_next (GTK_TREE_MODEL (model), &iter))
1598 return NULL;
1599 } else {
1600 /* If the entry isn't in the model, the "next" entry is the first. */
1601 if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter))
1602 return NULL;
1605 return rhythmdb_query_model_iter_to_entry (model, &iter);
1608 RhythmDBEntry *
1609 rhythmdb_query_model_get_previous_from_entry (RhythmDBQueryModel *model,
1610 RhythmDBEntry *entry)
1612 GtkTreeIter iter;
1613 GtkTreePath *path;
1615 g_return_val_if_fail (entry != NULL, NULL);
1617 if (!rhythmdb_query_model_entry_to_iter (model, entry, &iter))
1618 return NULL;
1620 path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
1621 g_assert (path);
1622 if (!gtk_tree_path_prev (path)) {
1623 gtk_tree_path_free (path);
1624 return NULL;
1627 g_assert (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path));
1628 gtk_tree_path_free (path);
1630 return rhythmdb_query_model_iter_to_entry (model, &iter);
1633 static gboolean
1634 rhythmdb_query_model_row_draggable (RbTreeDragSource *dragsource,
1635 GList *paths)
1637 return TRUE;
1640 static gboolean
1641 rhythmdb_query_model_drag_data_delete (RbTreeDragSource *dragsource,
1642 GList *paths)
1644 RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (dragsource);
1645 GtkTreeModel *treemodel = GTK_TREE_MODEL (model);
1647 /* we don't delete if it is a reorder drag and drop because the deletion already
1648 occured in rhythmdb_query_model_drag_data_received */
1649 if (model->priv->sort_func == NULL && !model->priv->reorder_drag_and_drop) {
1651 RhythmDBEntry *entry;
1652 GtkTreeIter iter;
1653 GtkTreePath *path;
1655 for (; paths; paths = paths->next) {
1657 path = gtk_tree_row_reference_get_path (paths->data);
1659 if (path) {
1660 if (rhythmdb_query_model_get_iter (treemodel, &iter, path)) {
1661 entry = g_sequence_ptr_get_data (iter.user_data);
1662 rhythmdb_query_model_remove_entry (model, entry);
1664 gtk_tree_path_free (path);
1669 model->priv->reorder_drag_and_drop = FALSE;
1670 return TRUE;
1674 static gboolean
1675 rhythmdb_query_model_drag_data_get (RbTreeDragSource *dragsource,
1676 GList *paths,
1677 GtkSelectionData *selection_data)
1679 RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (dragsource);
1680 RhythmDBEntry *entry;
1681 GString *data;
1682 guint target;
1683 GList *tem;
1684 gboolean need_newline = FALSE;
1686 rb_debug ("getting drag data");
1688 if (!gtk_target_list_find (rhythmdb_query_model_drag_target_list,
1689 selection_data->target, &target)) {
1690 return FALSE;
1694 data = g_string_new ("");
1696 for (tem = paths; tem; tem = tem->next) {
1697 GtkTreeIter iter;
1698 GtkTreePath *path;
1700 path = gtk_tree_row_reference_get_path (tem->data);
1702 gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path);
1704 entry = g_sequence_ptr_get_data (iter.user_data);
1706 if (need_newline)
1707 g_string_append (data, "\r\n");
1709 if (target == TARGET_URIS) {
1710 char *location;
1712 location = rhythmdb_entry_get_playback_uri (entry);
1713 if (location == NULL) {
1714 need_newline = FALSE;
1715 continue;
1718 g_string_append (data, location);
1719 g_free (location);
1720 } else if (target == TARGET_ENTRIES) {
1721 g_string_append_printf (data,
1722 "%lu",
1723 rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_ENTRY_ID));
1725 need_newline = TRUE;
1728 gtk_selection_data_set (selection_data,
1729 selection_data->target,
1730 8, (guchar *) data->str,
1731 data->len);
1733 g_string_free (data, TRUE);
1735 return TRUE;
1738 static gboolean
1739 rhythmdb_query_model_drag_data_received (RbTreeDragDest *drag_dest,
1740 GtkTreePath *dest,
1741 GtkTreeViewDropPosition pos,
1742 GtkSelectionData *selection_data)
1744 RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (drag_dest);
1746 if (model->priv->base_model) {
1747 GtkTreeIter base_iter;
1748 GtkTreePath *base_dest;
1749 RhythmDBEntry *entry;
1750 gboolean result;
1752 if (dest) {
1753 entry = rhythmdb_query_model_tree_path_to_entry (model, dest);
1754 g_assert (entry);
1755 rhythmdb_query_model_entry_to_iter (model->priv->base_model, entry, &base_iter);
1756 base_dest = gtk_tree_model_get_path (GTK_TREE_MODEL (model->priv->base_model), &base_iter);
1757 rhythmdb_entry_unref (entry);
1758 } else {
1759 base_dest = NULL;
1762 result = rhythmdb_query_model_drag_data_received ((RbTreeDragDest*)model->priv->base_model,
1763 base_dest, pos, selection_data);
1764 if (base_dest)
1765 gtk_tree_path_free (base_dest);
1767 return result;
1770 rb_debug ("drag received");
1772 if (model->priv->sort_func != NULL)
1773 return FALSE;
1775 if (selection_data->format == 8 && selection_data->length >= 0) {
1776 GtkTreeIter iter;
1777 GSequencePtr ptr;
1778 char **strv;
1779 RhythmDBEntry *entry;
1780 gboolean uri_list;
1781 int i = 0;
1783 uri_list = (selection_data->type == gdk_atom_intern ("text/uri-list", TRUE));
1785 strv = g_strsplit ((char *) selection_data->data, "\r\n", -1);
1787 if (dest == NULL || !rhythmdb_query_model_get_iter (GTK_TREE_MODEL (model), &iter, dest))
1788 ptr = g_sequence_get_end_ptr (model->priv->entries);
1789 else
1790 ptr = iter.user_data;
1792 if (pos == GTK_TREE_VIEW_DROP_AFTER)
1793 ptr = g_sequence_ptr_next (ptr);
1795 for (; strv[i]; i++) {
1796 GSequencePtr tem_ptr;
1797 GtkTreeIter tem_iter;
1799 if (g_utf8_strlen (strv[i], -1) == 0)
1800 continue;
1802 entry = rhythmdb_entry_lookup_from_string (model->priv->db, strv[i], !uri_list);
1803 if (entry == NULL) {
1804 int pos;
1806 if (uri_list) {
1807 if (g_sequence_ptr_is_end (ptr))
1808 pos = -1;
1809 else
1810 pos = g_sequence_ptr_get_position (ptr);
1812 g_signal_emit (G_OBJECT (model),
1813 rhythmdb_query_model_signals[NON_ENTRY_DROPPED],
1814 0, strv[i], pos);
1815 } else {
1816 rb_debug ("got drop with entry id %s, but can't find the entry", strv[i]);
1818 } else {
1819 GSequencePtr old_ptr;
1820 GtkTreePath *tem_path;
1821 gint old_pos = 0;
1822 gint new_pos;
1824 old_ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
1825 /* trying to drag drop an entry on itself ! */
1826 if (old_ptr == ptr)
1827 continue;
1829 /* take temporary ref */
1830 rhythmdb_entry_ref (entry);
1832 /* the entry already exists it is either a reorder drag and drop
1833 (or a drag and drop from another application), so we delete
1834 the existing one before adding it again. */
1835 if (old_ptr) {
1836 model->priv->reorder_drag_and_drop = TRUE;
1838 old_pos = g_sequence_ptr_get_position (old_ptr);
1839 g_sequence_remove (old_ptr);
1840 g_assert (g_hash_table_remove (model->priv->reverse_map, entry));
1841 } else {
1842 model->priv->reorder_drag_and_drop = FALSE;
1845 g_sequence_insert (ptr, entry);
1847 tem_ptr = g_sequence_ptr_prev (ptr);
1848 new_pos = g_sequence_ptr_get_position (tem_ptr);
1850 tem_iter.stamp = model->priv->stamp;
1851 tem_iter.user_data = tem_ptr;
1852 /* the hash now owns this reference to the entry */
1853 g_hash_table_insert (model->priv->reverse_map, entry, tem_ptr);
1855 if (old_ptr) {
1856 rb_debug ("moving entry %p from %d to %d", entry, old_pos, new_pos);
1857 rhythmdb_query_model_emit_reorder (model, old_pos, new_pos);
1858 } else {
1859 tem_path = rhythmdb_query_model_get_path (GTK_TREE_MODEL (model),
1860 &tem_iter);
1861 gtk_tree_model_row_inserted (GTK_TREE_MODEL (model),
1862 tem_path, &tem_iter);
1863 gtk_tree_path_free (tem_path);
1868 g_strfreev (strv);
1869 return TRUE;
1871 return FALSE;
1874 static gboolean
1875 rhythmdb_query_model_row_drop_possible (RbTreeDragDest *drag_dest,
1876 GtkTreePath *dest,
1877 GtkTreeViewDropPosition pos,
1878 GtkSelectionData *selection_data)
1880 RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (drag_dest);
1882 while (model) {
1883 if (model->priv->sort_func != NULL)
1884 return FALSE;
1886 model = model->priv->base_model;
1888 return TRUE;
1891 static gboolean
1892 rhythmdb_query_model_row_drop_position (RbTreeDragDest *drag_dest,
1893 GtkTreePath *dest_path,
1894 GList *targets,
1895 GtkTreeViewDropPosition *pos)
1897 return TRUE;
1900 static void
1901 rhythmdb_query_model_set_query (RhythmDBQueryResults *results, GPtrArray *query)
1903 g_object_set (G_OBJECT (results), "query", query, NULL);
1906 /* Threading: Called from the database query thread for async queries,
1907 * from the main thread for synchronous queries.
1909 static void
1910 rhythmdb_query_model_add_results (RhythmDBQueryResults *results,
1911 GPtrArray *entries)
1913 RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (results);
1914 struct RhythmDBQueryModelUpdate *update;
1915 guint i;
1917 rb_debug ("adding %d entries", entries->len);
1919 update = g_new (struct RhythmDBQueryModelUpdate, 1);
1920 update->type = RHYTHMDB_QUERY_MODEL_UPDATE_ROWS_INSERTED;
1921 update->entrydata.entries = entries;
1922 update->model = model;
1924 /* take references; released in update idle */
1925 g_object_ref (model);
1927 for (i = 0; i < update->entrydata.entries->len; i++) {
1928 rhythmdb_entry_ref (g_ptr_array_index (update->entrydata.entries, i));
1931 rhythmdb_query_model_process_update (update);
1934 static void
1935 rhythmdb_query_model_query_complete (RhythmDBQueryResults *results)
1937 RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (results);
1938 struct RhythmDBQueryModelUpdate *update;
1940 update = g_new0 (struct RhythmDBQueryModelUpdate, 1);
1941 update->type = RHYTHMDB_QUERY_MODEL_UPDATE_QUERY_COMPLETE;
1942 update->model = model;
1944 /* take reference; released in update idle */
1945 g_object_ref (model);
1947 rhythmdb_query_model_process_update (update);
1950 static GtkTreeModelFlags
1951 rhythmdb_query_model_get_flags (GtkTreeModel *model)
1953 return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY;
1956 static gint
1957 rhythmdb_query_model_get_n_columns (GtkTreeModel *tree_model)
1959 return 2;
1962 static GType
1963 rhythmdb_query_model_get_column_type (GtkTreeModel *tree_model,
1964 int index)
1966 switch (index) {
1967 case 0:
1968 return RHYTHMDB_TYPE_ENTRY;
1969 case 1:
1970 return G_TYPE_INT;
1971 default:
1972 g_assert_not_reached ();
1973 return G_TYPE_INVALID;
1977 static gboolean
1978 rhythmdb_query_model_get_iter (GtkTreeModel *tree_model,
1979 GtkTreeIter *iter,
1980 GtkTreePath *path)
1982 RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (tree_model);
1983 guint index;
1984 GSequencePtr ptr;
1986 index = gtk_tree_path_get_indices (path)[0];
1988 if (index >= g_sequence_get_length (model->priv->entries))
1989 return FALSE;
1991 ptr = g_sequence_get_ptr_at_pos (model->priv->entries, index);
1992 g_assert (ptr);
1994 iter->stamp = model->priv->stamp;
1995 iter->user_data = ptr;
1997 return TRUE;
2000 static GtkTreePath *
2001 rhythmdb_query_model_get_path (GtkTreeModel *tree_model,
2002 GtkTreeIter *iter)
2004 RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (tree_model);
2005 GtkTreePath *path;
2007 g_return_val_if_fail (iter->stamp == model->priv->stamp, NULL);
2009 if (g_sequence_ptr_is_end (iter->user_data))
2010 return NULL;
2012 path = gtk_tree_path_new ();
2013 gtk_tree_path_append_index (path, g_sequence_ptr_get_position (iter->user_data));
2014 return path;
2017 static void
2018 rhythmdb_query_model_get_value (GtkTreeModel *tree_model,
2019 GtkTreeIter *iter,
2020 gint column,
2021 GValue *value)
2023 RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (tree_model);
2024 RhythmDBEntry *entry;
2026 g_return_if_fail (!g_sequence_ptr_is_end (iter->user_data));
2027 g_return_if_fail (model->priv->stamp == iter->stamp);
2029 entry = g_sequence_ptr_get_data (iter->user_data);
2031 switch (column) {
2032 case 0:
2033 g_value_init (value, RHYTHMDB_TYPE_ENTRY);
2034 g_value_set_boxed (value, entry);
2035 break;
2036 case 1:
2037 g_value_init (value, G_TYPE_INT);
2038 g_value_set_int (value, g_sequence_ptr_get_position (iter->user_data)+1);
2039 break;
2040 default:
2041 g_assert_not_reached ();
2045 static gboolean
2046 rhythmdb_query_model_iter_next (GtkTreeModel *tree_model,
2047 GtkTreeIter *iter)
2049 RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (tree_model);
2051 g_return_val_if_fail (iter->stamp == model->priv->stamp, FALSE);
2053 iter->user_data = g_sequence_ptr_next (iter->user_data);
2055 return !g_sequence_ptr_is_end (iter->user_data);
2058 static gboolean
2059 rhythmdb_query_model_iter_children (GtkTreeModel *tree_model,
2060 GtkTreeIter *iter,
2061 GtkTreeIter *parent)
2063 RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (tree_model);
2065 if (parent != NULL)
2066 return FALSE;
2068 if (g_sequence_get_length (model->priv->entries) == 0)
2069 return FALSE;
2071 iter->stamp = model->priv->stamp;
2072 iter->user_data = g_sequence_get_begin_ptr (model->priv->entries);
2074 return TRUE;
2077 static gboolean
2078 rhythmdb_query_model_iter_has_child (GtkTreeModel *tree_model,
2079 GtkTreeIter *iter)
2081 return FALSE;
2084 static gint
2085 rhythmdb_query_model_iter_n_children (GtkTreeModel *tree_model,
2086 GtkTreeIter *iter)
2088 RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (tree_model);
2090 if (iter == NULL)
2091 return g_sequence_get_length (model->priv->entries);
2093 g_return_val_if_fail (model->priv->stamp == iter->stamp, -1);
2095 return 0;
2098 static gboolean
2099 rhythmdb_query_model_iter_nth_child (GtkTreeModel *tree_model,
2100 GtkTreeIter *iter,
2101 GtkTreeIter *parent,
2102 gint n)
2104 RhythmDBQueryModel *model = RHYTHMDB_QUERY_MODEL (tree_model);
2105 GSequencePtr child;
2107 if (parent)
2108 return FALSE;
2110 child = g_sequence_get_ptr_at_pos (model->priv->entries, n);
2112 if (g_sequence_ptr_is_end (child))
2113 return FALSE;
2115 iter->stamp = model->priv->stamp;
2116 iter->user_data = child;
2118 return TRUE;
2121 static gboolean
2122 rhythmdb_query_model_iter_parent (GtkTreeModel *tree_model,
2123 GtkTreeIter *iter,
2124 GtkTreeIter *child)
2126 return FALSE;
2129 char *
2130 rhythmdb_query_model_compute_status_normal (RhythmDBQueryModel *model,
2131 const char *singular,
2132 const char *plural)
2134 return rhythmdb_compute_status_normal (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model), NULL),
2135 rhythmdb_query_model_get_duration (model),
2136 rhythmdb_query_model_get_size (model),
2137 singular,
2138 plural);
2141 static void
2142 apply_updated_entry_sequence (RhythmDBQueryModel *model,
2143 GSequence *new_entries)
2145 int *reorder_map;
2146 int length, i;
2147 GtkTreePath *path;
2148 GtkTreeIter iter;
2149 GSequencePtr ptr;
2151 length = g_sequence_get_length (new_entries);
2152 /* generate resort map and rebuild reverse map */
2153 reorder_map = malloc (length * sizeof(gint));
2155 ptr = g_sequence_get_begin_ptr (new_entries);
2156 for (i = 0; i < length; i++) {
2157 gpointer entry = g_sequence_ptr_get_data (ptr);
2158 GSequencePtr old_ptr;
2160 old_ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
2161 reorder_map[i] = g_sequence_ptr_get_position (old_ptr);
2162 g_hash_table_replace (model->priv->reverse_map, rhythmdb_entry_ref (entry), ptr);
2164 ptr = g_sequence_ptr_next (ptr);
2166 g_sequence_free (model->priv->entries);
2167 model->priv->entries = new_entries;
2169 /* emit the re-order and clean up */
2170 gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter);
2171 path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
2172 gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model),
2173 path, &iter,
2174 reorder_map);
2176 gtk_tree_path_free (path);
2177 free (reorder_map);
2180 void
2181 rhythmdb_query_model_set_sort_order (RhythmDBQueryModel *model,
2182 GCompareDataFunc sort_func,
2183 gpointer sort_data,
2184 GDestroyNotify sort_data_destroy,
2185 gboolean sort_reverse)
2187 GSequence *new_entries;
2188 GSequencePtr ptr;
2189 int length, i;
2190 struct ReverseSortData reverse_data;
2192 if ((model->priv->sort_func == sort_func) &&
2193 (model->priv->sort_data == sort_data) &&
2194 (model->priv->sort_data_destroy == sort_data_destroy) &&
2195 (model->priv->sort_reverse == sort_reverse))
2196 return;
2198 g_return_if_fail ((model->priv->limit_type == RHYTHMDB_QUERY_MODEL_LIMIT_NONE) ||
2199 (model->priv->sort_func == NULL));
2200 if (model->priv->sort_func == NULL)
2201 g_assert (g_sequence_get_length (model->priv->limited_entries) == 0);
2203 if (model->priv->sort_data_destroy && model->priv->sort_data)
2204 model->priv->sort_data_destroy (model->priv->sort_data);
2206 model->priv->sort_func = sort_func;
2207 model->priv->sort_data = sort_data;
2208 model->priv->sort_data_destroy = sort_data_destroy;
2209 model->priv->sort_reverse = sort_reverse;
2211 if (model->priv->sort_reverse) {
2212 reverse_data.func = sort_func;
2213 reverse_data.data = sort_data;
2214 sort_func = (GCompareDataFunc) _reverse_sorting_func;
2215 sort_data = &reverse_data;
2218 /* create the new sorted entry sequence */
2219 length = g_sequence_get_length (model->priv->entries);
2220 if (length > 0) {
2221 new_entries = g_sequence_new (NULL);
2222 ptr = g_sequence_get_begin_ptr (model->priv->entries);
2223 for (i = 0; i < length; i++) {
2224 gpointer entry = g_sequence_ptr_get_data (ptr);
2226 g_sequence_insert_sorted (new_entries, entry,
2227 sort_func,
2228 sort_data);
2229 ptr = g_sequence_ptr_next (ptr);
2232 apply_updated_entry_sequence (model, new_entries);
2236 static int
2237 rhythmdb_query_model_child_index_to_base_index (RhythmDBQueryModel *model,
2238 int index)
2240 GSequencePtr ptr;
2241 RhythmDBEntry *entry;
2242 g_assert (model->priv->base_model);
2244 ptr = g_sequence_get_ptr_at_pos (model->priv->entries, index);
2245 if (ptr == NULL || g_sequence_ptr_is_end (ptr))
2246 return -1;
2247 entry = (RhythmDBEntry*)g_sequence_ptr_get_data (ptr);
2249 ptr = g_hash_table_lookup (model->priv->base_model->priv->reverse_map, entry);
2250 g_assert (ptr); /* all child model entries are in the base model */
2252 return g_sequence_ptr_get_position (ptr);
2255 /*static int
2256 rhythmdb_query_model_base_index_to_child_index (RhythmDBQueryModel *model, int index)
2258 GSequencePtr ptr;
2259 RhythmDBEntry *entry;
2260 int pos;
2262 g_assert (model->priv->base_model);
2263 if (index == -1)
2264 return -1;
2266 ptr = g_sequence_get_ptr_at_pos (model->priv->base_model->priv->entries, index);
2267 if (ptr == NULL || g_sequence_ptr_is_end (ptr))
2268 return -1;
2269 entry = (RhythmDBEntry*)g_sequence_ptr_get_data (ptr);
2271 ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
2272 if (ptr == NULL)
2273 return -1;
2275 pos = g_sequence_ptr_get_position (ptr);
2276 return pos;
2279 static int
2280 rhythmdb_query_model_get_entry_index (RhythmDBQueryModel *model,
2281 RhythmDBEntry *entry)
2283 GSequencePtr ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
2285 if (ptr)
2286 return g_sequence_ptr_get_position (ptr);
2287 else
2288 return -1;
2291 static void
2292 rhythmdb_query_model_base_row_inserted (GtkTreeModel *tree_model,
2293 GtkTreePath *path,
2294 GtkTreeIter *iter,
2295 RhythmDBQueryModel *model)
2297 RhythmDBQueryModel *base_model = RHYTHMDB_QUERY_MODEL (tree_model);
2298 RhythmDBEntry *entry;
2299 RhythmDBEntry *prev_entry;
2300 int index;
2302 g_assert (base_model == model->priv->base_model);
2304 entry = rhythmdb_query_model_iter_to_entry (base_model, iter);
2306 if (!model->priv->show_hidden && rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN))
2307 goto out;
2309 if (rhythmdb_evaluate_query (model->priv->db, model->priv->query, entry)) {
2310 /* find the closest previous entry that is in the filter model, and it it after that */
2311 prev_entry = rhythmdb_query_model_get_previous_from_entry (base_model, entry);
2312 while (prev_entry && g_hash_table_lookup (model->priv->reverse_map, prev_entry) == NULL) {
2313 rhythmdb_entry_unref (prev_entry);
2314 prev_entry = rhythmdb_query_model_get_previous_from_entry (base_model, prev_entry);
2317 if (entry != NULL) {
2318 index = rhythmdb_query_model_get_entry_index (model, prev_entry) + 1;
2319 } else {
2320 index = 0;
2323 if (prev_entry != NULL) {
2324 rhythmdb_entry_unref (prev_entry);
2327 rb_debug ("inserting entry %p from base model %p to model %p in position %d", entry, base_model, model, index);
2328 rhythmdb_query_model_do_insert (model, entry, index);
2330 out:
2331 rhythmdb_entry_unref (entry);
2334 static void
2335 rhythmdb_query_model_base_row_deleted (GtkTreeModel *base_model,
2336 GtkTreePath *path,
2337 RhythmDBQueryModel *model)
2339 RhythmDBEntry *entry;
2341 entry = rhythmdb_query_model_tree_path_to_entry (RHYTHMDB_QUERY_MODEL (base_model), path);
2342 rb_debug ("deleting entry %p from base model %p to model %p", entry, base_model, model);
2344 rhythmdb_query_model_filter_out_entry (model, entry);
2345 rhythmdb_entry_unref (entry);
2348 static void
2349 rhythmdb_query_model_base_non_entry_dropped (GtkTreeModel *base_model,
2350 const char *location,
2351 int position,
2352 RhythmDBQueryModel *model)
2354 g_signal_emit (G_OBJECT (model), rhythmdb_query_model_signals[NON_ENTRY_DROPPED], 0,
2355 location, rhythmdb_query_model_child_index_to_base_index (model, position));
2358 static void
2359 rhythmdb_query_model_base_complete (GtkTreeModel *base_model,
2360 RhythmDBQueryModel *model)
2362 g_signal_emit (G_OBJECT (model), rhythmdb_query_model_signals[COMPLETE], 0);
2365 static void
2366 rhythmdb_query_model_base_rows_reordered (GtkTreeModel *base_model,
2367 GtkTreePath *arg1,
2368 GtkTreeIter *arg2,
2369 gint *order_map,
2370 RhythmDBQueryModel *model)
2372 RhythmDBQueryModel *base_query_model = RHYTHMDB_QUERY_MODEL (base_model);
2373 GSequence *new_entries;
2374 GSequencePtr ptr;
2376 /* ignore, if this model sorts */
2377 if (model->priv->sort_func)
2378 return;
2380 new_entries = g_sequence_new (NULL);
2382 /* iterate over the entries in the base, and recreate the sequence */
2383 ptr = g_sequence_get_begin_ptr (base_query_model->priv->entries);
2384 while (!g_sequence_ptr_is_end (ptr)) {
2385 gpointer entry = g_sequence_ptr_get_data (ptr);
2387 if (g_hash_table_lookup (model->priv->reverse_map, entry))
2388 g_sequence_append (new_entries, entry);
2390 ptr = g_sequence_ptr_next (ptr);
2393 apply_updated_entry_sequence (model, new_entries);
2396 static void
2397 rhythmdb_query_model_base_entry_removed (RhythmDBQueryModel *base_model,
2398 RhythmDBEntry *entry,
2399 RhythmDBQueryModel *model)
2401 if (g_hash_table_lookup (model->priv->reverse_map, entry)) {
2402 /* propagate the signal out to any attached property models */
2403 g_signal_emit (G_OBJECT (model),
2404 rhythmdb_query_model_signals[ENTRY_REMOVED], 0,
2405 entry);
2409 void
2410 rhythmdb_query_model_reapply_query (RhythmDBQueryModel *model,
2411 gboolean filter)
2413 GSequencePtr ptr;
2414 GSequencePtr next;
2415 RhythmDBEntry *entry;
2416 gboolean changed = FALSE;
2417 GList *remove = NULL;
2419 /* process limited list first, so entries that don't match can't sneak in
2420 * to the main list from there
2422 if (model->priv->limited_entries) {
2423 ptr = g_sequence_get_begin_ptr (model->priv->limited_entries);
2424 while (!g_sequence_ptr_is_end (ptr)) {
2425 next = g_sequence_ptr_next (ptr);
2426 entry = (RhythmDBEntry *)g_sequence_ptr_get_data (ptr);
2427 if (!rhythmdb_evaluate_query (model->priv->db,
2428 model->priv->query,
2429 entry)) {
2430 remove = g_list_prepend (remove, entry);
2433 ptr = next;
2436 if (remove) {
2437 GList *t;
2438 for (t = remove; t; t = t->next) {
2439 RhythmDBEntry *entry = t->data;
2440 rhythmdb_query_model_remove_from_limited_list (model, entry);
2443 changed = TRUE;
2444 g_list_free (remove);
2445 remove = NULL;
2449 if (model->priv->entries) {
2450 ptr = g_sequence_get_begin_ptr (model->priv->entries);
2451 while (!g_sequence_ptr_is_end (ptr)) {
2452 next = g_sequence_ptr_next (ptr);
2453 entry = (RhythmDBEntry *)g_sequence_ptr_get_data (ptr);
2454 if (!rhythmdb_evaluate_query (model->priv->db,
2455 model->priv->query,
2456 entry)) {
2457 remove = g_list_prepend (remove, entry);
2460 ptr = next;
2463 if (remove) {
2464 GList *t;
2465 for (t = remove; t; t = t->next) {
2466 RhythmDBEntry *entry = t->data;
2467 if (!filter) {
2468 g_signal_emit (G_OBJECT (model),
2469 rhythmdb_query_model_signals[ENTRY_REMOVED], 0,
2470 entry);
2472 rhythmdb_query_model_remove_from_main_list (model, entry);
2475 changed = TRUE;
2476 g_list_free (remove);
2477 remove = NULL;
2481 if (changed)
2482 rhythmdb_query_model_update_limited_entries (model);
2485 static gint
2486 _reverse_sorting_func (gpointer a,
2487 gpointer b,
2488 struct ReverseSortData *reverse_data)
2490 return - reverse_data->func (a, b, reverse_data->data);
2493 gint
2494 rhythmdb_query_model_location_sort_func (RhythmDBEntry *a,
2495 RhythmDBEntry *b,
2496 gpointer data)
2498 const char *a_val;
2499 const char *b_val;
2501 a_val = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_LOCATION);
2502 b_val = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_LOCATION);
2504 if (a_val == NULL) {
2505 if (b_val == NULL)
2506 return 0;
2507 else
2508 return -1;
2509 } else if (b_val == NULL)
2510 return 1;
2511 else
2512 return strcmp (a_val, b_val);
2515 gint
2516 rhythmdb_query_model_title_sort_func (RhythmDBEntry *a,
2517 RhythmDBEntry *b,
2518 gpointer data)
2520 const char *a_val;
2521 const char *b_val;
2522 gint ret;
2524 /* Sort by album name */
2525 a_val = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_TITLE_SORT_KEY);
2526 b_val = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_TITLE_SORT_KEY);
2528 if (a_val == NULL) {
2529 if (b_val == NULL)
2530 ret = 0;
2531 else
2532 ret = -1;
2533 } else if (b_val == NULL)
2534 ret = 1;
2535 else
2536 ret = strcmp (a_val, b_val);
2538 if (ret != 0)
2539 return ret;
2540 else
2541 return rhythmdb_query_model_location_sort_func (a, b, data);
2544 gint
2545 rhythmdb_query_model_album_sort_func (RhythmDBEntry *a,
2546 RhythmDBEntry *b,
2547 gpointer data)
2549 const char *a_val;
2550 const char *b_val;
2551 gulong a_num;
2552 gulong b_num;
2553 gint ret;
2555 /* Sort by album name */
2556 a_val = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_ALBUM_SORT_KEY);
2557 b_val = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_ALBUM_SORT_KEY);
2559 if (a_val == NULL) {
2560 if (b_val == NULL)
2561 ret = 0;
2562 else
2563 ret = -1;
2564 } else if (b_val == NULL)
2565 ret = 1;
2566 else
2567 ret = strcmp (a_val, b_val);
2569 if (ret != 0)
2570 return ret;
2572 /* Then by disc number (assume 1 if non-existent) */
2573 a_num = rhythmdb_entry_get_ulong (a, RHYTHMDB_PROP_DISC_NUMBER);
2574 b_num = rhythmdb_entry_get_ulong (b, RHYTHMDB_PROP_DISC_NUMBER);
2575 a_num = (a_num ? a_num : 1);
2576 b_num = (b_num ? b_num : 1);
2577 if (a_num != b_num)
2578 return (a_num < b_num ? -1 : 1);
2580 /* by track number (assume 0 if non-existent) */
2581 a_num = rhythmdb_entry_get_ulong (a, RHYTHMDB_PROP_TRACK_NUMBER);
2582 b_num = rhythmdb_entry_get_ulong (b, RHYTHMDB_PROP_TRACK_NUMBER);
2583 if (a_num != b_num)
2584 return (a_num < b_num ? -1 : 1);
2586 /* by title */
2587 a_val = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_TITLE_SORT_KEY);
2588 b_val = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_TITLE_SORT_KEY);
2590 if (a_val == NULL) {
2591 if (b_val == NULL)
2592 return 0;
2593 else
2594 return -1;
2595 } else if (b_val == NULL)
2596 return 1;
2597 else
2598 return rhythmdb_query_model_location_sort_func (a, b, data);
2601 gint
2602 rhythmdb_query_model_artist_sort_func (RhythmDBEntry *a,
2603 RhythmDBEntry *b,
2604 gpointer data)
2606 const char *a_val;
2607 const char *b_val;
2608 gint ret;
2610 a_val = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_ARTIST_SORT_KEY);
2611 b_val = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_ARTIST_SORT_KEY);
2613 if (a_val == NULL) {
2614 if (b_val == NULL)
2615 ret = 0;
2616 else
2617 ret = -1;
2618 } else if (b_val == NULL)
2619 ret = 1;
2620 else
2621 ret = strcmp (a_val, b_val);
2623 if (ret != 0)
2624 return ret;
2625 else
2626 return rhythmdb_query_model_album_sort_func (a, b, data);
2629 gint
2630 rhythmdb_query_model_genre_sort_func (RhythmDBEntry *a, RhythmDBEntry *b,
2631 gpointer data)
2633 const char *a_val;
2634 const char *b_val;
2635 gint ret;
2637 a_val = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_GENRE_SORT_KEY);
2638 b_val = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_GENRE_SORT_KEY);
2640 if (a_val == NULL) {
2641 if (b_val == NULL)
2642 ret = 0;
2643 else
2644 ret = -1;
2645 } else if (b_val == NULL)
2646 ret = 1;
2647 else
2648 ret = strcmp (a_val, b_val);
2650 if (ret != 0)
2651 return ret;
2652 else
2653 return rhythmdb_query_model_artist_sort_func (a, b, data);
2656 gint
2657 rhythmdb_query_model_track_sort_func (RhythmDBEntry *a,
2658 RhythmDBEntry *b,
2659 gpointer data)
2661 return rhythmdb_query_model_album_sort_func (a, b, data);
2664 gint
2665 rhythmdb_query_model_double_ceiling_sort_func (RhythmDBEntry *a,
2666 RhythmDBEntry *b,
2667 gpointer data)
2669 gdouble a_val, b_val;
2670 RhythmDBPropType prop_id;
2672 prop_id = (RhythmDBPropType) GPOINTER_TO_INT (data);
2674 a_val = ceil (rhythmdb_entry_get_double (a, prop_id));
2675 b_val = ceil (rhythmdb_entry_get_double (b, prop_id));
2677 if (a_val != b_val)
2678 return (a_val > b_val ? 1 : -1);
2679 else
2680 return rhythmdb_query_model_location_sort_func (a, b, data);
2683 gint
2684 rhythmdb_query_model_ulong_sort_func (RhythmDBEntry *a,
2685 RhythmDBEntry *b,
2686 gpointer data)
2688 gulong a_val, b_val;
2689 RhythmDBPropType prop_id;
2691 prop_id = (RhythmDBPropType) GPOINTER_TO_INT (data);
2692 a_val = rhythmdb_entry_get_ulong (a, prop_id);
2693 b_val = rhythmdb_entry_get_ulong (b, prop_id);
2695 if (a_val != b_val)
2696 return (a_val > b_val ? 1 : -1);
2697 else
2698 return rhythmdb_query_model_location_sort_func (a, b, data);
2701 gint
2702 rhythmdb_query_model_date_sort_func (RhythmDBEntry *a,
2703 RhythmDBEntry *b,
2704 gpointer data)
2707 gulong a_val, b_val;
2708 gint ret;
2710 a_val = rhythmdb_entry_get_ulong (a, RHYTHMDB_PROP_DATE);
2711 b_val = rhythmdb_entry_get_ulong (b, RHYTHMDB_PROP_DATE);
2713 ret = (a_val == b_val ? 0 : (a_val > b_val ? 1 : -1));
2714 if (a_val > b_val)
2715 return 1;
2716 else if (a_val < b_val)
2717 return -1;
2718 else
2719 return rhythmdb_query_model_album_sort_func (a, b, data);
2722 gint
2723 rhythmdb_query_model_string_sort_func (RhythmDBEntry *a,
2724 RhythmDBEntry *b,
2725 gpointer data)
2727 const char *a_val;
2728 const char *b_val;
2729 gint ret;
2730 RhythmDBPropType prop_id;
2732 prop_id = (RhythmDBPropType) GPOINTER_TO_INT (data);
2733 a_val = rhythmdb_entry_get_string (a, prop_id);
2734 b_val = rhythmdb_entry_get_string (b, prop_id);
2736 if (a_val == NULL) {
2737 if (b_val == NULL)
2738 ret = 0;
2739 else
2740 ret = -1;
2741 } else if (b_val == NULL)
2742 ret = 1;
2743 else
2744 ret = strcmp (a_val, b_val);
2746 if (ret != 0)
2747 return ret;
2748 else
2749 return rhythmdb_query_model_location_sort_func (a, b, data);
2752 static gboolean
2753 rhythmdb_query_model_within_limit (RhythmDBQueryModel *model,
2754 RhythmDBEntry *entry)
2756 gboolean result = TRUE;
2758 switch (model->priv->limit_type) {
2759 case RHYTHMDB_QUERY_MODEL_LIMIT_NONE:
2760 result = TRUE;
2761 break;
2763 case RHYTHMDB_QUERY_MODEL_LIMIT_COUNT:
2765 gulong limit_count;
2766 gulong current_count;
2768 limit_count = g_value_get_ulong (g_value_array_get_nth (model->priv->limit_value, 0));
2769 current_count = g_hash_table_size (model->priv->reverse_map);
2771 if (entry)
2772 current_count++;
2774 result = (current_count <= limit_count);
2775 break;
2778 case RHYTHMDB_QUERY_MODEL_LIMIT_SIZE:
2780 guint64 limit_size;
2781 guint64 current_size;
2783 limit_size = g_value_get_uint64 (g_value_array_get_nth (model->priv->limit_value, 0));
2784 current_size = model->priv->total_size;
2786 if (entry)
2787 current_size += rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
2789 /* the limit is in MB */
2790 result = (current_size / (1024 * 1024) <= limit_size);
2791 break;
2794 case RHYTHMDB_QUERY_MODEL_LIMIT_TIME:
2796 gulong limit_time;
2797 gulong current_time;
2799 limit_time = g_value_get_ulong (g_value_array_get_nth (model->priv->limit_value, 0));
2800 current_time = model->priv->total_duration;
2802 if (entry)
2803 current_time += rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
2805 result = (current_time <= limit_time);
2806 break;
2810 return result;
2813 /* This should really be standard. */
2814 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
2816 GType
2817 rhythmdb_query_model_limit_type_get_type (void)
2819 static GType etype = 0;
2821 if (etype == 0)
2823 static const GEnumValue values[] =
2826 ENUM_ENTRY (RHYTHMDB_QUERY_MODEL_LIMIT_NONE, "No limit"),
2827 ENUM_ENTRY (RHYTHMDB_QUERY_MODEL_LIMIT_COUNT, "Limit by number of entries (count)"),
2828 ENUM_ENTRY (RHYTHMDB_QUERY_MODEL_LIMIT_SIZE, "Limit by data size (Mb)"),
2829 ENUM_ENTRY (RHYTHMDB_QUERY_MODEL_LIMIT_TIME, "Limit by duration (seconds)"),
2830 { 0, 0, 0 }
2833 etype = g_enum_register_static ("RhythmDBQueryModelLimitType", values);
2836 return etype;
2839 static gboolean
2840 rhythmdb_query_model_reapply_query_cb (RhythmDBQueryModel *model)
2842 GDK_THREADS_ENTER ();
2843 rhythmdb_query_model_reapply_query (model, FALSE);
2844 rhythmdb_do_full_query_async_parsed (model->priv->db,
2845 RHYTHMDB_QUERY_RESULTS (model),
2846 model->priv->original_query);
2847 GDK_THREADS_LEAVE ();
2848 return TRUE;