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.
31 #include <gtk/gtktreednd.h>
33 #include "rhythmdb-query-model.h"
35 #include "gsequence.h"
36 #include "rb-tree-dnd.h"
37 #include "rb-marshal.h"
40 struct ReverseSortData
42 GCompareDataFunc func
;
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
,
70 static void rhythmdb_query_model_get_property (GObject
*object
,
74 static void rhythmdb_query_model_do_insert (RhythmDBQueryModel
*model
,
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
,
90 GtkSelectionData
*selection_data
);
91 static gboolean
rhythmdb_query_model_drag_data_delete (RbTreeDragSource
*dragsource
,
93 static gboolean
rhythmdb_query_model_row_draggable (RbTreeDragSource
*dragsource
,
95 static gboolean
rhythmdb_query_model_drag_data_received (RbTreeDragDest
*drag_dest
,
97 GtkTreeViewDropPosition pos
,
98 GtkSelectionData
*selection_data
);
99 static gboolean
rhythmdb_query_model_row_drop_possible (RbTreeDragDest
*drag_dest
,
101 GtkTreeViewDropPosition pos
,
102 GtkSelectionData
*selection_data
);
103 static gboolean
rhythmdb_query_model_row_drop_position (RbTreeDragDest
*drag_dest
,
104 GtkTreePath
*dest_path
,
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
,
117 static GtkTreePath
* rhythmdb_query_model_get_path (GtkTreeModel
*tree_model
,
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
,
123 static gboolean
rhythmdb_query_model_iter_children (GtkTreeModel
*tree_model
,
125 GtkTreeIter
*parent
);
126 static gboolean
rhythmdb_query_model_iter_has_child (GtkTreeModel
*tree_model
,
128 static gint
rhythmdb_query_model_iter_n_children (GtkTreeModel
*tree_model
,
130 static gboolean
rhythmdb_query_model_iter_nth_child (GtkTreeModel
*tree_model
,
131 GtkTreeIter
*iter
, GtkTreeIter
*parent
,
133 static gboolean
rhythmdb_query_model_iter_parent (GtkTreeModel
*tree_model
,
137 static void rhythmdb_query_model_base_row_inserted (GtkTreeModel
*base_model
,
140 RhythmDBQueryModel
*model
);
141 static void rhythmdb_query_model_base_row_deleted (GtkTreeModel
*base_model
,
143 RhythmDBQueryModel
*model
);
144 static void rhythmdb_query_model_base_non_entry_dropped (GtkTreeModel
*base_model
,
145 const char *location
,
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
,
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
,
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
;
175 RHYTHMDB_QUERY_MODEL_UPDATE_ROWS_INSERTED
,
176 RHYTHMDB_QUERY_MODEL_UPDATE_ROW_INSERTED_INDEX
,
177 RHYTHMDB_QUERY_MODEL_UPDATE_QUERY_COMPLETE
,
182 RhythmDBEntry
*entry
;
189 static void rhythmdb_query_model_process_update (struct RhythmDBQueryModelUpdate
*update
);
191 static void idle_process_update (struct RhythmDBQueryModelUpdate
*update
);
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
209 RhythmDBQueryModel
*base_model
;
211 GCompareDataFunc sort_func
;
213 GDestroyNotify sort_data_destroy
;
214 gboolean sort_reverse
;
217 GPtrArray
*original_query
;
221 RhythmDBQueryModelLimitType limit_type
;
222 GValueArray
*limit_value
;
224 glong total_duration
;
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))
250 PROP_SORT_DATA_DESTROY
,
268 static guint rhythmdb_query_model_signals
[LAST_SIGNAL
] = { 0 };
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
,
289 g_param_spec_object ("db",
293 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
294 g_object_class_install_property (object_class
,
296 g_param_spec_pointer ("query",
300 g_object_class_install_property (object_class
,
302 g_param_spec_pointer ("sort-func",
306 g_object_class_install_property (object_class
,
308 g_param_spec_pointer ("sort-data",
312 g_object_class_install_property (object_class
,
313 PROP_SORT_DATA_DESTROY
,
314 g_param_spec_pointer ("sort-data-destroy",
316 "Sort data destroy function",
318 g_object_class_install_property (object_class
,
320 g_param_spec_boolean ("sort-reverse",
322 "Reverse sort order flag",
325 g_object_class_install_property (object_class
,
327 g_param_spec_enum ("limit-type",
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
,
335 g_param_spec_boxed ("limit-value",
339 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
340 g_object_class_install_property (object_class
,
342 g_param_spec_boolean ("show-hidden",
344 "maximum time (seconds)",
346 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT
));
347 g_object_class_install_property (object_class
,
349 g_param_spec_object ("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
,
359 G_STRUCT_OFFSET (RhythmDBQueryModelClass
, entry_prop_changed
),
361 rb_marshal_VOID__BOXED_INT_POINTER_POINTER
,
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
,
368 G_STRUCT_OFFSET (RhythmDBQueryModelClass
, entry_removed
),
370 g_cclosure_marshal_VOID__BOXED
,
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
,
377 G_STRUCT_OFFSET (RhythmDBQueryModelClass
, non_entry_dropped
),
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
,
385 G_STRUCT_OFFSET (RhythmDBQueryModelClass
, complete
),
387 g_cclosure_marshal_VOID__VOID
,
389 rhythmdb_query_model_signals
[POST_ENTRY_DELETE
] =
390 g_signal_new ("post-entry-delete",
391 RHYTHMDB_TYPE_QUERY_MODEL
,
393 G_STRUCT_OFFSET (RhythmDBQueryModelClass
, post_entry_delete
),
395 g_cclosure_marshal_VOID__BOXED
,
397 1, RHYTHMDB_TYPE_ENTRY
);
399 g_type_class_add_private (klass
, sizeof (RhythmDBQueryModelPrivate
));
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
;
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
;
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
;
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
;
444 rhythmdb_query_model_set_query_internal (RhythmDBQueryModel
*model
,
447 if (query
== model
->priv
->original_query
)
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
,
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;
474 rhythmdb_query_model_set_property (GObject
*object
,
479 RhythmDBQueryModel
*model
= RHYTHMDB_QUERY_MODEL (object
);
483 model
->priv
->db
= g_value_get_object (value
);
486 rhythmdb_query_model_set_query_internal (model
, g_value_get_pointer (value
));
489 model
->priv
->sort_func
= g_value_get_pointer (value
);
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
);
496 case PROP_SORT_DATA_DESTROY
:
497 model
->priv
->sort_data_destroy
= g_value_get_pointer (value
);
499 case PROP_SORT_REVERSE
:
500 model
->priv
->sort_reverse
= g_value_get_boolean (value
);
502 case PROP_LIMIT_TYPE
:
503 model
->priv
->limit_type
= g_value_get_enum (value
);
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
);
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 */
514 case PROP_BASE_MODEL
:
515 rhythmdb_query_model_chain (model
, g_value_get_object (value
), TRUE
);
518 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
524 rhythmdb_query_model_get_property (GObject
*object
,
529 RhythmDBQueryModel
*model
= RHYTHMDB_QUERY_MODEL (object
);
533 g_value_set_object (value
, model
->priv
->db
);
536 g_value_set_pointer (value
, model
->priv
->original_query
);
539 g_value_set_pointer (value
, model
->priv
->sort_func
);
542 g_value_set_pointer (value
, model
->priv
->sort_data
);
544 case PROP_SORT_DATA_DESTROY
:
545 g_value_set_pointer (value
, model
->priv
->sort_data_destroy
);
547 case PROP_SORT_REVERSE
:
548 g_value_set_boolean (value
, model
->priv
->sort_reverse
);
550 case PROP_LIMIT_TYPE
:
551 g_value_set_enum (value
, model
->priv
->limit_type
);
553 case PROP_LIMIT_VALUE
:
554 g_value_set_boxed (value
, model
->priv
->limit_value
);
556 case PROP_SHOW_HIDDEN
:
557 g_value_set_boolean (value
, model
->priv
->show_hidden
);
559 case PROP_BASE_MODEL
:
560 g_value_set_object (value
, model
->priv
->base_model
);
563 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
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
,
578 (GDestroyNotify
)rhythmdb_entry_unref
,
581 model
->priv
->limited_entries
= g_sequence_new (NULL
);
582 model
->priv
->limited_reverse_map
= g_hash_table_new_full (g_direct_hash
,
584 (GDestroyNotify
)rhythmdb_entry_unref
,
587 model
->priv
->hidden_entry_map
= g_hash_table_new_full (g_direct_hash
,
589 (GDestroyNotify
)rhythmdb_entry_unref
,
592 model
->priv
->reorder_drag_and_drop
= FALSE
;
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
),
607 G_CALLBACK (rhythmdb_query_model_entry_added_cb
),
609 g_signal_connect_object (G_OBJECT (model
->priv
->db
),
611 G_CALLBACK (rhythmdb_query_model_entry_changed_cb
),
613 g_signal_connect_object (G_OBJECT (model
->priv
->db
),
615 G_CALLBACK (rhythmdb_query_model_entry_deleted_cb
),
618 return G_OBJECT (model
);
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
),
639 g_signal_handlers_disconnect_by_func (G_OBJECT (model
->priv
->base_model
),
640 G_CALLBACK (rhythmdb_query_model_base_row_deleted
),
642 g_signal_handlers_disconnect_by_func (G_OBJECT (model
->priv
->base_model
),
643 G_CALLBACK (rhythmdb_query_model_base_non_entry_dropped
),
645 g_signal_handlers_disconnect_by_func (G_OBJECT (model
->priv
->base_model
),
646 G_CALLBACK (rhythmdb_query_model_base_complete
),
648 g_signal_handlers_disconnect_by_func (G_OBJECT (model
->priv
->base_model
),
649 G_CALLBACK (rhythmdb_query_model_base_rows_reordered
),
651 g_signal_handlers_disconnect_by_func (G_OBJECT (model
->priv
->base_model
),
652 G_CALLBACK (rhythmdb_query_model_base_entry_removed
),
654 g_signal_handlers_disconnect_by_func (G_OBJECT (model
->priv
->base_model
),
655 G_CALLBACK (rhythmdb_query_model_base_entry_prop_changed
),
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
);
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
);
702 rhythmdb_query_model_new (RhythmDB
*db
,
704 GCompareDataFunc sort_func
,
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
,
717 g_return_val_if_fail (model
->priv
!= NULL
, NULL
);
723 rhythmdb_query_model_new_empty (RhythmDB
*db
)
725 return g_object_new (RHYTHMDB_TYPE_QUERY_MODEL
,
730 rhythmdb_query_model_copy_contents (RhythmDBQueryModel
*dest
,
731 RhythmDBQueryModel
*src
)
733 GSequencePtr ptr
, next
;
734 RhythmDBEntry
*entry
;
736 if (src
->priv
->entries
== NULL
)
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);
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
),
764 g_signal_handlers_disconnect_by_func (model
->priv
->base_model
,
765 G_CALLBACK (rhythmdb_query_model_base_row_deleted
),
767 g_signal_handlers_disconnect_by_func (model
->priv
->base_model
,
768 G_CALLBACK (rhythmdb_query_model_base_non_entry_dropped
),
770 g_signal_handlers_disconnect_by_func (model
->priv
->base_model
,
771 G_CALLBACK (rhythmdb_query_model_base_complete
),
773 g_signal_handlers_disconnect_by_func (model
->priv
->base_model
,
774 G_CALLBACK (rhythmdb_query_model_base_rows_reordered
),
776 g_signal_handlers_disconnect_by_func (model
->priv
->base_model
,
777 G_CALLBACK (rhythmdb_query_model_base_entry_removed
),
779 g_signal_handlers_disconnect_by_func (model
->priv
->base_model
,
780 G_CALLBACK (rhythmdb_query_model_base_entry_prop_changed
),
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
,
794 G_CALLBACK (rhythmdb_query_model_base_row_inserted
),
796 g_signal_connect_object (model
->priv
->base_model
,
798 G_CALLBACK (rhythmdb_query_model_base_row_deleted
),
800 g_signal_connect_object (model
->priv
->base_model
,
802 G_CALLBACK (rhythmdb_query_model_base_non_entry_dropped
),
804 g_signal_connect_object (model
->priv
->base_model
,
806 G_CALLBACK (rhythmdb_query_model_base_complete
),
808 g_signal_connect_object (model
->priv
->base_model
,
810 G_CALLBACK (rhythmdb_query_model_base_rows_reordered
),
812 g_signal_connect_object (model
->priv
->base_model
,
814 G_CALLBACK (rhythmdb_query_model_base_entry_removed
),
816 g_signal_connect_object (model
->priv
->base_model
,
817 "entry-prop-changed",
818 G_CALLBACK (rhythmdb_query_model_base_entry_prop_changed
),
822 rhythmdb_query_model_copy_contents (model
, model
->priv
->base_model
);
827 rhythmdb_query_model_has_pending_changes (RhythmDBQueryModel
*model
)
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
);
839 rhythmdb_query_model_entry_added_cb (RhythmDB
*db
,
840 RhythmDBEntry
*entry
,
841 RhythmDBQueryModel
*model
)
844 gboolean insert
= FALSE
;
845 if (!model
->priv
->show_hidden
&& rhythmdb_entry_get_boolean (entry
, RHYTHMDB_PROP_HIDDEN
)) {
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
) {
856 if (model
->priv
->query
!= NULL
) {
857 insert
= rhythmdb_evaluate_query (db
, model
->priv
->query
, entry
);
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
);
862 rb_debug ("adding unhidden entry at index %d", index
);
866 rhythmdb_query_model_do_insert (model
, entry
, index
);
871 rhythmdb_query_model_entry_changed_cb (RhythmDB
*db
,
872 RhythmDBEntry
*entry
,
874 RhythmDBQueryModel
*model
)
876 gboolean hidden
= FALSE
;
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
885 rhythmdb_query_model_entry_added_cb (db
, entry
, model
);
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
) {
923 /* find the entry's position so we can restore it there
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
);
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
);
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 */
974 if (rhythmdb_query_model_entry_to_iter (model
, entry
, &iter
)) {
975 path
= rhythmdb_query_model_get_path (GTK_TREE_MODEL (model
),
977 gtk_tree_model_row_changed (GTK_TREE_MODEL (model
),
979 gtk_tree_path_free (path
);
985 rhythmdb_query_model_base_entry_prop_changed (RhythmDBQueryModel
*base_model
,
986 RhythmDBEntry
*entry
,
987 RhythmDBPropType prop
,
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
);
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
);
1012 idle_process_update_idle (struct RhythmDBQueryModelUpdate
*update
)
1014 GDK_THREADS_ENTER ();
1015 idle_process_update (update
);
1016 GDK_THREADS_LEAVE ();
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
);
1027 g_idle_add ((GSourceFunc
) idle_process_update_idle
, update
);
1031 idle_process_update (struct RhythmDBQueryModelUpdate
*update
)
1033 switch (update
->type
) {
1034 case RHYTHMDB_QUERY_MODEL_UPDATE_ROWS_INSERTED
:
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
;
1046 g_hash_table_lookup (base_model
->priv
->reverse_map
, entry
) == NULL
)
1049 rhythmdb_query_model_do_insert (update
->model
, entry
, -1);
1052 rhythmdb_entry_unref (entry
);
1055 g_ptr_array_free (update
->entrydata
.entries
, TRUE
);
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
);
1066 case RHYTHMDB_QUERY_MODEL_UPDATE_QUERY_COMPLETE
:
1067 g_signal_emit (G_OBJECT (update
->model
), rhythmdb_query_model_signals
[COMPLETE
], 0);
1071 g_atomic_int_add (&update
->model
->priv
->pending_update_count
, -1);
1072 g_object_unref (update
->model
);
1077 rhythmdb_query_model_add_entry (RhythmDBQueryModel
*model
,
1078 RhythmDBEntry
*entry
,
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");
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
));
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
);
1112 rhythmdb_query_model_get_size (RhythmDBQueryModel
*model
)
1114 return model
->priv
->total_size
;
1118 rhythmdb_query_model_get_duration (RhythmDBQueryModel
*model
)
1120 return model
->priv
->total_duration
;
1124 rhythmdb_query_model_insert_into_main_list (RhythmDBQueryModel
*model
,
1125 RhythmDBEntry
*entry
,
1130 /* take reference; released when removed from hash */
1131 rhythmdb_entry_ref (entry
);
1133 if (model
->priv
->sort_func
) {
1134 GCompareDataFunc sort_func
;
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
;
1144 sort_func
= model
->priv
->sort_func
;
1145 sort_data
= model
->priv
->sort_data
;
1148 ptr
= g_sequence_insert_sorted (model
->priv
->entries
,
1154 ptr
= g_sequence_get_end_ptr (model
->priv
->entries
);
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
);
1171 rhythmdb_query_model_insert_into_limited_list (RhythmDBQueryModel
*model
,
1172 RhythmDBEntry
*entry
)
1176 /* take reference; released when removed from hash */
1177 rhythmdb_entry_ref (entry
);
1179 if (model
->priv
->sort_func
) {
1180 GCompareDataFunc sort_func
;
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
;
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
,
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
);
1208 rhythmdb_query_model_remove_from_main_list (RhythmDBQueryModel
*model
,
1209 RhythmDBEntry
*entry
)
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
);
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
);
1258 rhythmdb_query_model_update_limited_entries (RhythmDBQueryModel
*model
)
1260 RhythmDBEntry
*entry
;
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 */
1281 ptr
= g_sequence_get_begin_ptr (model
->priv
->limited_entries
);
1282 if (!ptr
|| ptr
== g_sequence_get_end_ptr (model
->priv
->limited_entries
))
1284 entry
= (RhythmDBEntry
*) g_sequence_ptr_get_data (ptr
);
1288 if (!rhythmdb_query_model_within_limit (model
, entry
))
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
),
1303 gtk_tree_model_row_inserted (GTK_TREE_MODEL (model
),
1305 gtk_tree_path_free (path
);
1310 rhythmdb_query_model_emit_reorder (RhythmDBQueryModel
*model
,
1319 if (new_pos
== old_pos
) {
1320 /* it hasn't moved, don't emit a re-order */
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
++)
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
++)
1337 /* it has moved up the list */
1338 for (i
= 0; i
< new_pos
; 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
++)
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
),
1353 gtk_tree_path_free (path
);
1359 rhythmdb_query_model_do_reorder (RhythmDBQueryModel
*model
,
1360 RhythmDBEntry
*entry
)
1363 int old_pos
, new_pos
;
1366 GCompareDataFunc sort_func
;
1368 struct ReverseSortData reverse_data
;
1370 if (model
->priv
->sort_func
== NULL
)
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
;
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
);
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
);
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
),
1406 gtk_tree_model_row_changed (GTK_TREE_MODEL (model
),
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
,
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
);
1430 rhythmdb_query_model_do_insert (RhythmDBQueryModel
*model
,
1431 RhythmDBEntry
*entry
,
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
)
1444 /* take temporary ref */
1445 rhythmdb_entry_ref (entry
);
1447 ptr
= g_hash_table_lookup (model
->priv
->limited_reverse_map
, entry
);
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
),
1463 gtk_tree_model_row_inserted (GTK_TREE_MODEL (model
),
1465 gtk_tree_path_free (path
);
1467 rhythmdb_query_model_update_limited_entries (model
);
1471 rhythmdb_query_model_filter_out_entry (RhythmDBQueryModel
*model
,
1472 RhythmDBEntry
*entry
)
1476 ptr
= g_hash_table_lookup (model
->priv
->reverse_map
, entry
);
1478 rhythmdb_query_model_remove_from_main_list (model
, entry
);
1479 rhythmdb_query_model_update_limited_entries (model
);
1483 ptr
= g_hash_table_lookup (model
->priv
->limited_reverse_map
, entry
);
1485 rhythmdb_query_model_remove_from_limited_list (model
, entry
);
1486 rhythmdb_query_model_update_limited_entries (model
);
1492 rhythmdb_query_model_move_entry (RhythmDBQueryModel
*model
,
1493 RhythmDBEntry
*entry
,
1500 ptr
= g_hash_table_lookup (model
->priv
->reverse_map
, entry
);
1504 nptr
= g_sequence_get_ptr_at_pos (model
->priv
->entries
, index
);
1505 if ((nptr
== NULL
) || (ptr
== nptr
))
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
);
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
1541 g_signal_emit (G_OBJECT (model
),
1542 rhythmdb_query_model_signals
[ENTRY_REMOVED
], 0,
1544 rhythmdb_query_model_filter_out_entry (model
, entry
);
1550 rhythmdb_query_model_entry_to_iter (RhythmDBQueryModel
*model
,
1551 RhythmDBEntry
*entry
,
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
);
1564 iter
->stamp
= model
->priv
->stamp
;
1565 iter
->user_data
= ptr
;
1570 rhythmdb_query_model_tree_path_to_entry (RhythmDBQueryModel
*model
,
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
);
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);
1589 rhythmdb_query_model_get_next_from_entry (RhythmDBQueryModel
*model
,
1590 RhythmDBEntry
*entry
)
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
))
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
))
1605 return rhythmdb_query_model_iter_to_entry (model
, &iter
);
1609 rhythmdb_query_model_get_previous_from_entry (RhythmDBQueryModel
*model
,
1610 RhythmDBEntry
*entry
)
1615 g_return_val_if_fail (entry
!= NULL
, NULL
);
1617 if (!rhythmdb_query_model_entry_to_iter (model
, entry
, &iter
))
1620 path
= gtk_tree_model_get_path (GTK_TREE_MODEL (model
), &iter
);
1622 if (!gtk_tree_path_prev (path
)) {
1623 gtk_tree_path_free (path
);
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
);
1634 rhythmdb_query_model_row_draggable (RbTreeDragSource
*dragsource
,
1641 rhythmdb_query_model_drag_data_delete (RbTreeDragSource
*dragsource
,
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
;
1655 for (; paths
; paths
= paths
->next
) {
1657 path
= gtk_tree_row_reference_get_path (paths
->data
);
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
;
1675 rhythmdb_query_model_drag_data_get (RbTreeDragSource
*dragsource
,
1677 GtkSelectionData
*selection_data
)
1679 RhythmDBQueryModel
*model
= RHYTHMDB_QUERY_MODEL (dragsource
);
1680 RhythmDBEntry
*entry
;
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
)) {
1694 data
= g_string_new ("");
1696 for (tem
= paths
; tem
; tem
= tem
->next
) {
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
);
1707 g_string_append (data
, "\r\n");
1709 if (target
== TARGET_URIS
) {
1712 location
= rhythmdb_entry_get_playback_uri (entry
);
1713 if (location
== NULL
) {
1714 need_newline
= FALSE
;
1718 g_string_append (data
, location
);
1720 } else if (target
== TARGET_ENTRIES
) {
1721 g_string_append_printf (data
,
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
,
1733 g_string_free (data
, TRUE
);
1739 rhythmdb_query_model_drag_data_received (RbTreeDragDest
*drag_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
;
1753 entry
= rhythmdb_query_model_tree_path_to_entry (model
, dest
);
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
);
1762 result
= rhythmdb_query_model_drag_data_received ((RbTreeDragDest
*)model
->priv
->base_model
,
1763 base_dest
, pos
, selection_data
);
1765 gtk_tree_path_free (base_dest
);
1770 rb_debug ("drag received");
1772 if (model
->priv
->sort_func
!= NULL
)
1775 if (selection_data
->format
== 8 && selection_data
->length
>= 0) {
1779 RhythmDBEntry
*entry
;
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
);
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)
1802 entry
= rhythmdb_entry_lookup_from_string (model
->priv
->db
, strv
[i
], !uri_list
);
1803 if (entry
== NULL
) {
1807 if (g_sequence_ptr_is_end (ptr
))
1810 pos
= g_sequence_ptr_get_position (ptr
);
1812 g_signal_emit (G_OBJECT (model
),
1813 rhythmdb_query_model_signals
[NON_ENTRY_DROPPED
],
1816 rb_debug ("got drop with entry id %s, but can't find the entry", strv
[i
]);
1819 GSequencePtr old_ptr
;
1820 GtkTreePath
*tem_path
;
1824 old_ptr
= g_hash_table_lookup (model
->priv
->reverse_map
, entry
);
1825 /* trying to drag drop an entry on itself ! */
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. */
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
));
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
);
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
);
1859 tem_path
= rhythmdb_query_model_get_path (GTK_TREE_MODEL (model
),
1861 gtk_tree_model_row_inserted (GTK_TREE_MODEL (model
),
1862 tem_path
, &tem_iter
);
1863 gtk_tree_path_free (tem_path
);
1875 rhythmdb_query_model_row_drop_possible (RbTreeDragDest
*drag_dest
,
1877 GtkTreeViewDropPosition pos
,
1878 GtkSelectionData
*selection_data
)
1880 RhythmDBQueryModel
*model
= RHYTHMDB_QUERY_MODEL (drag_dest
);
1883 if (model
->priv
->sort_func
!= NULL
)
1886 model
= model
->priv
->base_model
;
1892 rhythmdb_query_model_row_drop_position (RbTreeDragDest
*drag_dest
,
1893 GtkTreePath
*dest_path
,
1895 GtkTreeViewDropPosition
*pos
)
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.
1910 rhythmdb_query_model_add_results (RhythmDBQueryResults
*results
,
1913 RhythmDBQueryModel
*model
= RHYTHMDB_QUERY_MODEL (results
);
1914 struct RhythmDBQueryModelUpdate
*update
;
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
);
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
;
1957 rhythmdb_query_model_get_n_columns (GtkTreeModel
*tree_model
)
1963 rhythmdb_query_model_get_column_type (GtkTreeModel
*tree_model
,
1968 return RHYTHMDB_TYPE_ENTRY
;
1972 g_assert_not_reached ();
1973 return G_TYPE_INVALID
;
1978 rhythmdb_query_model_get_iter (GtkTreeModel
*tree_model
,
1982 RhythmDBQueryModel
*model
= RHYTHMDB_QUERY_MODEL (tree_model
);
1986 index
= gtk_tree_path_get_indices (path
)[0];
1988 if (index
>= g_sequence_get_length (model
->priv
->entries
))
1991 ptr
= g_sequence_get_ptr_at_pos (model
->priv
->entries
, index
);
1994 iter
->stamp
= model
->priv
->stamp
;
1995 iter
->user_data
= ptr
;
2000 static GtkTreePath
*
2001 rhythmdb_query_model_get_path (GtkTreeModel
*tree_model
,
2004 RhythmDBQueryModel
*model
= RHYTHMDB_QUERY_MODEL (tree_model
);
2007 g_return_val_if_fail (iter
->stamp
== model
->priv
->stamp
, NULL
);
2009 if (g_sequence_ptr_is_end (iter
->user_data
))
2012 path
= gtk_tree_path_new ();
2013 gtk_tree_path_append_index (path
, g_sequence_ptr_get_position (iter
->user_data
));
2018 rhythmdb_query_model_get_value (GtkTreeModel
*tree_model
,
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
);
2033 g_value_init (value
, RHYTHMDB_TYPE_ENTRY
);
2034 g_value_set_boxed (value
, entry
);
2037 g_value_init (value
, G_TYPE_INT
);
2038 g_value_set_int (value
, g_sequence_ptr_get_position (iter
->user_data
)+1);
2041 g_assert_not_reached ();
2046 rhythmdb_query_model_iter_next (GtkTreeModel
*tree_model
,
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
);
2059 rhythmdb_query_model_iter_children (GtkTreeModel
*tree_model
,
2061 GtkTreeIter
*parent
)
2063 RhythmDBQueryModel
*model
= RHYTHMDB_QUERY_MODEL (tree_model
);
2068 if (g_sequence_get_length (model
->priv
->entries
) == 0)
2071 iter
->stamp
= model
->priv
->stamp
;
2072 iter
->user_data
= g_sequence_get_begin_ptr (model
->priv
->entries
);
2078 rhythmdb_query_model_iter_has_child (GtkTreeModel
*tree_model
,
2085 rhythmdb_query_model_iter_n_children (GtkTreeModel
*tree_model
,
2088 RhythmDBQueryModel
*model
= RHYTHMDB_QUERY_MODEL (tree_model
);
2091 return g_sequence_get_length (model
->priv
->entries
);
2093 g_return_val_if_fail (model
->priv
->stamp
== iter
->stamp
, -1);
2099 rhythmdb_query_model_iter_nth_child (GtkTreeModel
*tree_model
,
2101 GtkTreeIter
*parent
,
2104 RhythmDBQueryModel
*model
= RHYTHMDB_QUERY_MODEL (tree_model
);
2110 child
= g_sequence_get_ptr_at_pos (model
->priv
->entries
, n
);
2112 if (g_sequence_ptr_is_end (child
))
2115 iter
->stamp
= model
->priv
->stamp
;
2116 iter
->user_data
= child
;
2122 rhythmdb_query_model_iter_parent (GtkTreeModel
*tree_model
,
2130 rhythmdb_query_model_compute_status_normal (RhythmDBQueryModel
*model
,
2131 const char *singular
,
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
),
2142 apply_updated_entry_sequence (RhythmDBQueryModel
*model
,
2143 GSequence
*new_entries
)
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
),
2176 gtk_tree_path_free (path
);
2181 rhythmdb_query_model_set_sort_order (RhythmDBQueryModel
*model
,
2182 GCompareDataFunc sort_func
,
2184 GDestroyNotify sort_data_destroy
,
2185 gboolean sort_reverse
)
2187 GSequence
*new_entries
;
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
))
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
);
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
,
2229 ptr
= g_sequence_ptr_next (ptr
);
2232 apply_updated_entry_sequence (model
, new_entries
);
2237 rhythmdb_query_model_child_index_to_base_index (RhythmDBQueryModel
*model
,
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
))
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
);
2256 rhythmdb_query_model_base_index_to_child_index (RhythmDBQueryModel *model, int index)
2259 RhythmDBEntry *entry;
2262 g_assert (model->priv->base_model);
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))
2269 entry = (RhythmDBEntry*)g_sequence_ptr_get_data (ptr);
2271 ptr = g_hash_table_lookup (model->priv->reverse_map, entry);
2275 pos = g_sequence_ptr_get_position (ptr);
2280 rhythmdb_query_model_get_entry_index (RhythmDBQueryModel
*model
,
2281 RhythmDBEntry
*entry
)
2283 GSequencePtr ptr
= g_hash_table_lookup (model
->priv
->reverse_map
, entry
);
2286 return g_sequence_ptr_get_position (ptr
);
2292 rhythmdb_query_model_base_row_inserted (GtkTreeModel
*tree_model
,
2295 RhythmDBQueryModel
*model
)
2297 RhythmDBQueryModel
*base_model
= RHYTHMDB_QUERY_MODEL (tree_model
);
2298 RhythmDBEntry
*entry
;
2299 RhythmDBEntry
*prev_entry
;
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
))
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;
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
);
2331 rhythmdb_entry_unref (entry
);
2335 rhythmdb_query_model_base_row_deleted (GtkTreeModel
*base_model
,
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
);
2349 rhythmdb_query_model_base_non_entry_dropped (GtkTreeModel
*base_model
,
2350 const char *location
,
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
));
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);
2366 rhythmdb_query_model_base_rows_reordered (GtkTreeModel
*base_model
,
2370 RhythmDBQueryModel
*model
)
2372 RhythmDBQueryModel
*base_query_model
= RHYTHMDB_QUERY_MODEL (base_model
);
2373 GSequence
*new_entries
;
2376 /* ignore, if this model sorts */
2377 if (model
->priv
->sort_func
)
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
);
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,
2410 rhythmdb_query_model_reapply_query (RhythmDBQueryModel
*model
,
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
,
2430 remove
= g_list_prepend (remove
, entry
);
2438 for (t
= remove
; t
; t
= t
->next
) {
2439 RhythmDBEntry
*entry
= t
->data
;
2440 rhythmdb_query_model_remove_from_limited_list (model
, entry
);
2444 g_list_free (remove
);
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
,
2457 remove
= g_list_prepend (remove
, entry
);
2465 for (t
= remove
; t
; t
= t
->next
) {
2466 RhythmDBEntry
*entry
= t
->data
;
2468 g_signal_emit (G_OBJECT (model
),
2469 rhythmdb_query_model_signals
[ENTRY_REMOVED
], 0,
2472 rhythmdb_query_model_remove_from_main_list (model
, entry
);
2476 g_list_free (remove
);
2482 rhythmdb_query_model_update_limited_entries (model
);
2486 _reverse_sorting_func (gpointer a
,
2488 struct ReverseSortData
*reverse_data
)
2490 return - reverse_data
->func (a
, b
, reverse_data
->data
);
2494 rhythmdb_query_model_location_sort_func (RhythmDBEntry
*a
,
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
) {
2509 } else if (b_val
== NULL
)
2512 return strcmp (a_val
, b_val
);
2516 rhythmdb_query_model_title_sort_func (RhythmDBEntry
*a
,
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
) {
2533 } else if (b_val
== NULL
)
2536 ret
= strcmp (a_val
, b_val
);
2541 return rhythmdb_query_model_location_sort_func (a
, b
, data
);
2545 rhythmdb_query_model_album_sort_func (RhythmDBEntry
*a
,
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
) {
2564 } else if (b_val
== NULL
)
2567 ret
= strcmp (a_val
, b_val
);
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);
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
);
2584 return (a_num
< b_num
? -1 : 1);
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
) {
2595 } else if (b_val
== NULL
)
2598 return rhythmdb_query_model_location_sort_func (a
, b
, data
);
2602 rhythmdb_query_model_artist_sort_func (RhythmDBEntry
*a
,
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
) {
2618 } else if (b_val
== NULL
)
2621 ret
= strcmp (a_val
, b_val
);
2626 return rhythmdb_query_model_album_sort_func (a
, b
, data
);
2630 rhythmdb_query_model_genre_sort_func (RhythmDBEntry
*a
, RhythmDBEntry
*b
,
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
) {
2645 } else if (b_val
== NULL
)
2648 ret
= strcmp (a_val
, b_val
);
2653 return rhythmdb_query_model_artist_sort_func (a
, b
, data
);
2657 rhythmdb_query_model_track_sort_func (RhythmDBEntry
*a
,
2661 return rhythmdb_query_model_album_sort_func (a
, b
, data
);
2665 rhythmdb_query_model_double_ceiling_sort_func (RhythmDBEntry
*a
,
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
));
2678 return (a_val
> b_val
? 1 : -1);
2680 return rhythmdb_query_model_location_sort_func (a
, b
, data
);
2684 rhythmdb_query_model_ulong_sort_func (RhythmDBEntry
*a
,
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
);
2696 return (a_val
> b_val
? 1 : -1);
2698 return rhythmdb_query_model_location_sort_func (a
, b
, data
);
2702 rhythmdb_query_model_date_sort_func (RhythmDBEntry
*a
,
2707 gulong a_val
, b_val
;
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));
2716 else if (a_val
< b_val
)
2719 return rhythmdb_query_model_album_sort_func (a
, b
, data
);
2723 rhythmdb_query_model_string_sort_func (RhythmDBEntry
*a
,
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
) {
2741 } else if (b_val
== NULL
)
2744 ret
= strcmp (a_val
, b_val
);
2749 return rhythmdb_query_model_location_sort_func (a
, b
, data
);
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
:
2763 case RHYTHMDB_QUERY_MODEL_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
);
2774 result
= (current_count
<= limit_count
);
2778 case RHYTHMDB_QUERY_MODEL_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
;
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
);
2794 case RHYTHMDB_QUERY_MODEL_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
;
2803 current_time
+= rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_DURATION
);
2805 result
= (current_time
<= limit_time
);
2813 /* This should really be standard. */
2814 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
2817 rhythmdb_query_model_limit_type_get_type (void)
2819 static GType 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)"),
2833 etype
= g_enum_register_static ("RhythmDBQueryModelLimitType", values
);
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 ();