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", &current, 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