No issues found
Tool | Failure ID | Location | Function | Message | Data |
---|---|---|---|---|---|
clang-analyzer | no-output-found | rhythmdb-property-model.c | Message(text='Unable to locate XML output from invoke-clang-analyzer') | None | |
clang-analyzer | no-output-found | rhythmdb-property-model.c | Message(text='Unable to locate XML output from invoke-clang-analyzer') | None |
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2003 Colin Walters <walters@gnome.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
26 *
27 */
28
29 #include "config.h"
30
31 #include <unistd.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <glib/gi18n.h>
35 #include <glib.h>
36
37 #include "rhythmdb-property-model.h"
38 #include "rb-debug.h"
39 #include "rb-refstring.h"
40 #include "rb-tree-dnd.h"
41
42 static void rhythmdb_property_model_tree_model_init (GtkTreeModelIface *iface);
43 static void rhythmdb_property_model_drag_source_init (RbTreeDragSourceIface *iface);
44
45 G_DEFINE_TYPE_WITH_CODE(RhythmDBPropertyModel, rhythmdb_property_model, G_TYPE_OBJECT,
46 G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL,
47 rhythmdb_property_model_tree_model_init)
48 G_IMPLEMENT_INTERFACE(RB_TYPE_TREE_DRAG_SOURCE,
49 rhythmdb_property_model_drag_source_init))
50
51 /*
52 * Structure for entries in the property model.
53 * The sort string is derived from one of a list of properties, so we
54 * store the index in the list of the property that gave us the current
55 * sort string, so that if a newly added entry has a value for a more preferred
56 * property, we can use that instead.
57 */
58 typedef struct {
59 RBRefString *string;
60 RBRefString *sort_string;
61 gint sort_string_from;
62 gint refcount;
63 } RhythmDBPropertyModelEntry;
64
65 static void rhythmdb_property_model_dispose (GObject *object);
66 static void rhythmdb_property_model_finalize (GObject *object);
67 static void rhythmdb_property_model_set_property (GObject *object,
68 guint prop_id,
69 const GValue *value,
70 GParamSpec *pspec);
71 static void rhythmdb_property_model_get_property (GObject *object,
72 guint prop_id,
73 GValue *value,
74 GParamSpec *pspec);
75 static void rhythmdb_property_model_sync (RhythmDBPropertyModel *model);
76 static void rhythmdb_property_model_row_inserted_cb (GtkTreeModel *model,
77 GtkTreePath *path,
78 GtkTreeIter *iter,
79 RhythmDBPropertyModel *propmodel);
80 static void rhythmdb_property_model_prop_changed_cb (RhythmDB *db, RhythmDBEntry *entry,
81 RhythmDBPropType prop, const GValue *old,
82 const GValue *new,
83 RhythmDBPropertyModel *propmodel);
84 static void rhythmdb_property_model_entry_removed_cb (RhythmDBQueryModel *model,
85 RhythmDBEntry *entry,
86 RhythmDBPropertyModel *propmodel);
87 static gboolean update_sort_string (RhythmDBPropertyModel *model,
88 RhythmDBPropertyModelEntry *prop,
89 RhythmDBEntry *entry);
90 static void property_sort_changed (RhythmDBPropertyModel *model,
91 GSequenceIter *ptr,
92 GtkTreeIter *iter);
93 static RhythmDBPropertyModelEntry* rhythmdb_property_model_insert (RhythmDBPropertyModel *model,
94 RhythmDBEntry *entry);
95 static void rhythmdb_property_model_delete (RhythmDBPropertyModel *model,
96 RhythmDBEntry *entry);
97 static void rhythmdb_property_model_delete_prop (RhythmDBPropertyModel *model,
98 const char *propstr);
99 static GtkTreeModelFlags rhythmdb_property_model_get_flags (GtkTreeModel *model);
100 static gint rhythmdb_property_model_get_n_columns (GtkTreeModel *tree_model);
101 static GType rhythmdb_property_model_get_column_type (GtkTreeModel *tree_model, int index);
102 static gboolean rhythmdb_property_model_get_iter (GtkTreeModel *tree_model, GtkTreeIter *iter,
103 GtkTreePath *path);
104 static GtkTreePath * rhythmdb_property_model_get_path (GtkTreeModel *tree_model,
105 GtkTreeIter *iter);
106 static void rhythmdb_property_model_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter,
107 gint column, GValue *value);
108 static gboolean rhythmdb_property_model_iter_next (GtkTreeModel *tree_model,
109 GtkTreeIter *iter);
110 static gboolean rhythmdb_property_model_iter_children (GtkTreeModel *tree_model,
111 GtkTreeIter *iter,
112 GtkTreeIter *parent);
113 static gboolean rhythmdb_property_model_iter_has_child (GtkTreeModel *tree_model,
114 GtkTreeIter *iter);
115 static gint rhythmdb_property_model_iter_n_children (GtkTreeModel *tree_model,
116 GtkTreeIter *iter);
117 static gboolean rhythmdb_property_model_iter_nth_child (GtkTreeModel *tree_model,
118 GtkTreeIter *iter, GtkTreeIter *parent,
119 gint n);
120 static gboolean rhythmdb_property_model_iter_parent (GtkTreeModel *tree_model,
121 GtkTreeIter *iter,
122 GtkTreeIter *child);
123
124 static gboolean rhythmdb_property_model_drag_data_get (RbTreeDragSource *dragsource,
125 GList *paths,
126 GtkSelectionData *selection_data);
127 static gboolean rhythmdb_property_model_drag_data_delete (RbTreeDragSource *dragsource,
128 GList *paths);
129 static gboolean rhythmdb_property_model_row_draggable (RbTreeDragSource *dragsource,
130 GList *paths);
131
132 enum {
133 TARGET_ALBUMS,
134 TARGET_GENRE,
135 TARGET_ARTISTS,
136 TARGET_LOCATION,
137 TARGET_ENTRIES,
138 TARGET_URIS,
139 };
140
141 static const GtkTargetEntry targets_album [] = {
142 { "text/x-rhythmbox-album", 0, TARGET_ALBUMS },
143 { "application/x-rhythmbox-entry", 0, TARGET_ENTRIES },
144 { "text/uri-list", 0, TARGET_URIS },
145 };
146 static const GtkTargetEntry targets_genre [] = {
147 { "text/x-rhythmbox-genre", 0, TARGET_GENRE },
148 { "application/x-rhythmbox-entry", 0, TARGET_ENTRIES },
149 { "text/uri-list", 0, TARGET_URIS },
150 };
151 static const GtkTargetEntry targets_artist [] = {
152 { "text/x-rhythmbox-artist", 0, TARGET_ARTISTS },
153 { "application/x-rhythmbox-entry", 0, TARGET_ENTRIES },
154 { "text/uri-list", 0, TARGET_URIS },
155 };
156 static const GtkTargetEntry targets_location [] = {
157 { "text/x-rhythmbox-location", 0, TARGET_LOCATION },
158 { "application/x-rhythmbox-entry", 0, TARGET_ENTRIES },
159 { "text/uri-list", 0, TARGET_URIS },
160 };
161
162 static GtkTargetList *rhythmdb_property_model_album_drag_target_list = NULL;
163 static GtkTargetList *rhythmdb_property_model_artist_drag_target_list = NULL;
164 static GtkTargetList *rhythmdb_property_model_genre_drag_target_list = NULL;
165 static GtkTargetList *rhythmdb_property_model_location_drag_target_list = NULL;
166
167 struct RhythmDBPropertyModelPrivate
168 {
169 RhythmDB *db;
170
171 RhythmDBQueryModel *query_model;
172 GHashTable *entries;
173
174 RhythmDBPropType propid;
175 GArray *sort_propids;
176
177 guint stamp;
178
179 GSequence *properties;
180 GHashTable *reverse_map;
181
182 RhythmDBPropertyModelEntry *all;
183
184 guint syncing_id;
185 };
186
187 #define RHYTHMDB_PROPERTY_MODEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE_PROPERTY_MODEL, RhythmDBPropertyModelPrivate))
188
189 enum
190 {
191 PRE_ROW_DELETION,
192 LAST_SIGNAL
193 };
194
195 enum
196 {
197 PROP_0,
198 PROP_RHYTHMDB,
199 PROP_PROP,
200 PROP_QUERY_MODEL,
201 };
202
203 static guint rhythmdb_property_model_signals[LAST_SIGNAL] = { 0 };
204
205 /**
206 * SECTION:rhythmdb-property-model
207 * @short_description: tree model grouping entries from a query model by property values
208 *
209 * A RhythmDBPropertyModel groups the entries in a #RhythmDBQueryModel by
210 * the value of a property. For example, a RhythmDBPropertyModel using
211 * the RHYTHMDB_PROP_ARTIST property can be used as the model for a
212 * #GtkTreeView that will list the artists present in the query model.
213 *
214 * The album/artist/genre browsers displayed in the library and other sources are
215 * populated using a RhythmDBPropertyModel for each property.
216 */
217
218 static void
219 rhythmdb_property_model_class_init (RhythmDBPropertyModelClass *klass)
220 {
221 GObjectClass *object_class = G_OBJECT_CLASS (klass);
222
223 if (!rhythmdb_property_model_artist_drag_target_list)
224 rhythmdb_property_model_artist_drag_target_list =
225 gtk_target_list_new (targets_artist,
226 G_N_ELEMENTS (targets_artist));
227 if (!rhythmdb_property_model_album_drag_target_list)
228 rhythmdb_property_model_album_drag_target_list =
229 gtk_target_list_new (targets_album,
230 G_N_ELEMENTS (targets_album));
231 if (!rhythmdb_property_model_genre_drag_target_list)
232 rhythmdb_property_model_genre_drag_target_list =
233 gtk_target_list_new (targets_genre,
234 G_N_ELEMENTS (targets_genre));
235 if (!rhythmdb_property_model_location_drag_target_list)
236 rhythmdb_property_model_location_drag_target_list =
237 gtk_target_list_new (targets_location,
238 G_N_ELEMENTS (targets_location));
239
240 object_class->set_property = rhythmdb_property_model_set_property;
241 object_class->get_property = rhythmdb_property_model_get_property;
242
243 object_class->dispose = rhythmdb_property_model_dispose;
244 object_class->finalize = rhythmdb_property_model_finalize;
245
246 /**
247 * RhythmDBPropertyModel::pre-row-deletion:
248 * @model: the #RhythmDBPropertyModel
249 *
250 * Emitted just before a row is deleted from the model.
251 */
252 rhythmdb_property_model_signals[PRE_ROW_DELETION] =
253 g_signal_new ("pre-row-deletion",
254 G_OBJECT_CLASS_TYPE (object_class),
255 G_SIGNAL_RUN_LAST,
256 G_STRUCT_OFFSET (RhythmDBPropertyModelClass, pre_row_deletion),
257 NULL, NULL,
258 g_cclosure_marshal_VOID__VOID,
259 G_TYPE_NONE,
260 0);
261
262 /**
263 * RhythmDBPropertyModel:db:
264 *
265 * The #RhythmDB object the model is associated with.
266 */
267 g_object_class_install_property (object_class,
268 PROP_RHYTHMDB,
269 g_param_spec_object ("db",
270 "RhythmDB",
271 "RhythmDB object",
272 RHYTHMDB_TYPE,
273 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
274
275 /**
276 * RhythmDBPropertyModel:prop:
277 *
278 * The property that this property model indexes.
279 */
280 g_object_class_install_property (object_class,
281 PROP_PROP,
282 g_param_spec_int ("prop",
283 "propid",
284 "Property id",
285 0, RHYTHMDB_NUM_PROPERTIES,
286 RHYTHMDB_PROP_TYPE,
287 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
288
289 /**
290 * RhythmDBPropertyModel:query-model:
291 *
292 * The query model that this property model indexes.
293 */
294 g_object_class_install_property (object_class,
295 PROP_QUERY_MODEL,
296 g_param_spec_object ("query-model",
297 "RhythmDBQueryModel",
298 "RhythmDBQueryModel object ",
299 RHYTHMDB_TYPE_QUERY_MODEL,
300 G_PARAM_READWRITE));
301
302 g_type_class_add_private (klass, sizeof (RhythmDBPropertyModelPrivate));
303 }
304
305 static void
306 rhythmdb_property_model_tree_model_init (GtkTreeModelIface *iface)
307 {
308 iface->get_flags = rhythmdb_property_model_get_flags;
309 iface->get_n_columns = rhythmdb_property_model_get_n_columns;
310 iface->get_column_type = rhythmdb_property_model_get_column_type;
311 iface->get_iter = rhythmdb_property_model_get_iter;
312 iface->get_path = rhythmdb_property_model_get_path;
313 iface->get_value = rhythmdb_property_model_get_value;
314 iface->iter_next = rhythmdb_property_model_iter_next;
315 iface->iter_children = rhythmdb_property_model_iter_children;
316 iface->iter_has_child = rhythmdb_property_model_iter_has_child;
317 iface->iter_n_children = rhythmdb_property_model_iter_n_children;
318 iface->iter_nth_child = rhythmdb_property_model_iter_nth_child;
319 iface->iter_parent = rhythmdb_property_model_iter_parent;
320 }
321
322 static void
323 rhythmdb_property_model_drag_source_init (RbTreeDragSourceIface *iface)
324 {
325 iface->rb_row_draggable = rhythmdb_property_model_row_draggable;
326 iface->rb_drag_data_delete = rhythmdb_property_model_drag_data_delete;
327 iface->rb_drag_data_get = rhythmdb_property_model_drag_data_get;
328 }
329
330 static gboolean
331 _remove_entry_cb (GtkTreeModel *model,
332 GtkTreePath *path,
333 GtkTreeIter *iter,
334 RhythmDBPropertyModel *propmodel)
335 {
336 RhythmDBEntry *entry;
337
338 entry = rhythmdb_query_model_iter_to_entry (RHYTHMDB_QUERY_MODEL (model), iter);
339 rhythmdb_property_model_entry_removed_cb (RHYTHMDB_QUERY_MODEL (model),
340 entry,
341 propmodel);
342 rhythmdb_entry_unref (entry);
343 return FALSE;
344 }
345
346 static gboolean
347 _add_entry_cb (GtkTreeModel *model,
348 GtkTreePath *path,
349 GtkTreeIter *iter,
350 RhythmDBPropertyModel *propmodel)
351 {
352 rhythmdb_property_model_row_inserted_cb (model, path, iter, propmodel);
353 return FALSE;
354 }
355
356 static void
357 rhythmdb_property_model_set_query_model_internal (RhythmDBPropertyModel *model,
358 RhythmDBQueryModel *query_model)
359 {
360 if (model->priv->query_model != NULL) {
361 g_signal_handlers_disconnect_by_func (model->priv->query_model,
362 G_CALLBACK (rhythmdb_property_model_row_inserted_cb),
363 model);
364 g_signal_handlers_disconnect_by_func (model->priv->query_model,
365 G_CALLBACK (rhythmdb_property_model_entry_removed_cb),
366 model);
367 g_signal_handlers_disconnect_by_func (model->priv->query_model,
368 G_CALLBACK (rhythmdb_property_model_prop_changed_cb),
369 model);
370
371 gtk_tree_model_foreach (GTK_TREE_MODEL (model->priv->query_model),
372 (GtkTreeModelForeachFunc)_remove_entry_cb,
373 model);
374
375 g_object_unref (model->priv->query_model);
376 }
377
378 model->priv->query_model = query_model;
379 g_assert (rhythmdb_property_model_iter_n_children (GTK_TREE_MODEL (model), NULL) == 1);
380
381 if (model->priv->query_model != NULL) {
382 g_object_ref (model->priv->query_model);
383
384 g_signal_connect_object (model->priv->query_model,
385 "row_inserted",
386 G_CALLBACK (rhythmdb_property_model_row_inserted_cb),
387 model,
388 0);
389 g_signal_connect_object (model->priv->query_model,
390 "post-entry-delete",
391 G_CALLBACK (rhythmdb_property_model_entry_removed_cb),
392 model,
393 0);
394 g_signal_connect_object (model->priv->query_model,
395 "entry-prop-changed",
396 G_CALLBACK (rhythmdb_property_model_prop_changed_cb),
397 model,
398 0);
399 gtk_tree_model_foreach (GTK_TREE_MODEL (model->priv->query_model),
400 (GtkTreeModelForeachFunc)_add_entry_cb,
401 model);
402 }
403 }
404
405 static void
406 append_sort_property (RhythmDBPropertyModel *model, RhythmDBPropType prop)
407 {
408 RhythmDBPropType p = prop;
409 g_array_append_val (model->priv->sort_propids, p);
410 }
411
412 static void
413 rhythmdb_property_model_set_property (GObject *object,
414 guint prop_id,
415 const GValue *value,
416 GParamSpec *pspec)
417 {
418 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (object);
419
420 switch (prop_id) {
421 case PROP_RHYTHMDB:
422 model->priv->db = g_value_get_object (value);
423 break;
424 case PROP_PROP:
425 model->priv->propid = g_value_get_int (value);
426 switch (model->priv->propid) {
427 case RHYTHMDB_PROP_GENRE:
428 append_sort_property (model, RHYTHMDB_PROP_GENRE);
429 break;
430 case RHYTHMDB_PROP_ARTIST:
431 append_sort_property (model, RHYTHMDB_PROP_ARTIST_SORTNAME);
432 append_sort_property (model, RHYTHMDB_PROP_ARTIST);
433 break;
434 case RHYTHMDB_PROP_ALBUM:
435 append_sort_property (model, RHYTHMDB_PROP_ALBUM_SORTNAME);
436 append_sort_property (model, RHYTHMDB_PROP_ALBUM);
437 break;
438 case RHYTHMDB_PROP_SUBTITLE:
439 append_sort_property (model, RHYTHMDB_PROP_ALBUM);
440 append_sort_property (model, RHYTHMDB_PROP_SUBTITLE);
441 break;
442 case RHYTHMDB_PROP_TITLE:
443 case RHYTHMDB_PROP_LOCATION:
444 append_sort_property (model, RHYTHMDB_PROP_TITLE);
445 break;
446 default:
447 g_assert_not_reached ();
448 break;
449 }
450 break;
451 case PROP_QUERY_MODEL:
452 rhythmdb_property_model_set_query_model_internal (model, g_value_get_object (value));
453 break;
454 default:
455 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
456 break;
457 }
458 }
459
460 static void
461 rhythmdb_property_model_get_property (GObject *object,
462 guint prop_id,
463 GValue *value,
464 GParamSpec *pspec)
465 {
466 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (object);
467
468 switch (prop_id) {
469 case PROP_RHYTHMDB:
470 g_value_set_object (value, model->priv->db);
471 break;
472 case PROP_PROP:
473 g_value_set_int (value, model->priv->propid);
474 break;
475 case PROP_QUERY_MODEL:
476 g_value_set_object (value, model->priv->query_model);
477 break;
478 default:
479 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
480 break;
481 }
482 }
483
484 static void
485 rhythmdb_property_model_init (RhythmDBPropertyModel *model)
486 {
487 model->priv = RHYTHMDB_PROPERTY_MODEL_GET_PRIVATE (model);
488
489 model->priv->stamp = g_random_int ();
490
491 model->priv->properties = g_sequence_new (NULL);
492 model->priv->reverse_map = g_hash_table_new (g_str_hash, g_str_equal);
493 model->priv->entries = g_hash_table_new (g_direct_hash, g_direct_equal);
494
495 model->priv->all = g_new0 (RhythmDBPropertyModelEntry, 1);
496 model->priv->all->string = rb_refstring_new (_("All"));
497
498 model->priv->sort_propids = g_array_new (FALSE, FALSE, sizeof (RhythmDBPropType));
499 }
500
501 static void
502 _prop_model_entry_cleanup (RhythmDBPropertyModelEntry *prop, gpointer data)
503 {
504 rb_refstring_unref (prop->string);
505 rb_refstring_unref (prop->sort_string);
506 g_free (prop);
507 }
508
509 static void
510 rhythmdb_property_model_dispose (GObject *object)
511 {
512 RhythmDBPropertyModel *model;
513
514 g_return_if_fail (object != NULL);
515 g_return_if_fail (RHYTHMDB_IS_PROPERTY_MODEL (object));
516
517 model = RHYTHMDB_PROPERTY_MODEL (object);
518
519 rb_debug ("disposing property model %p", model);
520
521 g_return_if_fail (model->priv != NULL);
522
523 if (model->priv->syncing_id != 0) {
524 g_source_remove (model->priv->syncing_id);
525 model->priv->syncing_id = 0;
526 }
527
528 if (model->priv->query_model != NULL) {
529 g_object_unref (model->priv->query_model);
530 model->priv->query_model = NULL;
531 }
532
533 G_OBJECT_CLASS (rhythmdb_property_model_parent_class)->dispose (object);
534 }
535
536 static void
537 rhythmdb_property_model_finalize (GObject *object)
538 {
539 RhythmDBPropertyModel *model;
540
541 g_return_if_fail (object != NULL);
542 g_return_if_fail (RHYTHMDB_IS_PROPERTY_MODEL (object));
543
544 model = RHYTHMDB_PROPERTY_MODEL (object);
545
546 rb_debug ("finalizing property model %p", model);
547
548 g_return_if_fail (model->priv != NULL);
549
550 g_hash_table_destroy (model->priv->reverse_map);
551
552 g_sequence_foreach (model->priv->properties, (GFunc)_prop_model_entry_cleanup, NULL);
553 g_sequence_free (model->priv->properties);
554
555 g_hash_table_destroy (model->priv->entries);
556
557 g_free (model->priv->all);
558
559 g_array_free (model->priv->sort_propids, TRUE);
560
561 G_OBJECT_CLASS (rhythmdb_property_model_parent_class)->finalize (object);
562 }
563
564 /**
565 * rhythmdb_property_model_new:
566 * @db: the #RhythmDB object
567 * @propid: the property to index
568 *
569 * Creates a new property model for the specified property ID.
570 *
571 * Return value: the new #RhythmDBPropertyModel
572 */
573 RhythmDBPropertyModel *
574 rhythmdb_property_model_new (RhythmDB *db,
575 RhythmDBPropType propid)
576 {
577 return g_object_new (RHYTHMDB_TYPE_PROPERTY_MODEL, "db", db, "prop", propid, NULL);
578 }
579
580 static void
581 rhythmdb_property_model_row_inserted_cb (GtkTreeModel *model,
582 GtkTreePath *path,
583 GtkTreeIter *iter,
584 RhythmDBPropertyModel *propmodel)
585 {
586 RhythmDBEntry *entry;
587
588 entry = rhythmdb_query_model_iter_to_entry (RHYTHMDB_QUERY_MODEL (model), iter);
589
590 rhythmdb_property_model_insert (propmodel, entry);
591 rhythmdb_property_model_sync (propmodel);
592
593 rhythmdb_entry_unref (entry);
594 }
595
596 static void
597 rhythmdb_property_model_prop_changed_cb (RhythmDB *db,
598 RhythmDBEntry *entry,
599 RhythmDBPropType propid,
600 const GValue *old,
601 const GValue *new,
602 RhythmDBPropertyModel *propmodel)
603 {
604 if (propid == RHYTHMDB_PROP_HIDDEN) {
605 gboolean old_val = g_value_get_boolean (old);
606 gboolean new_val = g_value_get_boolean (new);
607
608 if (old_val != new_val) {
609 if (new_val == FALSE) {
610 g_assert (g_hash_table_remove (propmodel->priv->entries, entry));
611 rhythmdb_property_model_insert (propmodel, entry);
612 } else {
613 g_assert (g_hash_table_lookup (propmodel->priv->entries, entry) == NULL);
614
615 rhythmdb_property_model_delete (propmodel, entry);
616 g_hash_table_insert (propmodel->priv->entries, entry, GINT_TO_POINTER (1));
617 }
618 rhythmdb_property_model_sync (propmodel);
619 }
620 } else if (g_hash_table_lookup (propmodel->priv->entries, entry) == NULL) {
621 RhythmDBPropertyModelEntry *prop;
622
623 if (propid == propmodel->priv->propid) {
624 /* the updated property is the propmodel's prop */
625 rhythmdb_property_model_delete_prop (propmodel, g_value_get_string (old));
626 prop = rhythmdb_property_model_insert (propmodel, entry);
627 rhythmdb_property_model_sync (propmodel);
628 } else {
629 int pi;
630 const char *propstr;
631 GtkTreeIter iter;
632 GSequenceIter *ptr;
633
634 /* check if the updated property is one of the propmodel's sort props */
635 for (pi = 0; pi < propmodel->priv->sort_propids->len; pi++) {
636 if (propid == g_array_index (propmodel->priv->sort_propids, RhythmDBPropType, pi)) {
637 propstr = rhythmdb_entry_get_string (entry, propmodel->priv->propid);
638 ptr = g_hash_table_lookup (propmodel->priv->reverse_map, propstr);
639 prop = g_sequence_get (ptr);
640 iter.stamp = propmodel->priv->stamp;
641 iter.user_data = ptr;
642
643 /* check if the sort prop needs updated and the propmodel needs resorting */
644 if (update_sort_string (propmodel, prop, entry)) {
645 property_sort_changed (propmodel, ptr, &iter);
646 } else if (pi == prop->sort_string_from) {
647 rb_refstring_unref (prop->sort_string);
648 prop->sort_string = rb_refstring_new (g_value_get_string (new));
649 property_sort_changed (propmodel, ptr, &iter);
650 }
651 break;
652 }
653 }
654 }
655 }
656 }
657
658 static void
659 rhythmdb_property_model_entry_removed_cb (RhythmDBQueryModel *model,
660 RhythmDBEntry *entry,
661 RhythmDBPropertyModel *propmodel)
662 {
663 if (g_hash_table_remove (propmodel->priv->entries, entry))
664 return;
665
666 rhythmdb_property_model_delete (propmodel, entry);
667 rhythmdb_property_model_sync (propmodel);
668 }
669
670 static gint
671 rhythmdb_property_model_compare (RhythmDBPropertyModelEntry *a,
672 RhythmDBPropertyModelEntry *b,
673 RhythmDBPropertyModel *model)
674 {
675 const char *a_str, *b_str;
676
677 a_str = rb_refstring_get_sort_key (a->sort_string);
678 b_str = rb_refstring_get_sort_key (b->sort_string);
679
680 return strcmp (a_str, b_str);
681 }
682
683 /*
684 * looks for an updated sort string in a new entry for the specified
685 * property. if the new entry provides a value for a higher priority
686 * sort property, updates the property to use the new sort string and
687 * returns TRUE. otherwise, returns FALSE.
688 */
689 static gboolean
690 update_sort_string (RhythmDBPropertyModel *model,
691 RhythmDBPropertyModelEntry *prop,
692 RhythmDBEntry *entry)
693 {
694 const char *newvalue = NULL;
695 int pi;
696 int upto;
697
698 /* if the property that gave us the current sort string is being cleared,
699 * forget about the current string.
700 */
701 if (prop->sort_string != NULL) {
702 RhythmDBPropType propid;
703 const char *newvalue;
704
705 propid = g_array_index (model->priv->sort_propids, RhythmDBPropType, prop->sort_string_from);
706 newvalue = rhythmdb_entry_get_string (entry, propid);
707 if (newvalue == NULL || newvalue[0] == '\0') {
708 rb_debug ("current sort string %s is being removed", rb_refstring_get (prop->sort_string));
709 rb_refstring_unref (prop->sort_string);
710 prop->sort_string = NULL;
711 }
712 }
713
714 /* we only need to check properties that are preferred to the one that
715 * gave us the current sort string.
716 */
717 upto = model->priv->sort_propids->len;
718 if (prop->sort_string != NULL) {
719 upto = prop->sort_string_from;
720 }
721
722 /* search for a non-empty sort string value */
723 for (pi = 0; pi < upto; pi++) {
724 RhythmDBPropType propid;
725
726 propid = g_array_index (model->priv->sort_propids, RhythmDBPropType, pi);
727 newvalue = rhythmdb_entry_get_string (entry, propid);
728 if (newvalue != NULL && newvalue[0] != '\0') {
729 break;
730 }
731 }
732
733 /* if we found one, replace the current sort string */
734 if (newvalue != NULL && newvalue[0] != '\0' && (prop->sort_string == NULL || pi < prop->sort_string_from)) {
735 rb_debug ("replacing current sort string %s with %s (%d -> %d)",
736 prop->sort_string ? rb_refstring_get (prop->sort_string) : "NULL",
737 newvalue,
738 prop->sort_string_from,
739 pi);
740 if (prop->sort_string != NULL) {
741 rb_refstring_unref (prop->sort_string);
742 }
743 prop->sort_string = rb_refstring_new (newvalue);
744 g_assert (pi < model->priv->sort_propids->len);
745 prop->sort_string_from = pi;
746 return TRUE;
747 }
748
749 /* if we can't find a sort string at all, use the display string as fallback */
750 if (prop->sort_string == NULL) {
751 prop->sort_string = rb_refstring_ref (prop->string);
752 }
753
754 return FALSE;
755 }
756
757 /*
758 * to be called when a property's position in the sorted order may have changed.
759 * emits row-deleted, resorts the property sequence, and emits row-inserted.
760 */
761 static void
762 property_sort_changed (RhythmDBPropertyModel *model, GSequenceIter *ptr, GtkTreeIter *iter)
763 {
764 GtkTreePath *path;
765
766 path = rhythmdb_property_model_get_path (GTK_TREE_MODEL (model), iter);
767 gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
768 gtk_tree_path_free (path);
769
770 g_sequence_sort_changed (ptr,
771 (GCompareDataFunc) rhythmdb_property_model_compare,
772 model);
773
774 path = rhythmdb_property_model_get_path (GTK_TREE_MODEL (model), iter);
775 gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, iter);
776 gtk_tree_path_free (path);
777 }
778
779 static RhythmDBPropertyModelEntry *
780 rhythmdb_property_model_insert (RhythmDBPropertyModel *model,
781 RhythmDBEntry *entry)
782 {
783 RhythmDBPropertyModelEntry *prop;
784 GtkTreeIter iter;
785 GtkTreePath *path;
786 GSequenceIter *ptr;
787 const char *propstr;
788
789 iter.stamp = model->priv->stamp;
790 propstr = rhythmdb_entry_get_string (entry, model->priv->propid);
791
792 g_atomic_int_inc (&model->priv->all->refcount);
793
794 if ((ptr = g_hash_table_lookup (model->priv->reverse_map, propstr))) {
795 iter.user_data = ptr;
796 prop = g_sequence_get (ptr);
797 g_atomic_int_inc (&prop->refcount);
798 rb_debug ("adding \"%s\": refcount %d", propstr, prop->refcount);
799
800 if (update_sort_string (model, prop, entry)) {
801 property_sort_changed (model, ptr, &iter);
802 }
803
804 path = rhythmdb_property_model_get_path (GTK_TREE_MODEL (model), &iter);
805 gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
806 gtk_tree_path_free (path);
807
808 return prop;
809 }
810 rb_debug ("adding new property \"%s\"", propstr);
811
812 prop = g_new0 (RhythmDBPropertyModelEntry, 1);
813 prop->string = rb_refstring_new (propstr);
814 update_sort_string (model, prop, entry);
815 g_atomic_int_set (&prop->refcount, 1);
816
817 ptr = g_sequence_insert_sorted (model->priv->properties, prop,
818 (GCompareDataFunc) rhythmdb_property_model_compare,
819 model);
820 g_hash_table_insert (model->priv->reverse_map,
821 (gpointer)rb_refstring_get (prop->string),
822 ptr);
823
824 iter.user_data = ptr;
825 path = rhythmdb_property_model_get_path (GTK_TREE_MODEL (model), &iter);
826 gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
827 gtk_tree_path_free (path);
828
829 return prop;
830 }
831
832 static void
833 rhythmdb_property_model_delete (RhythmDBPropertyModel *model,
834 RhythmDBEntry *entry)
835 {
836 const char *propstr;
837
838 if (g_hash_table_lookup (model->priv->entries, entry))
839 return;
840
841 propstr = rhythmdb_entry_get_string (entry, model->priv->propid);
842 rhythmdb_property_model_delete_prop (model, propstr);
843 }
844
845 static void
846 rhythmdb_property_model_delete_prop (RhythmDBPropertyModel *model,
847 const char *propstr)
848 {
849 GSequenceIter *ptr;
850 RhythmDBPropertyModelEntry *prop;
851 GtkTreePath *path;
852 GtkTreeIter iter;
853 gboolean ret;
854
855 g_assert ((ptr = g_hash_table_lookup (model->priv->reverse_map, propstr)));
856
857 iter.stamp = model->priv->stamp;
858 iter.user_data = ptr;
859
860 ret = g_atomic_int_dec_and_test (&model->priv->all->refcount);
861
862 prop = g_sequence_get (ptr);
863 rb_debug ("deleting \"%s\": refcount: %d", propstr, prop->refcount);
864 if (g_atomic_int_dec_and_test (&prop->refcount) == FALSE) {
865 g_assert (ret == FALSE);
866 path = rhythmdb_property_model_get_path (GTK_TREE_MODEL (model), &iter);
867 gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
868 gtk_tree_path_free (path);
869 return;
870 }
871
872 path = rhythmdb_property_model_get_path (GTK_TREE_MODEL (model), &iter);
873 g_signal_emit (G_OBJECT (model), rhythmdb_property_model_signals[PRE_ROW_DELETION], 0);
874 gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
875 gtk_tree_path_free (path);
876
877 g_sequence_remove (ptr);
878 g_hash_table_remove (model->priv->reverse_map, propstr);
879 prop->refcount = 0xdeadbeef;
880 rb_refstring_unref (prop->string);
881 rb_refstring_unref (prop->sort_string);
882
883 g_free (prop);
884 }
885
886 /**
887 * rhythmdb_property_model_iter_from_string:
888 * @model: the #RhythmDBPropertyModel
889 * @name: the property value to find
890 * @iter: a #GtkTreeIter to point to the row
891 *
892 * Locates the row in the model for a property value.
893 *
894 * Return value: TRUE if the value was found.
895 */
896 gboolean
897 rhythmdb_property_model_iter_from_string (RhythmDBPropertyModel *model,
898 const char *name,
899 GtkTreeIter *iter)
900 {
901 GSequenceIter *ptr;
902
903 if (name == NULL) {
904 if (iter) {
905 iter->stamp = model->priv->stamp;
906 iter->user_data = model->priv->all;
907 }
908 return TRUE;
909 }
910
911 ptr = g_hash_table_lookup (model->priv->reverse_map, name);
912 if (!ptr)
913 return FALSE;
914
915 if (iter) {
916 iter->stamp = model->priv->stamp;
917 iter->user_data = ptr;
918 }
919
920 return TRUE;
921 }
922
923 static GtkTreeModelFlags
924 rhythmdb_property_model_get_flags (GtkTreeModel *model)
925 {
926 return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY;
927 }
928
929 static gint
930 rhythmdb_property_model_get_n_columns (GtkTreeModel *tree_model)
931 {
932 return RHYTHMDB_PROPERTY_MODEL_COLUMN_LAST;
933 }
934
935 static GType
936 rhythmdb_property_model_get_column_type (GtkTreeModel *tree_model,
937 int index)
938 {
939 switch (index) {
940 case RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE:
941 return G_TYPE_STRING;
942 case RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY:
943 return G_TYPE_BOOLEAN;
944 case RHYTHMDB_PROPERTY_MODEL_COLUMN_NUMBER:
945 return G_TYPE_UINT;
946 default:
947 g_assert_not_reached ();
948 return G_TYPE_INVALID;
949 }
950 }
951
952 static gboolean
953 rhythmdb_property_model_get_iter (GtkTreeModel *tree_model,
954 GtkTreeIter *iter,
955 GtkTreePath *path)
956 {
957 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
958 guint index;
959 GSequenceIter *ptr;
960
961 index = gtk_tree_path_get_indices (path)[0];
962
963 if (index == 0) {
964 iter->stamp = model->priv->stamp;
965 iter->user_data = model->priv->all;
966 return TRUE;
967 }
968
969 index--;
970 if (index >= g_sequence_get_length (model->priv->properties))
971 return FALSE;
972
973 ptr = g_sequence_get_iter_at_pos (model->priv->properties, index);
974
975 iter->stamp = model->priv->stamp;
976 iter->user_data = ptr;
977
978 return TRUE;
979 }
980
981 static GtkTreePath *
982 rhythmdb_property_model_get_path (GtkTreeModel *tree_model,
983 GtkTreeIter *iter)
984 {
985 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
986 GtkTreePath *path;
987
988 g_return_val_if_fail (iter->stamp == model->priv->stamp, NULL);
989
990 if (iter->user_data == model->priv->all) {
991 return gtk_tree_path_new_first ();
992 }
993
994 if (g_sequence_iter_is_end (iter->user_data))
995 return NULL;
996
997 path = gtk_tree_path_new ();
998 if (iter->user_data == model->priv->all)
999 gtk_tree_path_append_index (path, 0);
1000 else
1001 gtk_tree_path_append_index (path, g_sequence_iter_get_position (iter->user_data) + 1);
1002 return path;
1003 }
1004
1005 static void
1006 rhythmdb_property_model_get_value (GtkTreeModel *tree_model,
1007 GtkTreeIter *iter,
1008 gint column,
1009 GValue *value)
1010 {
1011 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
1012
1013 g_return_if_fail (model->priv->stamp == iter->stamp);
1014
1015 if (iter->user_data == model->priv->all) {
1016 switch (column) {
1017 case RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE:
1018 g_value_init (value, G_TYPE_STRING);
1019 g_value_set_string (value, rb_refstring_get (model->priv->all->string));
1020 break;
1021 case RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY:
1022 g_value_init (value, G_TYPE_BOOLEAN);
1023 g_value_set_boolean (value, TRUE);
1024 break;
1025 case RHYTHMDB_PROPERTY_MODEL_COLUMN_NUMBER:
1026 g_value_init (value, G_TYPE_UINT);
1027 g_value_set_uint (value, g_atomic_int_get (&model->priv->all->refcount));
1028 break;
1029 default:
1030 g_assert_not_reached ();
1031 }
1032 } else {
1033 RhythmDBPropertyModelEntry *prop;
1034
1035 g_return_if_fail (!g_sequence_iter_is_end (iter->user_data));
1036
1037 prop = g_sequence_get (iter->user_data);
1038 switch (column) {
1039 case RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE:
1040 g_value_init (value, G_TYPE_STRING);
1041 g_value_set_string (value, rb_refstring_get (prop->string));
1042 break;
1043 case RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY:
1044 g_value_init (value, G_TYPE_BOOLEAN);
1045 g_value_set_boolean (value, prop == model->priv->all);
1046 break;
1047 case RHYTHMDB_PROPERTY_MODEL_COLUMN_NUMBER:
1048 g_value_init (value, G_TYPE_UINT);
1049 g_value_set_uint (value, g_atomic_int_get (&prop->refcount));
1050 break;
1051 default:
1052 g_assert_not_reached ();
1053 }
1054 }
1055 }
1056
1057 static gboolean
1058 rhythmdb_property_model_iter_next (GtkTreeModel *tree_model,
1059 GtkTreeIter *iter)
1060 {
1061 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
1062
1063 g_return_val_if_fail (iter->stamp == model->priv->stamp, FALSE);
1064
1065 if (iter->user_data == model->priv->all) {
1066 iter->user_data = g_sequence_get_begin_iter (model->priv->properties);
1067 } else {
1068 g_return_val_if_fail (!g_sequence_iter_is_end (iter->user_data), FALSE);
1069 iter->user_data = g_sequence_iter_next (iter->user_data);
1070 }
1071
1072 return !g_sequence_iter_is_end (iter->user_data);
1073 }
1074
1075 static gboolean
1076 rhythmdb_property_model_iter_children (GtkTreeModel *tree_model,
1077 GtkTreeIter *iter,
1078 GtkTreeIter *parent)
1079 {
1080 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
1081
1082 if (parent != NULL)
1083 return FALSE;
1084
1085 iter->stamp = model->priv->stamp;
1086 iter->user_data = model->priv->all;
1087
1088 return TRUE;
1089 }
1090
1091 static gboolean
1092 rhythmdb_property_model_iter_has_child (GtkTreeModel *tree_model,
1093 GtkTreeIter *iter)
1094 {
1095 return FALSE;
1096 }
1097
1098 static gint
1099 rhythmdb_property_model_iter_n_children (GtkTreeModel *tree_model,
1100 GtkTreeIter *iter)
1101 {
1102 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
1103
1104 if (iter)
1105 g_return_val_if_fail (model->priv->stamp == iter->stamp, -1);
1106
1107 if (iter == NULL)
1108 return 1 + g_sequence_get_length (model->priv->properties);
1109
1110 return 0;
1111 }
1112
1113 static gboolean
1114 rhythmdb_property_model_iter_nth_child (GtkTreeModel *tree_model,
1115 GtkTreeIter *iter,
1116 GtkTreeIter *parent,
1117 gint n)
1118 {
1119 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (tree_model);
1120 GSequenceIter *child;
1121
1122 if (parent)
1123 return FALSE;
1124
1125 if (n != 0) {
1126 /* -1 to account for the 'all' property at position 0 */
1127 child = g_sequence_get_iter_at_pos (model->priv->properties, n-1);
1128
1129 if (g_sequence_iter_is_end (child))
1130 return FALSE;
1131 iter->user_data = child;
1132 } else {
1133 iter->user_data = model->priv->all;
1134 }
1135
1136 iter->stamp = model->priv->stamp;
1137
1138 return TRUE;
1139 }
1140
1141 static gboolean
1142 rhythmdb_property_model_iter_parent (GtkTreeModel *tree_model,
1143 GtkTreeIter *iter,
1144 GtkTreeIter *child)
1145 {
1146 return FALSE;
1147 }
1148
1149 static gboolean
1150 rhythmdb_property_model_row_draggable (RbTreeDragSource *dragsource,
1151 GList *paths)
1152 {
1153 return TRUE;
1154 }
1155
1156 static gboolean
1157 rhythmdb_property_model_drag_data_delete (RbTreeDragSource *dragsource,
1158 GList *paths)
1159 {
1160 /* not supported */
1161 return TRUE;
1162 }
1163
1164 /*Going through hoops to avoid nested functions*/
1165 struct QueryModelCbStruct {
1166 RhythmDB *db;
1167 GString *reply;
1168 gint target;
1169 };
1170
1171 static gboolean
1172 query_model_cb (GtkTreeModel *query_model,
1173 GtkTreePath *path,
1174 GtkTreeIter *iter,
1175 struct QueryModelCbStruct *data)
1176 {
1177 RhythmDBEntry *entry;
1178
1179 gtk_tree_model_get (query_model, iter, 0, &entry, -1);
1180 if (data->target == TARGET_ENTRIES) {
1181 g_string_append_printf (data->reply,
1182 "%ld",
1183 rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_ENTRY_ID));
1184 } else if (data->target == TARGET_URIS) {
1185 const char *uri;
1186 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
1187 g_string_append (data->reply, uri);
1188 }
1189 g_string_append (data->reply, "\r\n");
1190
1191 rhythmdb_entry_unref (entry);
1192 return FALSE;
1193 }
1194
1195 static gboolean
1196 rhythmdb_property_model_drag_data_get (RbTreeDragSource *dragsource,
1197 GList *paths,
1198 GtkSelectionData *selection_data)
1199 {
1200 RhythmDBPropertyModel *model = RHYTHMDB_PROPERTY_MODEL (dragsource);
1201 guint target;
1202 GtkTargetList *drag_target_list;
1203 GdkAtom selection_data_target;
1204
1205 switch (model->priv->propid) {
1206 case RHYTHMDB_PROP_GENRE:
1207 drag_target_list = rhythmdb_property_model_genre_drag_target_list;
1208 break;
1209 case RHYTHMDB_PROP_ALBUM:
1210 drag_target_list = rhythmdb_property_model_album_drag_target_list;
1211 break;
1212 case RHYTHMDB_PROP_ARTIST:
1213 drag_target_list = rhythmdb_property_model_artist_drag_target_list;
1214 break;
1215 case RHYTHMDB_PROP_LOCATION:
1216 drag_target_list = rhythmdb_property_model_location_drag_target_list;
1217 break;
1218 default:
1219 g_assert_not_reached ();
1220 }
1221
1222 selection_data_target = gtk_selection_data_get_target (selection_data);
1223 if (!gtk_target_list_find (drag_target_list,
1224 selection_data_target,
1225 &target)) {
1226 return FALSE;
1227 }
1228
1229 if (target == TARGET_URIS || target == TARGET_ENTRIES) {
1230 RhythmDB *db = model->priv->db;
1231 RhythmDBQueryModel *query_model;
1232 GString* reply = g_string_new ("");
1233 GtkTreeIter iter;
1234 gboolean is_all = FALSE;
1235 struct QueryModelCbStruct tmp;
1236 GtkTreePath *path;
1237 GCompareDataFunc sort_func = NULL;
1238 gpointer sort_data;
1239 gboolean sort_reverse;
1240
1241 query_model = rhythmdb_query_model_new_empty (db);
1242 /* FIXME the sort order on the query model at this point is usually
1243 * not the user's selected sort order.
1244 */
1245 g_object_get (G_OBJECT (model->priv->query_model),
1246 "sort-func", &sort_func,
1247 "sort-data", &sort_data,
1248 "sort-reverse", &sort_reverse,
1249 NULL);
1250 rhythmdb_query_model_set_sort_order (RHYTHMDB_QUERY_MODEL (query_model),
1251 sort_func, GUINT_TO_POINTER (sort_data), NULL, sort_reverse);
1252
1253 rb_debug ("getting drag data as uri list");
1254 /* check if first selected row is 'All' */
1255 path = gtk_tree_row_reference_get_path (paths->data);
1256 if (path && gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path))
1257 gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
1258 RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY,
1259 &is_all, -1);
1260 gtk_tree_path_free (path);
1261 if (is_all) {
1262 g_object_set (query_model,
1263 "base-model", model->priv->query_model,
1264 NULL);
1265 } else {
1266 GList *row;
1267 GPtrArray *subquery = g_ptr_array_new ();
1268
1269 for (row = paths; row; row = row->next) {
1270 char* name;
1271 path = gtk_tree_row_reference_get_path (row->data);
1272 if (path && gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path)) {
1273 gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
1274 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE,
1275 &name, -1);
1276 if (row == paths) {
1277 rhythmdb_query_append (db, subquery,
1278 RHYTHMDB_QUERY_PROP_EQUALS,
1279 model->priv->propid, name,
1280 RHYTHMDB_QUERY_END);
1281 } else {
1282 rhythmdb_query_append (db, subquery,
1283 RHYTHMDB_QUERY_DISJUNCTION,
1284 RHYTHMDB_QUERY_PROP_EQUALS,
1285 model->priv->propid, name,
1286 RHYTHMDB_QUERY_END);
1287 }
1288 }
1289
1290 gtk_tree_path_free (path);
1291 g_free (name);
1292 }
1293
1294 g_object_set (query_model,
1295 "query", subquery,
1296 "base-model", model->priv->query_model,
1297 NULL);
1298 rhythmdb_query_free (subquery);
1299 }
1300
1301 tmp.db = db;
1302 tmp.reply = reply;
1303 tmp.target = target;
1304 /* Too bad that we're on the main thread. Why doesn't gtk call us async?
1305 * How does file-roller manage? - it seems it refuses the drop when it isn't
1306 * done unpacking. In which case, we should tweak the drop acknowledgement,
1307 * and prepare the query using do_full_query_async. The query would be
1308 * hooked to the drag context.
1309 */
1310 gtk_tree_model_foreach (GTK_TREE_MODEL (query_model),
1311 (GtkTreeModelForeachFunc) query_model_cb,
1312 &tmp);
1313
1314 g_object_unref (query_model);
1315
1316 gtk_selection_data_set (selection_data,
1317 selection_data_target,
1318 8, (guchar *)reply->str,
1319 reply->len);
1320 g_string_free (reply, TRUE);
1321
1322 } else {
1323 char* title;
1324 GList *p;
1325 GString* reply = g_string_new ("");
1326
1327 rb_debug ("getting drag data as list of property values");
1328
1329 for (p = paths; p; p = p->next) {
1330 GtkTreeIter iter;
1331 GtkTreePath *path;
1332
1333 path = gtk_tree_row_reference_get_path (p->data);
1334 if (path && gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path)) {
1335 gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
1336 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE, &title, -1);
1337 g_string_append (reply, title);
1338 if (p->next)
1339 g_string_append (reply, "\r\n");
1340 g_free (title);
1341 }
1342 gtk_tree_path_free (path);
1343 }
1344 gtk_selection_data_set (selection_data,
1345 selection_data_target,
1346 8, (guchar *)reply->str,
1347 reply->len);
1348 g_string_free (reply, TRUE);
1349 }
1350
1351 return TRUE;
1352 }
1353
1354 /**
1355 * rhythmdb_property_model_enable_drag:
1356 * @model: the #RhythmDBPropertyModel.
1357 * @view: the #GtkTreeView from which to enable drag and drop
1358 *
1359 * Enables drag and drop from a specified #GtkTreeView that is
1360 * backed by the #RhythmDBPropertyModel. Drag targets are
1361 * determined by the indexed property.
1362 */
1363 void
1364 rhythmdb_property_model_enable_drag (RhythmDBPropertyModel *model,
1365 GtkTreeView *view)
1366 {
1367 const GtkTargetEntry *targets;
1368 gint n_elements;
1369
1370 switch (model->priv->propid) {
1371 case RHYTHMDB_PROP_GENRE:
1372 targets = targets_genre;
1373 n_elements = G_N_ELEMENTS (targets_genre);
1374 break;
1375 case RHYTHMDB_PROP_ALBUM:
1376 targets = targets_album;
1377 n_elements = G_N_ELEMENTS (targets_album);
1378 break;
1379 case RHYTHMDB_PROP_ARTIST:
1380 targets = targets_artist;
1381 n_elements = G_N_ELEMENTS (targets_artist);
1382 break;
1383 case RHYTHMDB_PROP_LOCATION:
1384 case RHYTHMDB_PROP_SUBTITLE: /* more or less */
1385 targets = targets_location;
1386 n_elements = G_N_ELEMENTS (targets_location);
1387 break;
1388 default:
1389 g_assert_not_reached ();
1390 }
1391
1392 rb_tree_dnd_add_drag_source_support (view,
1393 GDK_BUTTON1_MASK,
1394 targets, n_elements,
1395 GDK_ACTION_COPY);
1396 }
1397
1398 static gboolean
1399 rhythmdb_property_model_perform_sync (RhythmDBPropertyModel *model)
1400 {
1401 GtkTreeIter iter;
1402 GtkTreePath *path;
1403
1404 GDK_THREADS_ENTER ();
1405
1406 iter.stamp = model->priv->stamp;
1407 iter.user_data = model->priv->all;
1408 path = rhythmdb_property_model_get_path (GTK_TREE_MODEL (model), &iter);
1409 gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
1410 gtk_tree_path_free (path);
1411
1412 model->priv->syncing_id = 0;
1413 GDK_THREADS_LEAVE ();
1414 return FALSE;
1415 }
1416
1417 static void
1418 rhythmdb_property_model_sync (RhythmDBPropertyModel *model)
1419 {
1420 if (model->priv->syncing_id != 0)
1421 return;
1422
1423 model->priv->syncing_id = g_idle_add ((GSourceFunc)rhythmdb_property_model_perform_sync, model);
1424 }
1425
1426 /* This should really be standard. */
1427 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
1428
1429 GType
1430 rhythmdb_property_model_column_get_type (void)
1431 {
1432 static GType etype = 0;
1433
1434 if (etype == 0) {
1435 static const GEnumValue values[] = {
1436 ENUM_ENTRY (RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE, "property-title"),
1437 ENUM_ENTRY (RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY, "value-priority"),
1438 ENUM_ENTRY (RHYTHMDB_PROPERTY_MODEL_COLUMN_NUMBER, "track-count"),
1439 { 0, 0, 0 }
1440 };
1441
1442 etype = g_enum_register_static ("RhythmDBPropertyModelColumn", values);
1443 }
1444
1445 return etype;
1446 }