fix a memory leak
[rhythmbox.git] / rhythmdb / rhythmdb-property-model.c
blob6055531073b59ebde16370625702b5e39662e382
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * arch-tag: Implementation of RhythmDB property 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 <glib/gi18n.h>
30 #include "rhythmdb-property-model.h"
31 #include "rb-debug.h"
32 #include "gsequence.h"
33 #include "rb-refstring.h"
34 #include "rb-tree-dnd.h"
36 static void rhythmdb_property_model_tree_model_init (GtkTreeModelIface *iface);
37 static void rhythmdb_property_model_drag_source_init (RbTreeDragSourceIface *iface);
39 G_DEFINE_TYPE_WITH_CODE(RhythmDBPropertyModel, rhythmdb_property_model, G_TYPE_OBJECT,
40 G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL,
41 rhythmdb_property_model_tree_model_init)
42 G_IMPLEMENT_INTERFACE(RB_TYPE_TREE_DRAG_SOURCE,
43 rhythmdb_property_model_drag_source_init))
45 typedef struct {
46 RBRefString *string;
47 RBRefString *sort_string;
48 guint refcount;
49 } RhythmDBPropertyModelEntry;
51 static void rhythmdb_property_model_finalize (GObject *object);
52 static void rhythmdb_property_model_set_property (GObject *object,
53 guint prop_id,
54 const GValue *value,
55 GParamSpec *pspec);
56 static void rhythmdb_property_model_get_property (GObject *object,
57 guint prop_id,
58 GValue *value,
59 GParamSpec *pspec);
60 static void rhythmdb_property_model_sync (RhythmDBPropertyModel *model);
61 static void rhythmdb_property_model_row_inserted_cb (GtkTreeModel *model,
62 GtkTreePath *path,
63 GtkTreeIter *iter,
64 RhythmDBPropertyModel *propmodel);
65 static void rhythmdb_property_model_prop_changed_cb (RhythmDB *db, RhythmDBEntry *entry,
66 RhythmDBPropType prop, const GValue *old,
67 const GValue *new,
68 RhythmDBPropertyModel *propmodel);
69 static void rhythmdb_property_model_entry_removed_cb (RhythmDBQueryModel *model,
70 RhythmDBEntry *entry,
71 RhythmDBPropertyModel *propmodel);
72 static RhythmDBPropertyModelEntry* rhythmdb_property_model_insert (RhythmDBPropertyModel *model,
73 RhythmDBEntry *entry);
74 static void rhythmdb_property_model_delete (RhythmDBPropertyModel *model,
75 RhythmDBEntry *entry);
76 static void rhythmdb_property_model_delete_prop (RhythmDBPropertyModel *model,
77 const char *propstr);
78 static GtkTreeModelFlags rhythmdb_property_model_get_flags (GtkTreeModel *model);
79 static gint rhythmdb_property_model_get_n_columns (GtkTreeModel *tree_model);
80 static GType rhythmdb_property_model_get_column_type (GtkTreeModel *tree_model, int index);
81 static gboolean rhythmdb_property_model_get_iter (GtkTreeModel *tree_model, GtkTreeIter *iter,
82 GtkTreePath *path);
83 static GtkTreePath * rhythmdb_property_model_get_path (GtkTreeModel *tree_model,
84 GtkTreeIter *iter);
85 static void rhythmdb_property_model_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter,
86 gint column, GValue *value);
87 static gboolean rhythmdb_property_model_iter_next (GtkTreeModel *tree_model,
88 GtkTreeIter *iter);
89 static gboolean rhythmdb_property_model_iter_children (GtkTreeModel *tree_model,
90 GtkTreeIter *iter,
91 GtkTreeIter *parent);
92 static gboolean rhythmdb_property_model_iter_has_child (GtkTreeModel *tree_model,
93 GtkTreeIter *iter);
94 static gint rhythmdb_property_model_iter_n_children (GtkTreeModel *tree_model,
95 GtkTreeIter *iter);
96 static gboolean rhythmdb_property_model_iter_nth_child (GtkTreeModel *tree_model,
97 GtkTreeIter *iter, GtkTreeIter *parent,
98 gint n);
99 static gboolean rhythmdb_property_model_iter_parent (GtkTreeModel *tree_model,
100 GtkTreeIter *iter,
101 GtkTreeIter *child);
103 static gboolean rhythmdb_property_model_drag_data_get (RbTreeDragSource *dragsource,
104 GList *paths,
105 GtkSelectionData *selection_data);
106 static gboolean rhythmdb_property_model_drag_data_delete (RbTreeDragSource *dragsource,
107 GList *paths);
108 static gboolean rhythmdb_property_model_row_draggable (RbTreeDragSource *dragsource,
109 GList *paths);
111 enum {
112 TARGET_ALBUMS,
113 TARGET_GENRE,
114 TARGET_ARTISTS,
115 TARGET_LOCATION,
116 TARGET_ENTRIES,
117 TARGET_URIS,
120 static const GtkTargetEntry targets_album [] = {
121 { "text/x-rhythmbox-album", 0, TARGET_ALBUMS },
122 { "application/x-rhythmbox-entry", 0, TARGET_ENTRIES },
123 { "text/uri-list", 0, TARGET_URIS },
125 static const GtkTargetEntry targets_genre [] = {
126 { "text/x-rhythmbox-genre", 0, TARGET_GENRE },
127 { "application/x-rhythmbox-entry", 0, TARGET_ENTRIES },
128 { "text/uri-list", 0, TARGET_URIS },
130 static const GtkTargetEntry targets_artist [] = {
131 { "text/x-rhythmbox-artist", 0, TARGET_ARTISTS },
132 { "application/x-rhythmbox-entry", 0, TARGET_ENTRIES },
133 { "text/uri-list", 0, TARGET_URIS },
135 static const GtkTargetEntry targets_location [] = {
136 { "text/x-rhythmbox-location", 0, TARGET_LOCATION },
137 { "application/x-rhythmbox-entry", 0, TARGET_ENTRIES },
138 { "text/uri-list", 0, TARGET_URIS },
141 static GtkTargetList *rhythmdb_property_model_album_drag_target_list = NULL;
142 static GtkTargetList *rhythmdb_property_model_artist_drag_target_list = NULL;
143 static GtkTargetList *rhythmdb_property_model_genre_drag_target_list = NULL;
144 static GtkTargetList *rhythmdb_property_model_location_drag_target_list = NULL;
146 struct RhythmDBPropertyModelPrivate
148 RhythmDB *db;
150 RhythmDBQueryModel *query_model;
151 GHashTable *entries;
153 RhythmDBPropType propid;
154 RhythmDBPropType sort_propid;
156 guint stamp;
158 GSequence *properties;
159 GHashTable *reverse_map;
161 RhythmDBPropertyModelEntry *all;
163 guint syncing_id;
166 #define RHYTHMDB_PROPERTY_MODEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE_PROPERTY_MODEL, RhythmDBPropertyModelPrivate))
168 enum
170 PRE_ROW_DELETION,
171 LAST_SIGNAL
174 enum
176 PROP_0,
177 PROP_RHYTHMDB,
178 PROP_PROP,
179 PROP_QUERY_MODEL,
182 static guint rhythmdb_property_model_signals[LAST_SIGNAL] = { 0 };
184 static void
185 rhythmdb_property_model_class_init (RhythmDBPropertyModelClass *klass)
187 GObjectClass *object_class = G_OBJECT_CLASS (klass);
189 if (!rhythmdb_property_model_artist_drag_target_list)
190 rhythmdb_property_model_artist_drag_target_list =
191 gtk_target_list_new (targets_artist,
192 G_N_ELEMENTS (targets_artist));
193 if (!rhythmdb_property_model_album_drag_target_list)
194 rhythmdb_property_model_album_drag_target_list =
195 gtk_target_list_new (targets_album,
196 G_N_ELEMENTS (targets_album));
197 if (!rhythmdb_property_model_genre_drag_target_list)
198 rhythmdb_property_model_genre_drag_target_list =
199 gtk_target_list_new (targets_genre,
200 G_N_ELEMENTS (targets_genre));
201 if (!rhythmdb_property_model_location_drag_target_list)
202 rhythmdb_property_model_location_drag_target_list =
203 gtk_target_list_new (targets_location,
204 G_N_ELEMENTS (targets_location));
206 object_class->set_property = rhythmdb_property_model_set_property;
207 object_class->get_property = rhythmdb_property_model_get_property;
209 object_class->finalize = rhythmdb_property_model_finalize;
211 rhythmdb_property_model_signals[PRE_ROW_DELETION] =
212 g_signal_new ("pre-row-deletion",
213 G_OBJECT_CLASS_TYPE (object_class),
214 G_SIGNAL_RUN_LAST,
215 G_STRUCT_OFFSET (RhythmDBPropertyModelClass, pre_row_deletion),
216 NULL, NULL,
217 g_cclosure_marshal_VOID__VOID,
218 G_TYPE_NONE,
221 g_object_class_install_property (object_class,
222 PROP_RHYTHMDB,
223 g_param_spec_object ("db",
224 "RhythmDB",
225 "RhythmDB object",
226 RHYTHMDB_TYPE,
227 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
229 g_object_class_install_property (object_class,
230 PROP_PROP,
231 g_param_spec_int ("prop",
232 "propid",
233 "Property id",
234 0, RHYTHMDB_NUM_PROPERTIES,
235 RHYTHMDB_PROP_TYPE,
236 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
237 g_object_class_install_property (object_class,
238 PROP_QUERY_MODEL,
239 g_param_spec_object ("query-model",
240 "RhythmDBQueryModel",
241 "RhythmDBQueryModel object ",
242 RHYTHMDB_TYPE_QUERY_MODEL,
243 G_PARAM_READWRITE));
245 g_type_class_add_private (klass, sizeof (RhythmDBPropertyModelPrivate));
248 static void
249 rhythmdb_property_model_tree_model_init (GtkTreeModelIface *iface)
251 iface->get_flags = rhythmdb_property_model_get_flags;
252 iface->get_n_columns = rhythmdb_property_model_get_n_columns;
253 iface->get_column_type = rhythmdb_property_model_get_column_type;
254 iface->get_iter = rhythmdb_property_model_get_iter;
255 iface->get_path = rhythmdb_property_model_get_path;
256 iface->get_value = rhythmdb_property_model_get_value;
257 iface->iter_next = rhythmdb_property_model_iter_next;
258 iface->iter_children = rhythmdb_property_model_iter_children;
259 iface->iter_has_child = rhythmdb_property_model_iter_has_child;
260 iface->iter_n_children = rhythmdb_property_model_iter_n_children;
261 iface->iter_nth_child = rhythmdb_property_model_iter_nth_child;
262 iface->iter_parent = rhythmdb_property_model_iter_parent;
265 static void
266 rhythmdb_property_model_drag_source_init (RbTreeDragSourceIface *iface)
268 iface->rb_row_draggable = rhythmdb_property_model_row_draggable;
269 iface->rb_drag_data_delete = rhythmdb_property_model_drag_data_delete;
270 iface->rb_drag_data_get = rhythmdb_property_model_drag_data_get;
273 static gboolean
274 _remove_entry_cb (GtkTreeModel *model,
275 GtkTreePath *path,
276 GtkTreeIter *iter,
277 RhythmDBPropertyModel *propmodel)
279 RhythmDBEntry *entry;
281 entry = rhythmdb_query_model_iter_to_entry (RHYTHMDB_QUERY_MODEL (model), iter);
282 rhythmdb_property_model_entry_removed_cb (RHYTHMDB_QUERY_MODEL (model),
283 entry,
284 propmodel);
285 return FALSE;
288 static gboolean
289 _add_entry_cb (GtkTreeModel *model,
290 GtkTreePath *path,
291 GtkTreeIter *iter,
292 RhythmDBPropertyModel *propmodel)
294 rhythmdb_property_model_row_inserted_cb (model, path, iter, propmodel);
295 return FALSE;
298 static void
299 rhythmdb_property_model_set_query_model_internal (RhythmDBPropertyModel *model,
300 RhythmDBQueryModel *query_model)
302 if (model->priv->query_model != NULL) {
303 g_signal_handlers_disconnect_by_func (model->priv->query_model,
304 G_CALLBACK (rhythmdb_property_model_row_inserted_cb),
305 model);
306 g_signal_handlers_disconnect_by_func (model->priv->query_model,
307 G_CALLBACK (rhythmdb_property_model_entry_removed_cb),
308 model);
309 g_signal_handlers_disconnect_by_func (model->priv->query_model,
310 G_CALLBACK (rhythmdb_property_model_prop_changed_cb),
311 model);
313 gtk_tree_model_foreach (GTK_TREE_MODEL (model->priv->query_model),
314 (GtkTreeModelForeachFunc)_remove_entry_cb,
315 model);
317 g_object_unref (model->priv->query_model);
320 model->priv->query_model = query_model;
321 g_assert (rhythmdb_property_model_iter_n_children (GTK_TREE_MODEL (model), NULL) == 1);
323 if (model->priv->query_model != NULL) {
324 g_object_ref (model->priv->query_model);
326 g_signal_connect_object (model->priv->query_model,
327 "row_inserted",
328 G_CALLBACK (rhythmdb_property_model_row_inserted_cb),
329 model,
331 g_signal_connect_object (model->priv->query_model,
332 "post-entry-delete",
333 G_CALLBACK (rhythmdb_property_model_entry_removed_cb),
334 model,
336 g_signal_connect_object (model->priv->query_model,
337 "entry-prop-changed",
338 G_CALLBACK (rhythmdb_property_model_prop_changed_cb),
339 model,
341 gtk_tree_model_foreach (GTK_TREE_MODEL (model->priv->query_model),
342 (GtkTreeModelForeachFunc)_add_entry_cb,
343 model);
347 static void
348 rhythmdb_property_model_set_property (GObject *object,
349 guint prop_id,
350 const GValue *value,
351 GParamSpec *pspec)
353 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (object);
355 switch (prop_id) {
356 case PROP_RHYTHMDB:
357 model->priv->db = g_value_get_object (value);
358 break;
359 case PROP_PROP:
360 model->priv->propid = g_value_get_int (value);
361 switch (model->priv->propid) {
362 case RHYTHMDB_PROP_GENRE:
363 model->priv->sort_propid = RHYTHMDB_PROP_GENRE;
364 break;
365 case RHYTHMDB_PROP_ARTIST:
366 model->priv->sort_propid = RHYTHMDB_PROP_ARTIST;
367 break;
368 case RHYTHMDB_PROP_ALBUM:
369 model->priv->sort_propid = RHYTHMDB_PROP_ALBUM;
370 break;
371 case RHYTHMDB_PROP_SUBTITLE:
372 model->priv->sort_propid = RHYTHMDB_PROP_SUBTITLE;
373 break;
374 case RHYTHMDB_PROP_TITLE:
375 case RHYTHMDB_PROP_LOCATION:
376 model->priv->sort_propid = RHYTHMDB_PROP_TITLE;
377 break;
378 default:
379 g_assert_not_reached ();
380 break;
382 break;
383 case PROP_QUERY_MODEL:
384 rhythmdb_property_model_set_query_model_internal (model, g_value_get_object (value));
385 break;
386 default:
387 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
388 break;
392 static void
393 rhythmdb_property_model_get_property (GObject *object,
394 guint prop_id,
395 GValue *value,
396 GParamSpec *pspec)
398 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (object);
400 switch (prop_id) {
401 case PROP_RHYTHMDB:
402 g_value_set_object (value, model->priv->db);
403 break;
404 case PROP_PROP:
405 g_value_set_int (value, model->priv->propid);
406 break;
407 case PROP_QUERY_MODEL:
408 g_value_set_object (value, model->priv->query_model);
409 break;
410 default:
411 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
412 break;
416 static void
417 rhythmdb_property_model_init (RhythmDBPropertyModel *model)
419 model->priv = RHYTHMDB_PROPERTY_MODEL_GET_PRIVATE (model);
421 model->priv->stamp = g_random_int ();
423 model->priv->properties = g_sequence_new (NULL);
424 model->priv->reverse_map = g_hash_table_new (g_str_hash, g_str_equal);
425 model->priv->entries = g_hash_table_new (g_direct_hash, g_direct_equal);
427 model->priv->all = g_new0 (RhythmDBPropertyModelEntry, 1);
428 model->priv->all->string = rb_refstring_new (_("All"));
431 static void
432 rhythmdb_property_model_finalize (GObject *object)
434 RhythmDBPropertyModel *model;
435 GSequencePtr ptr;
436 GSequencePtr end_ptr;
438 g_return_if_fail (object != NULL);
439 g_return_if_fail (RHYTHMDB_IS_PROPERTY_MODEL (object));
441 model = RHYTHMDB_PROPERTY_MODEL (object);
443 rb_debug ("finalizing property model %p", model);
445 g_return_if_fail (model->priv != NULL);
447 if (model->priv->syncing_id != 0)
448 g_source_remove (model->priv->syncing_id);
450 g_hash_table_destroy (model->priv->reverse_map);
452 end_ptr = g_sequence_get_end_ptr (model->priv->properties);
453 for (ptr = g_sequence_get_begin_ptr (model->priv->properties); ptr != end_ptr;
454 ptr = g_sequence_ptr_next (ptr)) {
455 RhythmDBPropertyModelEntry *prop = g_sequence_ptr_get_data (ptr);
456 rb_refstring_unref (prop->string);
457 rb_refstring_unref (prop->sort_string);
458 g_free (prop);
460 g_sequence_free (model->priv->properties);
462 g_hash_table_destroy (model->priv->entries);
464 if (model->priv->query_model != NULL) {
465 g_object_unref (model->priv->query_model);
468 G_OBJECT_CLASS (rhythmdb_property_model_parent_class)->finalize (object);
471 RhythmDBPropertyModel *
472 rhythmdb_property_model_new (RhythmDB *db,
473 RhythmDBPropType propid)
475 return g_object_new (RHYTHMDB_TYPE_PROPERTY_MODEL, "db", db, "prop", propid, NULL);
478 static void
479 rhythmdb_property_model_row_inserted_cb (GtkTreeModel *model,
480 GtkTreePath *path,
481 GtkTreeIter *iter,
482 RhythmDBPropertyModel *propmodel)
484 RhythmDBEntry *entry;
485 RhythmDBPropertyModelEntry *prop;
487 entry = rhythmdb_query_model_iter_to_entry (RHYTHMDB_QUERY_MODEL (model), iter);
489 prop = rhythmdb_property_model_insert (propmodel, entry);
490 rhythmdb_property_model_sync (propmodel);
492 rhythmdb_entry_unref (entry);
495 static void
496 rhythmdb_property_model_prop_changed_cb (RhythmDB *db,
497 RhythmDBEntry *entry,
498 RhythmDBPropType propid,
499 const GValue *old,
500 const GValue *new,
501 RhythmDBPropertyModel *propmodel)
503 if (propid == RHYTHMDB_PROP_HIDDEN) {
504 gboolean old_val = g_value_get_boolean (old);
505 gboolean new_val = g_value_get_boolean (new);
507 if (old_val != new_val) {
508 if (new_val == FALSE) {
509 g_assert (g_hash_table_remove (propmodel->priv->entries, entry));
510 rhythmdb_property_model_insert (propmodel, entry);
511 } else {
512 g_assert (g_hash_table_lookup (propmodel->priv->entries, entry) == NULL);
514 rhythmdb_property_model_delete (propmodel, entry);
515 g_hash_table_insert (propmodel->priv->entries, entry, GINT_TO_POINTER (1));
518 } else {
519 RhythmDBPropertyModelEntry *prop;
521 if (propid != propmodel->priv->propid)
522 return;
524 if (g_hash_table_lookup (propmodel->priv->entries, entry) != NULL)
525 return;
527 rhythmdb_property_model_delete_prop (propmodel, g_value_get_string (old));
528 prop = rhythmdb_property_model_insert (propmodel, entry);
531 rhythmdb_property_model_sync (propmodel);
534 static void
535 rhythmdb_property_model_entry_removed_cb (RhythmDBQueryModel *model,
536 RhythmDBEntry *entry,
537 RhythmDBPropertyModel *propmodel)
539 if (g_hash_table_remove (propmodel->priv->entries, entry))
540 return;
542 rhythmdb_property_model_delete (propmodel, entry);
543 rhythmdb_property_model_sync (propmodel);
546 static gint
547 rhythmdb_property_model_compare (RhythmDBPropertyModelEntry *a,
548 RhythmDBPropertyModelEntry *b,
549 RhythmDBPropertyModel *model)
551 const char *a_str, *b_str;
553 a_str = rb_refstring_get_sort_key (a->sort_string);
554 b_str = rb_refstring_get_sort_key (b->sort_string);
556 return strcmp (a_str, b_str);
559 static RhythmDBPropertyModelEntry *
560 rhythmdb_property_model_insert (RhythmDBPropertyModel *model,
561 RhythmDBEntry *entry)
563 RhythmDBPropertyModelEntry *prop;
564 GtkTreeIter iter;
565 GtkTreePath *path;
566 GSequencePtr ptr;
567 const char *propstr;
569 propstr = rhythmdb_entry_get_string (entry, model->priv->propid);
571 model->priv->all->refcount++;
573 if ((ptr = g_hash_table_lookup (model->priv->reverse_map, propstr))) {
574 prop = g_sequence_ptr_get_data (ptr);
575 prop->refcount++;
576 rb_debug ("adding \"%s\": refcount %d", propstr, prop->refcount);
577 return prop;
579 rb_debug ("adding new property \"%s\"", propstr);
581 prop = g_new0 (RhythmDBPropertyModelEntry, 1);
582 prop->string = rb_refstring_new (propstr);
583 prop->sort_string = rb_refstring_new (rhythmdb_entry_get_string (entry, model->priv->sort_propid));
584 prop->refcount = 1;
586 iter.stamp = model->priv->stamp;
587 ptr = g_sequence_insert_sorted (model->priv->properties, prop,
588 (GCompareDataFunc) rhythmdb_property_model_compare,
589 model);
590 g_hash_table_insert (model->priv->reverse_map,
591 (gpointer)rb_refstring_get (prop->string),
592 ptr);
594 iter.user_data = ptr;
595 path = rhythmdb_property_model_get_path (GTK_TREE_MODEL (model), &iter);
596 gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
597 gtk_tree_path_free (path);
599 return prop;
602 static void
603 rhythmdb_property_model_delete (RhythmDBPropertyModel *model,
604 RhythmDBEntry *entry)
606 const char *propstr;
608 if (g_hash_table_lookup (model->priv->entries, entry))
609 return;
611 propstr = rhythmdb_entry_get_string (entry, model->priv->propid);
612 rhythmdb_property_model_delete_prop (model, propstr);
615 static void
616 rhythmdb_property_model_delete_prop (RhythmDBPropertyModel *model,
617 const char *propstr)
619 GSequencePtr ptr;
620 RhythmDBPropertyModelEntry *prop;
621 GtkTreePath *path;
622 GtkTreeIter iter;
624 g_assert ((ptr = g_hash_table_lookup (model->priv->reverse_map, propstr)));
626 model->priv->all->refcount--;
628 prop = g_sequence_ptr_get_data (ptr);
629 rb_debug ("deleting \"%s\": refcount: %d", propstr, prop->refcount);
630 prop->refcount--;
631 if (prop->refcount > 0)
632 return;
634 iter.stamp = model->priv->stamp;
635 iter.user_data = ptr;
637 path = rhythmdb_property_model_get_path (GTK_TREE_MODEL (model), &iter);
638 g_signal_emit (G_OBJECT (model), rhythmdb_property_model_signals[PRE_ROW_DELETION], 0);
639 gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
640 gtk_tree_path_free (path);
641 g_sequence_remove (ptr);
642 g_hash_table_remove (model->priv->reverse_map, propstr);
643 rb_refstring_unref (prop->string);
644 rb_refstring_unref (prop->sort_string);
645 g_free (prop);
646 return;
649 gboolean
650 rhythmdb_property_model_iter_from_string (RhythmDBPropertyModel *model,
651 const char *name,
652 GtkTreeIter *iter)
654 GSequencePtr ptr;
656 if (name == NULL) {
657 if (iter) {
658 iter->stamp = model->priv->stamp;
659 iter->user_data = model->priv->all;
661 return TRUE;
664 ptr = g_hash_table_lookup (model->priv->reverse_map, name);
665 if (!ptr)
666 return FALSE;
668 if (iter) {
669 iter->stamp = model->priv->stamp;
670 iter->user_data = ptr;
673 return TRUE;
676 static GtkTreeModelFlags
677 rhythmdb_property_model_get_flags (GtkTreeModel *model)
679 return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY;
682 static gint
683 rhythmdb_property_model_get_n_columns (GtkTreeModel *tree_model)
685 return RHYTHMDB_PROPERTY_MODEL_COLUMN_LAST;
688 static GType
689 rhythmdb_property_model_get_column_type (GtkTreeModel *tree_model,
690 int index)
692 switch (index) {
693 case RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE:
694 return G_TYPE_STRING;
695 case RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY:
696 return G_TYPE_BOOLEAN;
697 case RHYTHMDB_PROPERTY_MODEL_COLUMN_NUMBER:
698 return G_TYPE_UINT;
699 default:
700 g_assert_not_reached ();
701 return G_TYPE_INVALID;
705 static gboolean
706 rhythmdb_property_model_get_iter (GtkTreeModel *tree_model,
707 GtkTreeIter *iter,
708 GtkTreePath *path)
710 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
711 guint index;
712 GSequencePtr ptr;
714 index = gtk_tree_path_get_indices (path)[0];
716 if (index == 0) {
717 iter->stamp = model->priv->stamp;
718 iter->user_data = model->priv->all;
719 return TRUE;
722 index--;
723 if (index >= g_sequence_get_length (model->priv->properties))
724 return FALSE;
726 ptr = g_sequence_get_ptr_at_pos (model->priv->properties, index);
728 iter->stamp = model->priv->stamp;
729 iter->user_data = ptr;
731 return TRUE;
734 static GtkTreePath *
735 rhythmdb_property_model_get_path (GtkTreeModel *tree_model,
736 GtkTreeIter *iter)
738 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
739 GtkTreePath *path;
741 g_return_val_if_fail (iter->stamp == model->priv->stamp, NULL);
743 if (iter->user_data == model->priv->all) {
744 return gtk_tree_path_new_first ();
747 if (g_sequence_ptr_is_end (iter->user_data))
748 return NULL;
750 path = gtk_tree_path_new ();
751 if (iter->user_data == model->priv->all)
752 gtk_tree_path_append_index (path, 0);
753 else
754 gtk_tree_path_append_index (path, g_sequence_ptr_get_position (iter->user_data) + 1);
755 return path;
758 static void
759 rhythmdb_property_model_get_value (GtkTreeModel *tree_model,
760 GtkTreeIter *iter,
761 gint column,
762 GValue *value)
764 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
766 g_return_if_fail (model->priv->stamp == iter->stamp);
768 if (iter->user_data == model->priv->all) {
769 switch (column) {
770 case RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE:
771 g_value_init (value, G_TYPE_STRING);
772 g_value_set_string (value, rb_refstring_get (model->priv->all->string));
773 break;
774 case RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY:
775 g_value_init (value, G_TYPE_BOOLEAN);
776 g_value_set_boolean (value, TRUE);
777 break;
778 case RHYTHMDB_PROPERTY_MODEL_COLUMN_NUMBER:
779 g_value_init (value, G_TYPE_UINT);
780 g_value_set_uint (value, model->priv->all->refcount);
781 break;
782 default:
783 g_assert_not_reached ();
785 } else {
786 RhythmDBPropertyModelEntry *prop;
788 g_return_if_fail (!g_sequence_ptr_is_end (iter->user_data));
790 prop = g_sequence_ptr_get_data (iter->user_data);
791 switch (column) {
792 case RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE:
793 g_value_init (value, G_TYPE_STRING);
794 g_value_set_string (value, rb_refstring_get (prop->string));
795 break;
796 case RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY:
797 g_value_init (value, G_TYPE_BOOLEAN);
798 g_value_set_boolean (value, prop == model->priv->all);
799 break;
800 case RHYTHMDB_PROPERTY_MODEL_COLUMN_NUMBER:
801 g_value_init (value, G_TYPE_UINT);
802 g_value_set_uint (value, prop->refcount);
803 break;
804 default:
805 g_assert_not_reached ();
810 static gboolean
811 rhythmdb_property_model_iter_next (GtkTreeModel *tree_model,
812 GtkTreeIter *iter)
814 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
816 g_return_val_if_fail (iter->stamp == model->priv->stamp, FALSE);
818 if (iter->user_data == model->priv->all) {
819 iter->user_data = g_sequence_get_begin_ptr (model->priv->properties);
820 } else {
821 g_return_val_if_fail (!g_sequence_ptr_is_end (iter->user_data), FALSE);
822 iter->user_data = g_sequence_ptr_next (iter->user_data);
825 return !g_sequence_ptr_is_end (iter->user_data);
828 static gboolean
829 rhythmdb_property_model_iter_children (GtkTreeModel *tree_model,
830 GtkTreeIter *iter,
831 GtkTreeIter *parent)
833 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
835 if (parent != NULL)
836 return FALSE;
838 iter->stamp = model->priv->stamp;
839 iter->user_data = model->priv->all;
841 return TRUE;
844 static gboolean
845 rhythmdb_property_model_iter_has_child (GtkTreeModel *tree_model,
846 GtkTreeIter *iter)
848 return FALSE;
851 static gint
852 rhythmdb_property_model_iter_n_children (GtkTreeModel *tree_model,
853 GtkTreeIter *iter)
855 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
857 if (iter)
858 g_return_val_if_fail (model->priv->stamp == iter->stamp, -1);
860 if (iter == NULL)
861 return 1 + g_sequence_get_length (model->priv->properties);
863 return 0;
866 static gboolean
867 rhythmdb_property_model_iter_nth_child (GtkTreeModel *tree_model,
868 GtkTreeIter *iter,
869 GtkTreeIter *parent,
870 gint n)
872 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
873 GSequencePtr child;
875 if (parent)
876 return FALSE;
878 if (n != 0) {
879 child = g_sequence_get_ptr_at_pos (model->priv->properties, n);
881 if (g_sequence_ptr_is_end (child))
882 return FALSE;
883 iter->user_data = child;
884 } else {
885 iter->user_data = model->priv->all;
888 iter->stamp = model->priv->stamp;
890 return TRUE;
893 static gboolean
894 rhythmdb_property_model_iter_parent (GtkTreeModel *tree_model,
895 GtkTreeIter *iter,
896 GtkTreeIter *child)
898 return FALSE;
901 static gboolean
902 rhythmdb_property_model_row_draggable (RbTreeDragSource *dragsource,
903 GList *paths)
905 return TRUE;
908 static gboolean
909 rhythmdb_property_model_drag_data_delete (RbTreeDragSource *dragsource,
910 GList *paths)
912 /* not supported */
913 return TRUE;
916 /*Going through hoops to avoid nested functions*/
917 struct QueryModelCbStruct {
918 RhythmDB *db;
919 GString *reply;
920 gint target;
923 static gboolean
924 query_model_cb (GtkTreeModel *query_model,
925 GtkTreePath *path,
926 GtkTreeIter *iter,
927 struct QueryModelCbStruct *data)
929 RhythmDBEntry *entry;
931 gtk_tree_model_get (query_model, iter, 0, &entry, -1);
932 if (data->target == TARGET_ENTRIES) {
933 g_string_append_printf (data->reply,
934 "%ld",
935 rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_ENTRY_ID));
936 } else if (data->target == TARGET_URIS) {
937 const char *uri;
938 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
939 g_string_append (data->reply, uri);
941 g_string_append (data->reply, "\r\n");
943 rhythmdb_entry_unref (entry);
944 return FALSE;
947 static gboolean
948 rhythmdb_property_model_drag_data_get (RbTreeDragSource *dragsource,
949 GList *paths,
950 GtkSelectionData *selection_data)
952 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (dragsource);
953 guint target;
954 GtkTargetList *drag_target_list;
956 switch (model->priv->propid) {
957 case RHYTHMDB_PROP_GENRE:
958 drag_target_list = rhythmdb_property_model_genre_drag_target_list;
959 break;
960 case RHYTHMDB_PROP_ALBUM:
961 drag_target_list = rhythmdb_property_model_album_drag_target_list;
962 break;
963 case RHYTHMDB_PROP_ARTIST:
964 drag_target_list = rhythmdb_property_model_artist_drag_target_list;
965 break;
966 case RHYTHMDB_PROP_LOCATION:
967 drag_target_list = rhythmdb_property_model_location_drag_target_list;
968 break;
969 default:
970 g_assert_not_reached ();
973 if (!gtk_target_list_find (drag_target_list,
974 selection_data->target,
975 &target)) {
976 return FALSE;
979 if (target == TARGET_URIS || target == TARGET_ENTRIES) {
980 RhythmDB *db = model->priv->db;
981 RhythmDBQueryModel *query_model;
982 GString* reply = g_string_new ("");
983 GtkTreeIter iter;
984 gboolean is_all = FALSE;
985 struct QueryModelCbStruct tmp;
986 GtkTreePath *path;
987 GCompareDataFunc sort_func = NULL;
988 gpointer sort_data;
989 gboolean sort_reverse;
991 query_model = rhythmdb_query_model_new_empty (db);
992 /* FIXME the sort order on the query model at this point is usually
993 * not the user's selected sort order.
995 g_object_get (G_OBJECT (model->priv->query_model),
996 "sort-func", &sort_func,
997 "sort-data", &sort_data,
998 "sort-reverse", &sort_reverse,
999 NULL);
1000 rhythmdb_query_model_set_sort_order (RHYTHMDB_QUERY_MODEL (query_model),
1001 sort_func, GUINT_TO_POINTER (sort_data), NULL, sort_reverse);
1003 rb_debug ("getting drag data as uri list");
1004 /* check if first selected row is 'All' */
1005 path = gtk_tree_row_reference_get_path (paths->data);
1006 if (path && gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path))
1007 gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
1008 RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY,
1009 &is_all, -1);
1010 gtk_tree_path_free (path);
1011 if (is_all) {
1012 g_object_set (query_model,
1013 "base-model", model->priv->query_model,
1014 NULL);
1015 } else {
1016 GList *row;
1017 GPtrArray *subquery = g_ptr_array_new ();
1019 for (row = paths; row; row = row->next) {
1020 char* name;
1021 path = gtk_tree_row_reference_get_path (row->data);
1022 if (path && gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path)) {
1023 gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
1024 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE,
1025 &name, -1);
1026 if (row == paths) {
1027 rhythmdb_query_append (db, subquery,
1028 RHYTHMDB_QUERY_PROP_EQUALS,
1029 model->priv->propid, name,
1030 RHYTHMDB_QUERY_END);
1031 } else {
1032 rhythmdb_query_append (db, subquery,
1033 RHYTHMDB_QUERY_DISJUNCTION,
1034 RHYTHMDB_QUERY_PROP_EQUALS,
1035 model->priv->propid, name,
1036 RHYTHMDB_QUERY_END);
1040 gtk_tree_path_free (path);
1041 g_free (name);
1044 g_object_set (query_model,
1045 "query", subquery,
1046 "base-model", model->priv->query_model,
1047 NULL);
1048 rhythmdb_query_free (subquery);
1051 tmp.db = db;
1052 tmp.reply = reply;
1053 tmp.target = target;
1054 /* Too bad that we're on the main thread. Why doesn't gtk call us async?
1055 * How does file-roller manage? - it seems it refuses the drop when it isn't
1056 * done unpacking. In which case, we should tweak the drop acknowledgement,
1057 * and prepare the query using do_full_query_async. The query would be
1058 * hooked to the drag context.
1060 gtk_tree_model_foreach (GTK_TREE_MODEL (query_model),
1061 (GtkTreeModelForeachFunc) query_model_cb,
1062 &tmp);
1064 g_object_unref (query_model);
1066 gtk_selection_data_set (selection_data,
1067 selection_data->target,
1068 8, (guchar *)reply->str,
1069 reply->len);
1070 g_string_free (reply, TRUE);
1072 } else {
1073 char* title;
1074 GList *p;
1075 GString* reply = g_string_new ("");
1077 rb_debug ("getting drag data as list of property values");
1079 for (p = paths; p; p = p->next) {
1080 GtkTreeIter iter;
1081 GtkTreePath *path;
1083 path = gtk_tree_row_reference_get_path (p->data);
1084 if (path && gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path)) {
1085 gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
1086 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE, &title, -1);
1087 g_string_append (reply, title);
1088 if (p->next)
1089 g_string_append (reply, "\r\n");
1090 g_free (title);
1092 gtk_tree_path_free (path);
1094 gtk_selection_data_set (selection_data,
1095 selection_data->target,
1096 8, (guchar *)reply->str,
1097 reply->len);
1098 g_string_free (reply, TRUE);
1101 return TRUE;
1104 void
1105 rhythmdb_property_model_enable_drag (RhythmDBPropertyModel *model,
1106 GtkTreeView *view)
1108 const GtkTargetEntry *targets;
1109 gint n_elements;
1111 switch (model->priv->propid) {
1112 case RHYTHMDB_PROP_GENRE:
1113 targets = targets_genre;
1114 n_elements = G_N_ELEMENTS (targets_genre);
1115 break;
1116 case RHYTHMDB_PROP_ALBUM:
1117 targets = targets_album;
1118 n_elements = G_N_ELEMENTS (targets_album);
1119 break;
1120 case RHYTHMDB_PROP_ARTIST:
1121 targets = targets_artist;
1122 n_elements = G_N_ELEMENTS (targets_artist);
1123 break;
1124 case RHYTHMDB_PROP_LOCATION:
1125 targets = targets_location;
1126 n_elements = G_N_ELEMENTS (targets_location);
1127 break;
1128 default:
1129 g_assert_not_reached ();
1132 rb_tree_dnd_add_drag_source_support (view,
1133 GDK_BUTTON1_MASK,
1134 targets, n_elements,
1135 GDK_ACTION_COPY);
1138 static gboolean
1139 rhythmdb_property_model_perform_sync (RhythmDBPropertyModel *model)
1141 GtkTreeIter iter;
1142 GtkTreePath *path;
1144 GDK_THREADS_ENTER ();
1146 iter.stamp = model->priv->stamp;
1147 iter.user_data = model->priv->all;
1148 path = rhythmdb_property_model_get_path (GTK_TREE_MODEL (model), &iter);
1149 gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
1150 gtk_tree_path_free (path);
1152 model->priv->syncing_id = 0;
1153 GDK_THREADS_LEAVE ();
1154 return FALSE;
1157 static void
1158 rhythmdb_property_model_sync (RhythmDBPropertyModel *model)
1160 if (model->priv->syncing_id != 0)
1161 return;
1163 model->priv->syncing_id = g_idle_add ((GSourceFunc)rhythmdb_property_model_perform_sync, model);
1166 /* This should really be standard. */
1167 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
1169 GType
1170 rhythmdb_property_model_column_get_type (void)
1172 static GType etype = 0;
1174 if (etype == 0) {
1175 static const GEnumValue values[] = {
1176 ENUM_ENTRY (RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE, "Property title"),
1177 ENUM_ENTRY (RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY, "Value priority"),
1178 ENUM_ENTRY (RHYTHMDB_PROPERTY_MODEL_COLUMN_NUMBER, "Track count"),
1179 { 0, 0, 0 }
1182 etype = g_enum_register_static ("RhythmDBPropertyModelColumn", values);
1185 return etype;