No issues found
Tool | Failure ID | Location | Function | Message | Data |
---|---|---|---|---|---|
clang-analyzer | no-output-found | rb-display-page-model.c | Message(text='Unable to locate XML output from invoke-clang-analyzer') | None | |
clang-analyzer | no-output-found | rb-display-page-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@verbum.org>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (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 GNU
21 * General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public
24 * License along with this program; if not, write to the
25 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
26 * Boston, MA 02110-1301 USA.
27 *
28 */
29
30 #include "config.h"
31
32 #include <string.h>
33
34 #include <glib/gi18n.h>
35 #include <gdk/gdk.h>
36 #include <gtk/gtk.h>
37
38 #include "rb-display-page-group.h"
39 #include "rb-display-page-model.h"
40 #include "rb-tree-dnd.h"
41 #include "rb-debug.h"
42 #include "rb-marshal.h"
43 #include "rb-playlist-source.h"
44 #include "rb-auto-playlist-source.h"
45 #include "rb-static-playlist-source.h"
46
47 /**
48 * SECTION:rb-display-page-model
49 * @short_description: models backing the display page tree
50 *
51 * The #RBDisplayPageTree widget is backed by a #GtkTreeStore containing
52 * the sources and a set of attributes used to structure and display
53 * them, and a #GtkTreeModelFilter that hides sources with the
54 * visibility property set to FALSE. This class implements the filter
55 * model and also creates the actual model.
56 *
57 * The display page model supports drag and drop in a variety of formats.
58 * The simplest of these are text/uri-list and application/x-rhythmbox-entry,
59 * which convey URIs and IDs of existing database entries. When dragged
60 * to an existing source, these just add the URIs or entries to the target
61 * source. When dragged to an empty space in the tree widget, this results
62 * in the creation of a static playlist.
63 *
64 * text/x-rhythmbox-artist, text/x-rhythmbox-album, and text/x-rhythmbox-genre
65 * are used when dragging items from the library browser. When dragged to
66 * the display page tree, these result in the creation of a new auto playlist with
67 * the dragged items as criteria.
68 */
69
70 enum
71 {
72 DROP_RECEIVED,
73 PAGE_INSERTED,
74 LAST_SIGNAL
75 };
76
77 static guint rb_display_page_model_signals[LAST_SIGNAL] = { 0 };
78
79 enum {
80 TARGET_PROPERTY,
81 TARGET_SOURCE,
82 TARGET_URIS,
83 TARGET_ENTRIES,
84 TARGET_DELETE
85 };
86
87 static const GtkTargetEntry dnd_targets[] = {
88 { "text/x-rhythmbox-album", 0, TARGET_PROPERTY },
89 { "text/x-rhythmbox-artist", 0, TARGET_PROPERTY },
90 { "text/x-rhythmbox-genre", 0, TARGET_PROPERTY },
91 { "application/x-rhythmbox-source", 0, TARGET_SOURCE },
92 { "application/x-rhythmbox-entry", 0, TARGET_ENTRIES },
93 { "text/uri-list", 0, TARGET_URIS },
94 { "application/x-delete-me", 0, TARGET_DELETE }
95 };
96
97 static GtkTargetList *drag_target_list = NULL;
98
99 static void rb_display_page_model_drag_dest_init (RbTreeDragDestIface *iface);
100 static void rb_display_page_model_drag_source_init (RbTreeDragSourceIface *iface);
101
102 G_DEFINE_TYPE_EXTENDED (RBDisplayPageModel,
103 rb_display_page_model,
104 GTK_TYPE_TREE_MODEL_FILTER,
105 0,
106 G_IMPLEMENT_INTERFACE (RB_TYPE_TREE_DRAG_SOURCE,
107 rb_display_page_model_drag_source_init)
108 G_IMPLEMENT_INTERFACE (RB_TYPE_TREE_DRAG_DEST,
109 rb_display_page_model_drag_dest_init));
110
111 static gboolean
112 rb_display_page_model_drag_data_received (RbTreeDragDest *drag_dest,
113 GtkTreePath *dest,
114 GtkTreeViewDropPosition pos,
115 GtkSelectionData *selection_data)
116 {
117 RBDisplayPageModel *model;
118 GdkAtom type;
119
120 g_return_val_if_fail (RB_IS_DISPLAY_PAGE_MODEL (drag_dest), FALSE);
121 model = RB_DISPLAY_PAGE_MODEL (drag_dest);
122 type = gtk_selection_data_get_data_type (selection_data);
123
124 if (type == gdk_atom_intern ("text/uri-list", TRUE) ||
125 type == gdk_atom_intern ("application/x-rhythmbox-entry", TRUE)) {
126 GtkTreeIter iter;
127 RBDisplayPage *target = NULL;
128
129 rb_debug ("text/uri-list or application/x-rhythmbox-entry drag data received");
130
131 if (dest != NULL && gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, dest)) {
132 gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
133 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &target, -1);
134 }
135
136 g_signal_emit (G_OBJECT (model), rb_display_page_model_signals[DROP_RECEIVED],
137 0, target, pos, selection_data);
138
139 if (target != NULL) {
140 g_object_unref (target);
141 }
142
143 return TRUE;
144 }
145
146 /* if artist, album or genre, only allow new playlists */
147 if (type == gdk_atom_intern ("text/x-rhythmbox-album", TRUE) ||
148 type == gdk_atom_intern ("text/x-rhythmbox-artist", TRUE) ||
149 type == gdk_atom_intern ("text/x-rhythmbox-genre", TRUE)) {
150 rb_debug ("text/x-rhythmbox-(album|artist|genre) drag data received");
151 g_signal_emit (G_OBJECT (model), rb_display_page_model_signals[DROP_RECEIVED],
152 0, NULL, pos, selection_data);
153 return TRUE;
154 }
155
156 if (type == gdk_atom_intern ("application/x-rhythmbox-source", TRUE)) {
157 /* don't support dnd of sources */
158 return FALSE;
159 }
160
161 return FALSE;
162 }
163
164 static gboolean
165 rb_display_page_model_row_drop_possible (RbTreeDragDest *drag_dest,
166 GtkTreePath *dest,
167 GtkTreeViewDropPosition pos,
168 GtkSelectionData *selection_data)
169 {
170 RBDisplayPageModel *model;
171
172 rb_debug ("row drop possible");
173 g_return_val_if_fail (RB_IS_DISPLAY_PAGE_MODEL (drag_dest), FALSE);
174
175 model = RB_DISPLAY_PAGE_MODEL (drag_dest);
176
177 if (!dest)
178 return TRUE;
179
180 /* Call the superclass method */
181 return gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (GTK_TREE_STORE (model)),
182 dest, selection_data);
183 }
184
185 static gboolean
186 path_is_droppable (RBDisplayPageModel *model,
187 GtkTreePath *dest)
188 {
189 GtkTreeIter iter;
190 gboolean res;
191
192 res = FALSE;
193
194 if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, dest)) {
195 RBDisplayPage *page;
196
197 gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
198 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page, -1);
199
200 if (page != NULL) {
201 if (RB_IS_SOURCE (page)) {
202 res = rb_source_can_paste (RB_SOURCE (page));
203 }
204 g_object_unref (page);
205 }
206 }
207
208 return res;
209 }
210
211 static gboolean
212 rb_display_page_model_row_drop_position (RbTreeDragDest *drag_dest,
213 GtkTreePath *dest_path,
214 GList *targets,
215 GtkTreeViewDropPosition *pos)
216 {
217 GtkTreeModel *model = GTK_TREE_MODEL (drag_dest);
218
219 if (g_list_find (targets, gdk_atom_intern ("application/x-rhythmbox-source", TRUE)) && dest_path) {
220 rb_debug ("application/x-rhythmbox-source type");
221 return FALSE;
222 }
223
224 if (g_list_find (targets, gdk_atom_intern ("text/uri-list", TRUE)) ||
225 g_list_find (targets, gdk_atom_intern ("application/x-rhythmbox-entry", TRUE))) {
226 rb_debug ("text/uri-list or application/x-rhythmbox-entry type");
227 if (dest_path && !path_is_droppable (RB_DISPLAY_PAGE_MODEL (model), dest_path))
228 return FALSE;
229
230 *pos = GTK_TREE_VIEW_DROP_INTO_OR_BEFORE;
231 return TRUE;
232 }
233
234 if ((g_list_find (targets, gdk_atom_intern ("text/x-rhythmbox-artist", TRUE))
235 || g_list_find (targets, gdk_atom_intern ("text/x-rhythmbox-album", TRUE))
236 || g_list_find (targets, gdk_atom_intern ("text/x-rhythmbox-genre", TRUE)))
237 && !g_list_find (targets, gdk_atom_intern ("application/x-rhythmbox-source", TRUE))) {
238 rb_debug ("genre, album, or artist type");
239 *pos = GTK_TREE_VIEW_DROP_AFTER;
240 return TRUE;
241 }
242
243 return FALSE;
244 }
245
246 static GdkAtom
247 rb_display_page_model_get_drag_target (RbTreeDragDest *drag_dest,
248 GtkWidget *widget,
249 GdkDragContext *context,
250 GtkTreePath *path,
251 GtkTargetList *target_list)
252 {
253 if (g_list_find (gdk_drag_context_list_targets (context),
254 gdk_atom_intern ("application/x-rhythmbox-source", TRUE))) {
255 /* always accept rb source path if offered */
256 return gdk_atom_intern ("application/x-rhythmbox-source", TRUE);
257 }
258
259 if (path) {
260 /* only accept text/uri-list or application/x-rhythmbox-entry drops into existing sources */
261 GdkAtom entry_atom;
262
263 entry_atom = gdk_atom_intern ("application/x-rhythmbox-entry", FALSE);
264 if (g_list_find (gdk_drag_context_list_targets (context), entry_atom))
265 return entry_atom;
266
267 return gdk_atom_intern ("text/uri-list", FALSE);
268 }
269
270 return gtk_drag_dest_find_target (widget, context, target_list);
271 }
272
273 static gboolean
274 rb_display_page_model_row_draggable (RbTreeDragSource *drag_source, GList *path_list)
275 {
276 return FALSE;
277 }
278
279 static gboolean
280 rb_display_page_model_drag_data_get (RbTreeDragSource *drag_source,
281 GList *path_list,
282 GtkSelectionData *selection_data)
283 {
284 char *path_str;
285 GtkTreePath *path;
286 GdkAtom selection_data_target;
287 guint target;
288
289 selection_data_target = gtk_selection_data_get_target (selection_data);
290 path = gtk_tree_row_reference_get_path (path_list->data);
291 if (path == NULL)
292 return FALSE;
293
294 if (!gtk_target_list_find (drag_target_list,
295 selection_data_target,
296 &target)) {
297 return FALSE;
298 }
299
300 switch (target) {
301 case TARGET_SOURCE:
302 rb_debug ("getting drag data as rb display page path");
303 path_str = gtk_tree_path_to_string (path);
304 gtk_selection_data_set (selection_data,
305 selection_data_target,
306 8, (guchar *) path_str,
307 strlen (path_str));
308 g_free (path_str);
309 gtk_tree_path_free (path);
310 return TRUE;
311 case TARGET_URIS:
312 case TARGET_ENTRIES:
313 {
314 RBDisplayPage *page;
315 RhythmDBQueryModel *query_model;
316 GtkTreeIter iter;
317 GString *data;
318 gboolean first = TRUE;
319
320 rb_debug ("getting drag data as uri list");
321 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source), &iter, path))
322 return FALSE;
323
324 data = g_string_new ("");
325 gtk_tree_model_get (GTK_TREE_MODEL (drag_source),
326 &iter,
327 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
328 -1);
329 if (RB_IS_SOURCE (page) == FALSE) {
330 g_object_unref (page);
331 return FALSE;
332 }
333 g_object_get (page, "query-model", &query_model, NULL);
334 g_object_unref (page);
335
336 if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (query_model), &iter)) {
337 g_object_unref (query_model);
338 return FALSE;
339 }
340
341 do {
342 RhythmDBEntry *entry;
343
344 if (first) {
345 g_string_append(data, "\r\n");
346 first = FALSE;
347 }
348
349 entry = rhythmdb_query_model_iter_to_entry (query_model, &iter);
350 if (target == TARGET_URIS) {
351 g_string_append (data, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
352 } else {
353 g_string_append_printf (data,
354 "%lu",
355 rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_ENTRY_ID));
356 }
357
358 rhythmdb_entry_unref (entry);
359
360 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (query_model), &iter));
361
362 g_object_unref (query_model);
363
364 gtk_selection_data_set (selection_data,
365 selection_data_target,
366 8, (guchar *) data->str,
367 data->len);
368
369 g_string_free (data, TRUE);
370 return TRUE;
371 }
372 default:
373 /* unsupported target */
374 return FALSE;
375 }
376 }
377
378 static gboolean
379 rb_display_page_model_drag_data_delete (RbTreeDragSource *drag_source,
380 GList *paths)
381 {
382 return TRUE;
383 }
384
385 typedef struct _DisplayPageIter {
386 RBDisplayPage *page;
387 GtkTreeIter iter;
388 gboolean found;
389 } DisplayPageIter;
390
391 static gboolean
392 match_page_to_iter (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, DisplayPageIter *dpi)
393 {
394 RBDisplayPage *target = NULL;
395
396 gtk_tree_model_get (model, iter, RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &target, -1);
397
398 if (target == dpi->page) {
399 dpi->iter = *iter;
400 dpi->found = TRUE;
401 }
402
403 if (target != NULL) {
404 g_object_unref (target);
405 }
406
407 return dpi->found;
408 }
409
410 static gboolean
411 find_in_real_model (RBDisplayPageModel *page_model, RBDisplayPage *page, GtkTreeIter *iter)
412 {
413 GtkTreeModel *model;
414 DisplayPageIter dpi = {0, };
415 dpi.page = page;
416
417 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (page_model));
418 gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc) match_page_to_iter, &dpi);
419 if (dpi.found) {
420 *iter = dpi.iter;
421 return TRUE;
422 } else {
423 return FALSE;
424 }
425 }
426
427 static void
428 walk_up_to_page_group (GtkTreeModel *model, GtkTreeIter *page_group, GtkTreeIter *page)
429 {
430 GtkTreeIter walk_iter;
431 GtkTreeIter group_iter;
432
433 walk_iter = *page;
434 do {
435 group_iter = walk_iter;
436 } while (gtk_tree_model_iter_parent (model, &walk_iter, &group_iter));
437 *page_group = group_iter;
438 }
439
440 static int
441 compare_rows (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, RBDisplayPageModel *page_model)
442 {
443 RBDisplayPage *a_page;
444 RBDisplayPage *b_page;
445 char *a_name;
446 char *b_name;
447 int ret;
448
449 gtk_tree_model_get (model, a, RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &a_page, -1);
450 gtk_tree_model_get (model, b, RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &b_page, -1);
451
452 g_object_get (a_page, "name", &a_name, NULL);
453 g_object_get (b_page, "name", &b_name, NULL);
454
455 if (RB_IS_DISPLAY_PAGE_GROUP (a_page) && RB_IS_DISPLAY_PAGE_GROUP (b_page)) {
456 RBDisplayPageGroupCategory a_cat;
457 RBDisplayPageGroupCategory b_cat;
458 g_object_get (a_page, "category", &a_cat, NULL);
459 g_object_get (b_page, "category", &b_cat, NULL);
460 if (a_cat < b_cat) {
461 ret = -1;
462 } else if (a_cat > b_cat) {
463 ret = 1;
464 } else {
465 ret = g_utf8_collate (a_name, b_name);
466 }
467 } else {
468 /* walk up the tree until we find the group, then get its category
469 * to figure out how to sort the pages
470 */
471 GtkTreeIter group_iter;
472 RBDisplayPage *group_page;
473 RBDisplayPageGroupCategory category;
474 walk_up_to_page_group (model, &group_iter, a);
475 gtk_tree_model_get (model, &group_iter, RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &group_page, -1);
476 g_object_get (group_page, "category", &category, NULL);
477 g_object_unref (group_page);
478
479 /* sort mostly by name */
480 switch (category) {
481 case RB_DISPLAY_PAGE_GROUP_CATEGORY_FIXED:
482 /* fixed sources go in order of appearance */
483 ret = -1;
484 break;
485 case RB_DISPLAY_PAGE_GROUP_CATEGORY_PERSISTENT:
486 /* sort auto and static playlists separately */
487 if (RB_IS_AUTO_PLAYLIST_SOURCE (a_page)
488 && RB_IS_AUTO_PLAYLIST_SOURCE (b_page)) {
489 ret = g_utf8_collate (a_name, b_name);
490 } else if (RB_IS_STATIC_PLAYLIST_SOURCE (a_page)
491 && RB_IS_STATIC_PLAYLIST_SOURCE (b_page)) {
492 ret = g_utf8_collate (a_name, b_name);
493 } else if (RB_IS_AUTO_PLAYLIST_SOURCE (a_page)) {
494 ret = -1;
495 } else {
496 ret = 1;
497 }
498
499 break;
500 case RB_DISPLAY_PAGE_GROUP_CATEGORY_REMOVABLE:
501 case RB_DISPLAY_PAGE_GROUP_CATEGORY_TRANSIENT:
502 ret = g_utf8_collate (a_name, b_name);
503 break;
504 default:
505 g_assert_not_reached ();
506 break;
507 }
508 }
509
510 g_object_unref (a_page);
511 g_object_unref (b_page);
512 g_free (a_name);
513 g_free (b_name);
514
515 return ret;
516 }
517
518 static gboolean
519 rb_display_page_model_is_row_visible (GtkTreeModel *model,
520 GtkTreeIter *iter,
521 RBDisplayPageModel *page_model)
522 {
523 RBDisplayPage *page = NULL;
524 gboolean visibility = FALSE;
525
526 gtk_tree_model_get (model, iter,
527 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
528 -1);
529 if (page != NULL) {
530 g_object_get (page, "visibility", &visibility, NULL);
531 g_object_unref (page);
532 }
533
534 return visibility;
535 }
536
537 static void
538 update_group_visibility (GtkTreeModel *model, GtkTreeIter *iter, RBDisplayPageModel *page_model)
539 {
540 RBDisplayPage *page;
541
542 gtk_tree_model_get (model, iter,
543 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
544 -1);
545 if (RB_IS_DISPLAY_PAGE_GROUP (page)) {
546 gboolean has_children = FALSE;
547 gboolean current;
548 GtkTreeIter children;
549
550 if (gtk_tree_model_iter_children (model, &children, iter)) {
551 do {
552 has_children |= rb_display_page_model_is_row_visible (model, &children, page_model);
553 } while (gtk_tree_model_iter_next (model, &children));
554 }
555
556 g_object_get (page, "visibility", ¤t, NULL);
557
558 if (current != has_children) {
559 char *name;
560 g_object_get (page, "name", &name, NULL);
561 rb_debug ("page group %s changing visibility from %d to %d", name, current, has_children);
562 g_free (name);
563
564 g_object_set (page, "visibility", has_children, NULL);
565 }
566 }
567 g_object_unref (page);
568 }
569
570
571 /**
572 * rb_display_page_model_set_dnd_targets:
573 * @page_model: the #RBDisplayPageModel
574 * @treeview: the sourcel ist #GtkTreeView
575 *
576 * Sets up the drag and drop targets for the display page tree.
577 */
578 void
579 rb_display_page_model_set_dnd_targets (RBDisplayPageModel *display_page_model,
580 GtkTreeView *treeview)
581 {
582 int n_targets = G_N_ELEMENTS (dnd_targets);
583
584 rb_tree_dnd_add_drag_dest_support (treeview,
585 (RB_TREE_DEST_EMPTY_VIEW_DROP | RB_TREE_DEST_SELECT_ON_DRAG_TIMEOUT),
586 dnd_targets, n_targets,
587 GDK_ACTION_LINK);
588
589 rb_tree_dnd_add_drag_source_support (treeview,
590 GDK_BUTTON1_MASK,
591 dnd_targets, n_targets,
592 GDK_ACTION_COPY);
593 }
594
595
596 static void
597 page_notify_cb (GObject *object,
598 GParamSpec *pspec,
599 RBDisplayPageModel *page_model)
600 {
601 RBDisplayPage *page = RB_DISPLAY_PAGE (object);
602 GtkTreeIter iter;
603 GtkTreeModel *model;
604 GtkTreePath *path;
605
606 if (find_in_real_model (page_model, page, &iter) == FALSE) {
607 return;
608 }
609
610 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (page_model));
611 path = gtk_tree_model_get_path (model, &iter);
612 gtk_tree_model_row_changed (model, path, &iter);
613 gtk_tree_path_free (path);
614
615 /* update the page group's visibility */
616 if (g_strcmp0 (pspec->name, "visibility") == 0 && RB_IS_DISPLAY_PAGE_GROUP (page) == FALSE) {
617 GtkTreeIter group_iter;
618 walk_up_to_page_group (model, &group_iter, &iter);
619 update_group_visibility (model, &group_iter, page_model);
620 }
621 }
622
623
624 /**
625 * rb_display_page_model_add_page:
626 * @page_model: the #RBDisplayPageModel
627 * @page: the #RBDisplayPage to add
628 * @parent: the parent under which to add @page
629 *
630 * Adds a page to the model, either below a specified page (if it's a source or
631 * something else) or at the top level (if it's a group)
632 */
633 void
634 rb_display_page_model_add_page (RBDisplayPageModel *page_model, RBDisplayPage *page, RBDisplayPage *parent)
635 {
636 GtkTreeModel *model;
637 GtkTreeIter parent_iter;
638 GtkTreeIter group_iter;
639 GtkTreeIter *parent_iter_ptr;
640 GtkTreeIter iter;
641 char *name;
642 GList *child, *children;
643
644 g_return_if_fail (RB_IS_DISPLAY_PAGE_MODEL (page_model));
645 g_return_if_fail (RB_IS_DISPLAY_PAGE (page));
646
647 g_object_get (page, "name", &name, NULL);
648
649 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (page_model));
650 if (parent != NULL) {
651 if (find_in_real_model (page_model, parent, &parent_iter) == FALSE) {
652 /* it's okay for a page to create and add its own children
653 * in its constructor, but we need to defer adding the children
654 * until the parent is added.
655 */
656 rb_debug ("parent %p for source %s isn't in the model yet", parent, name);
657 _rb_display_page_add_pending_child (parent, page);
658 g_free (name);
659 return;
660 }
661 rb_debug ("inserting source %s with parent %p", name, parent);
662 parent_iter_ptr = &parent_iter;
663 } else {
664 rb_debug ("appending page %s with no parent", name);
665 g_object_set (page, "visibility", FALSE, NULL); /* hide until it has some content */
666 parent_iter_ptr = NULL;
667 }
668 g_free (name);
669
670 gtk_tree_store_insert_with_values (GTK_TREE_STORE (model), &iter, parent_iter_ptr, G_MAXINT,
671 RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING, FALSE,
672 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, page,
673 -1);
674 g_signal_emit (G_OBJECT (page_model), rb_display_page_model_signals[PAGE_INSERTED], 0, page, &iter);
675
676 g_signal_connect_object (page, "notify::name", G_CALLBACK (page_notify_cb), page_model, 0);
677 g_signal_connect_object (page, "notify::visibility", G_CALLBACK (page_notify_cb), page_model, 0);
678 g_signal_connect_object (page, "notify::pixbuf", G_CALLBACK (page_notify_cb), page_model, 0);
679
680 walk_up_to_page_group (model, &group_iter, &iter);
681 update_group_visibility (model, &group_iter, page_model);
682
683 children = _rb_display_page_get_pending_children (page);
684 for (child = children; child != NULL; child = child->next) {
685 rb_display_page_model_add_page (page_model, RB_DISPLAY_PAGE (child->data), page);
686 }
687 g_list_free (children);
688 }
689
690 /**
691 * rb_display_page_model_remove_page:
692 * @page_model: the #RBDisplayPageModel
693 * @page: the #RBDisplayPage to remove
694 *
695 * Removes a page from the model.
696 */
697 void
698 rb_display_page_model_remove_page (RBDisplayPageModel *page_model,
699 RBDisplayPage *page)
700 {
701 GtkTreeIter iter;
702 GtkTreeIter group_iter;
703 GtkTreeModel *model;
704
705 g_assert (find_in_real_model (page_model, page, &iter));
706
707 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (page_model));
708
709 walk_up_to_page_group (model, &group_iter, &iter);
710 gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
711 g_signal_handlers_disconnect_by_func (page, G_CALLBACK (page_notify_cb), page_model);
712
713 update_group_visibility (model, &group_iter, page_model);
714 }
715
716
717
718 /**
719 * rb_display_page_model_find_page:
720 * @page_model: the #RBDisplayPageModel
721 * @page: the #RBDisplayPage to find
722 * @iter: returns a #GtkTreeIter for the page
723 *
724 * Finds a #GtkTreeIter for a specified page in the model. This will only
725 * find pages that are currently visible. The returned #GtkTreeIter can be used
726 * with the #RBDisplayPageModel.
727 *
728 * Return value: %TRUE if the page was found
729 */
730 gboolean
731 rb_display_page_model_find_page (RBDisplayPageModel *page_model, RBDisplayPage *page, GtkTreeIter *iter)
732 {
733 DisplayPageIter dpi = {0, };
734 dpi.page = page;
735
736 gtk_tree_model_foreach (GTK_TREE_MODEL (page_model), (GtkTreeModelForeachFunc) match_page_to_iter, &dpi);
737 if (dpi.found) {
738 *iter = dpi.iter;
739 return TRUE;
740 } else {
741 return FALSE;
742 }
743 }
744
745 static gboolean
746 set_playing_flag (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, RBDisplayPage *source)
747 {
748 RBDisplayPage *page;
749 gboolean old_playing;
750
751 gtk_tree_model_get (model,
752 iter,
753 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
754 RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING, &old_playing,
755 -1);
756 if (RB_IS_SOURCE (page)) {
757 gboolean new_playing = (page == source);
758 if (old_playing || new_playing) {
759 gtk_tree_store_set (GTK_TREE_STORE (model),
760 iter,
761 RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING, new_playing,
762 -1);
763 }
764 }
765 g_object_unref (page);
766
767 return FALSE;
768 }
769
770 /**
771 * rb_display_page_model_set_playing_source:
772 * @page_model: the #RBDisplayPageModel
773 * @source: the new playing #RBSource (as a #RBDisplayPage)
774 *
775 * Updates the model with the new playing source.
776 */
777 void
778 rb_display_page_model_set_playing_source (RBDisplayPageModel *page_model, RBDisplayPage *source)
779 {
780 GtkTreeModel *model;
781 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (page_model));
782 gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc) set_playing_flag, source);
783 }
784
785 /**
786 * rb_display_page_model_new:
787 *
788 * This constructs both the GtkTreeStore holding the display page
789 * data and the filter model that hides invisible pages.
790 *
791 * Return value: the #RBDisplayPageModel
792 */
793 RBDisplayPageModel *
794 rb_display_page_model_new (void)
795 {
796 RBDisplayPageModel *model;
797 GtkTreeStore *store;
798 GType *column_types = g_new (GType, RB_DISPLAY_PAGE_MODEL_N_COLUMNS);
799
800 column_types[RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING] = G_TYPE_BOOLEAN;
801 column_types[RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE] = RB_TYPE_DISPLAY_PAGE;
802 store = gtk_tree_store_newv (RB_DISPLAY_PAGE_MODEL_N_COLUMNS,
803 column_types);
804 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
805 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE,
806 (GtkTreeIterCompareFunc) compare_rows,
807 NULL, NULL);
808 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
809 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE,
810 GTK_SORT_ASCENDING);
811
812 model = RB_DISPLAY_PAGE_MODEL (g_object_new (RB_TYPE_DISPLAY_PAGE_MODEL,
813 "child-model", store,
814 "virtual-root", NULL,
815 NULL));
816 g_object_unref (store);
817
818 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (model),
819 (GtkTreeModelFilterVisibleFunc) rb_display_page_model_is_row_visible,
820 model, NULL);
821
822 g_free (column_types);
823
824 return model;
825 }
826
827
828 static void
829 rb_display_page_model_init (RBDisplayPageModel *model)
830 {
831 }
832
833 static void
834 rb_display_page_model_drag_dest_init (RbTreeDragDestIface *iface)
835 {
836 iface->rb_drag_data_received = rb_display_page_model_drag_data_received;
837 iface->rb_row_drop_possible = rb_display_page_model_row_drop_possible;
838 iface->rb_row_drop_position = rb_display_page_model_row_drop_position;
839 iface->rb_get_drag_target = rb_display_page_model_get_drag_target;
840 }
841
842 static void
843 rb_display_page_model_drag_source_init (RbTreeDragSourceIface *iface)
844 {
845 iface->rb_row_draggable = rb_display_page_model_row_draggable;
846 iface->rb_drag_data_get = rb_display_page_model_drag_data_get;
847 iface->rb_drag_data_delete = rb_display_page_model_drag_data_delete;
848 }
849
850 static void
851 rb_display_page_model_class_init (RBDisplayPageModelClass *klass)
852 {
853 GObjectClass *object_class;
854
855 object_class = G_OBJECT_CLASS (klass);
856
857 /**
858 * RBDisplayPageModel::drop-received:
859 * @model: the #RBDisplayPageModel
860 * @target: the #RBSource receiving the drop
861 * @pos: the drop position
862 * @data: the drop data
863 *
864 * Emitted when a drag and drop operation to the display page tree completes.
865 */
866 rb_display_page_model_signals[DROP_RECEIVED] =
867 g_signal_new ("drop-received",
868 G_OBJECT_CLASS_TYPE (object_class),
869 G_SIGNAL_RUN_LAST,
870 G_STRUCT_OFFSET (RBDisplayPageModelClass, drop_received),
871 NULL, NULL,
872 rb_marshal_VOID__OBJECT_INT_POINTER,
873 G_TYPE_NONE,
874 3,
875 RB_TYPE_DISPLAY_PAGE, G_TYPE_INT, G_TYPE_POINTER);
876
877 /**
878 * RBDisplayPageModel::page-inserted:
879 * @model: the #RBDisplayPageModel
880 * @page: the #RBDisplayPage that was inserted
881 * @iter: a #GtkTreeIter indicating the page position
882 *
883 * Emitted when a new page is inserted into the model.
884 * Use this instead of GtkTreeModel::row-inserted as this
885 * doesn't get complicated by visibility filtering.
886 */
887 rb_display_page_model_signals[PAGE_INSERTED] =
888 g_signal_new ("page-inserted",
889 G_OBJECT_CLASS_TYPE (object_class),
890 G_SIGNAL_RUN_LAST,
891 G_STRUCT_OFFSET (RBDisplayPageModelClass, page_inserted),
892 NULL, NULL,
893 NULL,
894 G_TYPE_NONE,
895 2,
896 RB_TYPE_DISPLAY_PAGE, GTK_TYPE_TREE_ITER);
897
898 if (!drag_target_list) {
899 drag_target_list = gtk_target_list_new (dnd_targets, G_N_ELEMENTS (dnd_targets));
900 }
901 }
902
903 /**
904 * RBDisplayPageModelColumn:
905 * @RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING: TRUE if the page is the playing source
906 * @RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE: the #RBDisplayPage object
907 * @RB_DISPLAY_PAGE_MODEL_N_COLUMNS: the number of columns
908 *
909 * Columns present in the display page model.
910 */
911
912 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
913
914 GType
915 rb_display_page_model_column_get_type (void)
916 {
917 static GType etype = 0;
918
919 if (etype == 0) {
920 static const GEnumValue values[] = {
921 ENUM_ENTRY (RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING, "playing"),
922 ENUM_ENTRY (RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, "page"),
923 { 0, 0, 0 }
924 };
925
926 etype = g_enum_register_static ("RBDisplayPageModelColumn", values);
927 }
928
929 return etype;
930 }