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