1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2003,2004 Colin Walters <walters@verbum.org>
4  * Copyright (C) 2010 Jonathan Matthew <jonathan@d14n.org>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * The Rhythmbox authors hereby grant permission for non-GPL compatible
12  * GStreamer plugins to be used and distributed together with GStreamer
13  * and Rhythmbox. This permission is above and beyond the permissions granted
14  * by the GPL license by which Rhythmbox is covered. If you modify this code
15  * you may extend this exception to your version of the code, but you are not
16  * obligated to do so. If you do not wish to do so, delete this exception
17  * statement from your version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22  * General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public
25  * License along with this program; if not, write to the
26  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
27  * Boston, MA 02110-1301  USA.
28  *
29  */
30 
31 #include "config.h"
32 
33 #include <unistd.h>
34 #include <string.h>
35 
36 #include <glib/gi18n.h>
37 #include <gdk/gdk.h>
38 #include <gdk/gdkkeysyms.h>
39 #include <gtk/gtk.h>
40 
41 #include "rb-display-page-group.h"
42 #include "rb-display-page-tree.h"
43 #include "rb-display-page-model.h"
44 #include "rb-debug.h"
45 #include "rb-cell-renderer-pixbuf.h"
46 #include "gossip-cell-renderer-expander.h"
47 #include "rb-tree-dnd.h"
48 #include "rb-util.h"
49 #include "rb-auto-playlist-source.h"
50 #include "rb-static-playlist-source.h"
51 #include "rb-play-queue-source.h"
52 #include "rb-device-source.h"
53 #include "rb-builder-helpers.h"
54 #include "rb-application.h"
55 
56 /**
57  * SECTION:rb-display-page-tree
58  * @short_description: widget displaying the tree of #RBDisplayPage instances
59  *
60  * The display page tree widget is a GtkTreeView backed by a GtkListStore
61  * containing the display page instances (sources and other things).  Display
62  * pages include sources, such as the library and playlists, and other things
63  * like the visualization display.
64  *
65  * Display pages are shown in the list with an icon and the name.  The current
66  * playing source is displayed in bold.
67  *
68  * Sources are divided into groups - library, stores, playlists, devices,
69  * network shares.  Groups are displayed as headers, with expanders for hiding
70  * and showing the sources in the group.  Sources themselves may also have
71  * child sources, such as playlists on portable audio players.
72  */
73 
74 struct _RBDisplayPageTreePrivate
75 {
76 	GtkWidget *scrolled;
77 	GtkWidget *treeview;
78 	GtkCellRenderer *title_renderer;
79 	GtkCellRenderer *expander_renderer;
80 
81 	GtkWidget *toolbar;
82 	GtkWidget *add_menubutton;
83 
84 	RBDisplayPageModel *page_model;
85 	GtkTreeSelection *selection;
86 
87 	int child_source_count;
88 	GtkTreeViewColumn *main_column;
89 
90 	RBShell *shell;
91 
92 	GList *expand_rows;
93 	GtkTreeRowReference *expand_select_row;
94 	guint expand_rows_id;
95 
96 	GSimpleAction *remove_action;
97 	GSimpleAction *eject_action;
98 
99 	GdkPixbuf *blank_pixbuf;
100 };
101 
102 
103 enum
104 {
105 	PROP_0,
106 	PROP_SHELL,
107 	PROP_MODEL
108 };
109 
110 enum
111 {
112 	SELECTED,
113 	DROP_RECEIVED,
114 	LAST_SIGNAL
115 };
116 
117 static guint signals[LAST_SIGNAL] = { 0 };
118 
G_DEFINE_TYPE(RBDisplayPageTree,rb_display_page_tree,GTK_TYPE_GRID)119 G_DEFINE_TYPE (RBDisplayPageTree, rb_display_page_tree, GTK_TYPE_GRID)
120 
121 static RBDisplayPage *
122 get_selected_page (RBDisplayPageTree *display_page_tree)
123 {
124 	GtkTreeIter iter;
125 	GtkTreeModel *model;
126 	RBDisplayPage *page;
127 
128 	if (!gtk_tree_selection_get_selected (display_page_tree->priv->selection, &model, &iter))
129 		return NULL;
130 
131 	gtk_tree_model_get (model,
132 			    &iter,
133 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
134 			    -1);
135 	return page;
136 }
137 
138 static void
heading_cell_data_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,RBDisplayPageTree * display_page_tree)139 heading_cell_data_func (GtkTreeViewColumn *tree_column,
140 			GtkCellRenderer *cell,
141 			GtkTreeModel *model,
142 			GtkTreeIter *iter,
143 			RBDisplayPageTree *display_page_tree)
144 {
145 	RBDisplayPage *page;
146 
147 	gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model), iter,
148 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
149 			    -1);
150 
151 
152 	if (RB_IS_DISPLAY_PAGE_GROUP (page)) {
153 		char *name;
154 		g_object_get (page, "name", &name, NULL);
155 		g_object_set (cell,
156 			      "text", name,
157 			      "visible", TRUE,
158 			      NULL);
159 		g_free (name);
160 	} else {
161 		g_object_set (cell,
162 			      "visible", FALSE,
163 			      NULL);
164 	}
165 
166 	g_object_unref (page);
167 }
168 
169 static void
padding_cell_data_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,RBDisplayPageTree * display_page_tree)170 padding_cell_data_func (GtkTreeViewColumn *tree_column,
171 			GtkCellRenderer *cell,
172 			GtkTreeModel *model,
173 			GtkTreeIter *iter,
174 			RBDisplayPageTree *display_page_tree)
175 {
176 	RBDisplayPage *page;
177 
178 	gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model), iter,
179 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
180 			    -1);
181 	if (RB_IS_DISPLAY_PAGE_GROUP (page)) {
182 		g_object_set (cell,
183 			      "visible", FALSE,
184 			      "xpad", 0,
185 			      "ypad", 0,
186 			      NULL);
187 	} else {
188 		g_object_set (cell,
189 			      "visible", TRUE,
190 			      "xpad", 3,
191 			      "ypad", 3,
192 			      NULL);
193 	}
194 
195 	g_object_unref (page);
196 }
197 
198 static void
padding2_cell_data_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,RBDisplayPageTree * display_page_tree)199 padding2_cell_data_func (GtkTreeViewColumn *tree_column,
200 			 GtkCellRenderer *cell,
201 			 GtkTreeModel *model,
202 			 GtkTreeIter *iter,
203 			 RBDisplayPageTree *display_page_tree)
204 {
205 	GtkTreePath *path;
206 
207 	path = gtk_tree_model_get_path (model, iter);
208 	if (gtk_tree_path_get_depth (path) > 2) {
209 		g_object_set (cell,
210 			      "visible", TRUE,
211 			      "xpad", 3,
212 			      "ypad", 0,
213 			      NULL);
214 	} else {
215 		g_object_set (cell,
216 			      "visible", FALSE,
217 			      "xpad", 0,
218 			      "ypad", 0,
219 			      NULL);
220 	}
221 	gtk_tree_path_free (path);
222 }
223 
224 static void
pixbuf_cell_data_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,RBDisplayPageTree * display_page_tree)225 pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
226 		       GtkCellRenderer   *cell,
227 		       GtkTreeModel      *model,
228 		       GtkTreeIter       *iter,
229 		       RBDisplayPageTree *display_page_tree)
230 {
231 	RBDisplayPage *page;
232 	GtkTreePath *path;
233 	GIcon *icon = NULL;
234 
235 	path = gtk_tree_model_get_path (model, iter);
236 	gtk_tree_model_get (model, iter,
237 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
238 			    -1);
239 
240 	switch (gtk_tree_path_get_depth (path)) {
241 	case 1:
242 		g_object_set (cell, "visible", FALSE, NULL);
243 		break;
244 
245 	case 2:
246 	case 3:
247 		g_object_get (page, "icon", &icon, NULL);
248 		if (icon == NULL) {
249 			g_object_set (cell, "visible", TRUE, "pixbuf", display_page_tree->priv->blank_pixbuf, NULL);
250 		} else {
251 			g_object_set (cell, "visible", TRUE, "gicon", icon, NULL);
252 			g_object_unref (icon);
253 		}
254 		break;
255 
256 	default:
257 		g_object_set (cell, "visible", TRUE, "pixbuf", display_page_tree->priv->blank_pixbuf, NULL);
258 		break;
259 	}
260 
261 	gtk_tree_path_free (path);
262 	g_object_unref (page);
263 }
264 
265 static void
title_cell_data_func(GtkTreeViewColumn * column,GtkCellRenderer * renderer,GtkTreeModel * tree_model,GtkTreeIter * iter,RBDisplayPageTree * display_page_tree)266 title_cell_data_func (GtkTreeViewColumn *column,
267 		      GtkCellRenderer   *renderer,
268 		      GtkTreeModel      *tree_model,
269 		      GtkTreeIter       *iter,
270 		      RBDisplayPageTree *display_page_tree)
271 {
272 	RBDisplayPage *page;
273 	gboolean playing;
274 
275 	gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model), iter,
276 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
277 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING, &playing,
278 			    -1);
279 
280 	if (RB_IS_DISPLAY_PAGE_GROUP (page)) {
281 		g_object_set (renderer, "visible", FALSE, NULL);
282 	} else {
283 		char *name;
284 		g_object_get (page, "name", &name, NULL);
285 
286 		g_object_set (renderer,
287 			      "visible", TRUE,
288 			      "text", name,
289 			      "weight", playing ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
290 			      NULL);
291 		g_free (name);
292 	}
293 
294 	g_object_unref (page);
295 }
296 
297 static void
expander_cell_data_func(GtkTreeViewColumn * column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,RBDisplayPageTree * display_page_tree)298 expander_cell_data_func (GtkTreeViewColumn *column,
299 			 GtkCellRenderer   *cell,
300 			 GtkTreeModel      *model,
301 			 GtkTreeIter       *iter,
302 			 RBDisplayPageTree *display_page_tree)
303 {
304 	RBDisplayPage *page;
305 
306 	gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model), iter,
307 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
308 			    -1);
309 
310 	if (RB_IS_DISPLAY_PAGE_GROUP (page) || gtk_tree_model_iter_has_child (model, iter) == FALSE) {
311 		g_object_set (cell, "visible", FALSE, NULL);
312 	} else if (gtk_tree_model_iter_has_child (model, iter)) {
313 		GtkTreePath *path;
314 		gboolean     row_expanded;
315 
316 		path = gtk_tree_model_get_path (model, iter);
317 		row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (display_page_tree->priv->treeview),
318 							   path);
319 		gtk_tree_path_free (path);
320 
321 		g_object_set (cell,
322 			      "visible", TRUE,
323 			      "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
324 			      NULL);
325 	}
326 
327 	g_object_unref (page);
328 }
329 
330 static void
row_activated_cb(GtkTreeView * treeview,GtkTreePath * path,GtkTreeViewColumn * column,RBDisplayPageTree * display_page_tree)331 row_activated_cb (GtkTreeView       *treeview,
332 		  GtkTreePath       *path,
333 		  GtkTreeViewColumn *column,
334 		  RBDisplayPageTree *display_page_tree)
335 {
336 	GtkTreeModel *model;
337 	GtkTreeIter   iter;
338 	RBDisplayPage *page;
339 
340 	model = gtk_tree_view_get_model (treeview);
341 
342 	g_return_if_fail (gtk_tree_model_get_iter (model, &iter, path));
343 
344 	gtk_tree_model_get (model, &iter,
345 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
346 			    -1);
347 
348 	if (page != NULL) {
349 		rb_debug ("page %p activated", page);
350 		rb_display_page_activate (page);
351 		g_object_unref (page);
352 	}
353 }
354 
355 static void
drop_received_cb(RBDisplayPageModel * model,RBDisplayPage * page,GtkTreeViewDropPosition pos,GtkSelectionData * data,RBDisplayPageTree * display_page_tree)356 drop_received_cb (RBDisplayPageModel     *model,
357 		  RBDisplayPage          *page,
358 		  GtkTreeViewDropPosition pos,
359 		  GtkSelectionData       *data,
360 		  RBDisplayPageTree      *display_page_tree)
361 {
362 	rb_debug ("drop received");
363 	g_signal_emit (display_page_tree, signals[DROP_RECEIVED], 0, page, data);
364 }
365 
366 static gboolean
expand_rows_cb(RBDisplayPageTree * display_page_tree)367 expand_rows_cb (RBDisplayPageTree *display_page_tree)
368 {
369 	GList *l;
370 	rb_debug ("expanding %d rows", g_list_length (display_page_tree->priv->expand_rows));
371 	display_page_tree->priv->expand_rows_id = 0;
372 
373 	for (l = display_page_tree->priv->expand_rows; l != NULL; l = l->next) {
374 		GtkTreePath *path;
375 		path = gtk_tree_row_reference_get_path (l->data);
376 		if (path != NULL) {
377 			gtk_tree_view_expand_to_path (GTK_TREE_VIEW (display_page_tree->priv->treeview), path);
378 			if (l->data == display_page_tree->priv->expand_select_row) {
379 				GtkTreeSelection *selection;
380 				GtkTreeIter iter;
381 
382 				selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (display_page_tree->priv->treeview));
383 				if (gtk_tree_model_get_iter (GTK_TREE_MODEL (display_page_tree->priv->page_model), &iter, path)) {
384 					rb_debug ("selecting one of the expanded rows");
385 					gtk_tree_selection_select_iter (selection, &iter);
386 				}
387 			}
388 			gtk_tree_path_free (path);
389 		}
390 	}
391 
392 	rb_list_destroy_free (display_page_tree->priv->expand_rows, (GDestroyNotify) gtk_tree_row_reference_free);
393 	display_page_tree->priv->expand_rows = NULL;
394 	return FALSE;
395 }
396 
397 static void
model_row_inserted_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,RBDisplayPageTree * display_page_tree)398 model_row_inserted_cb (GtkTreeModel *model,
399 		       GtkTreePath *path,
400 		       GtkTreeIter *iter,
401 		       RBDisplayPageTree *display_page_tree)
402 {
403 	display_page_tree->priv->expand_rows = g_list_append (display_page_tree->priv->expand_rows,
404 							      gtk_tree_row_reference_new (model, path));
405 	if (display_page_tree->priv->expand_rows_id == 0) {
406 		display_page_tree->priv->expand_rows_id = g_idle_add ((GSourceFunc)expand_rows_cb, display_page_tree);
407 	}
408 
409 	gtk_tree_view_columns_autosize (GTK_TREE_VIEW (display_page_tree->priv->treeview));
410 }
411 
412 static gboolean
key_release_cb(GtkTreeView * treeview,GdkEventKey * event,RBDisplayPageTree * display_page_tree)413 key_release_cb (GtkTreeView *treeview,
414 		GdkEventKey *event,
415 		RBDisplayPageTree *display_page_tree)
416 {
417 	RBDisplayPage *page;
418 	gboolean res;
419 
420 	/* F2 = rename playlist */
421 	if (event->keyval != GDK_KEY_F2) {
422 		return FALSE;
423 	}
424 
425 	page = get_selected_page (display_page_tree);
426 	if (page == NULL) {
427 		return FALSE;
428 	} else if (RB_IS_SOURCE (page) == FALSE) {
429 		g_object_unref (page);
430 		return FALSE;
431 	}
432 
433 	res = FALSE;
434 	if (rb_source_can_rename (RB_SOURCE (page))) {
435 		rb_display_page_tree_edit_source_name (display_page_tree, RB_SOURCE (page));
436 		res = TRUE;
437 	}
438 
439 	g_object_unref (page);
440 	return res;
441 }
442 
443 /**
444  * rb_display_page_tree_edit_source_name:
445  * @display_page_tree: the #RBDisplayPageTree
446  * @source: the #RBSource to edit
447  *
448  * Initiates editing of the name of the specified source.  The row for the source
449  * is selected and given input focus, allowing the user to edit the name.
450  * source_name_edited_cb is called when the user finishes editing.
451  */
452 void
rb_display_page_tree_edit_source_name(RBDisplayPageTree * display_page_tree,RBSource * source)453 rb_display_page_tree_edit_source_name (RBDisplayPageTree *display_page_tree,
454 				       RBSource *source)
455 {
456 	GtkTreeIter  iter;
457 	GtkTreePath *path;
458 
459 	g_assert (rb_display_page_model_find_page (display_page_tree->priv->page_model,
460 						   RB_DISPLAY_PAGE (source),
461 						   &iter));
462 	path = gtk_tree_model_get_path (GTK_TREE_MODEL (display_page_tree->priv->page_model),
463 					&iter);
464 	gtk_tree_view_expand_to_path (GTK_TREE_VIEW (display_page_tree->priv->treeview), path);
465 
466 	/* Make cell editable just for the moment.
467 	   We'll turn it off once editing is done. */
468 	g_object_set (display_page_tree->priv->title_renderer, "editable", TRUE, NULL);
469 
470 	gtk_tree_view_set_cursor_on_cell (GTK_TREE_VIEW (display_page_tree->priv->treeview),
471 					  path, display_page_tree->priv->main_column,
472 					  display_page_tree->priv->title_renderer,
473 					  TRUE);
474 	gtk_tree_path_free (path);
475 }
476 
477 /**
478  * rb_display_page_tree_select:
479  * @display_page_tree: the #RBDisplayPageTree
480  * @page: the #RBDisplayPage to select
481  *
482  * Selects the specified page in the tree.  This will result in the 'selected'
483  * signal being emitted.
484  */
485 void
rb_display_page_tree_select(RBDisplayPageTree * display_page_tree,RBDisplayPage * page)486 rb_display_page_tree_select (RBDisplayPageTree *display_page_tree,
487 			     RBDisplayPage *page)
488 {
489 	GtkTreeIter iter;
490 	GtkTreePath *path;
491 	GList *l;
492 	gboolean defer = FALSE;
493 
494 	g_assert (rb_display_page_model_find_page (display_page_tree->priv->page_model,
495 						   page,
496 						   &iter));
497 
498 	/* if this is a path we're trying to expand to, wait until we've done that first */
499 	path = gtk_tree_model_get_path (GTK_TREE_MODEL (display_page_tree->priv->page_model), &iter);
500 	for (l = display_page_tree->priv->expand_rows; l != NULL; l = l->next) {
501 		GtkTreePath *expand_path;
502 
503 		expand_path = gtk_tree_row_reference_get_path (l->data);
504 		if (expand_path != NULL) {
505 			defer = (gtk_tree_path_compare (expand_path, path) == 0);
506 			gtk_tree_path_free (expand_path);
507 		}
508 
509 		if (defer) {
510 			display_page_tree->priv->expand_select_row = l->data;
511 			break;
512 		}
513 	}
514 
515 	if (defer == FALSE) {
516 		gtk_tree_selection_select_iter (display_page_tree->priv->selection, &iter);
517 	}
518 
519 	gtk_tree_path_free (path);
520 }
521 
522 /**
523  * rb_display_page_tree_toggle_expanded:
524  * @display_page_tree: the #RBDisplayPageTree
525  * @page: the #RBDisplayPage to toggle
526  *
527  * If @page is expanded (children visible), collapses it, otherwise expands it.
528  */
529 void
rb_display_page_tree_toggle_expanded(RBDisplayPageTree * display_page_tree,RBDisplayPage * page)530 rb_display_page_tree_toggle_expanded (RBDisplayPageTree *display_page_tree,
531 				      RBDisplayPage *page)
532 {
533 	GtkTreeIter iter;
534 	GtkTreePath *path;
535 
536 	g_assert (rb_display_page_model_find_page (display_page_tree->priv->page_model,
537 						   page,
538 						   &iter));
539 	path = gtk_tree_model_get_path (GTK_TREE_MODEL (display_page_tree->priv->page_model),
540 					&iter);
541 	if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (display_page_tree->priv->treeview), path)) {
542 		rb_debug ("collapsing page %p", page);
543 		gtk_tree_view_collapse_row (GTK_TREE_VIEW (display_page_tree->priv->treeview), path);
544 		g_object_set (display_page_tree->priv->expander_renderer,
545 			      "expander-style",
546 			      GTK_EXPANDER_COLLAPSED,
547 			      NULL);
548 	} else {
549 		rb_debug ("expanding page %p", page);
550 		gtk_tree_view_expand_row (GTK_TREE_VIEW (display_page_tree->priv->treeview), path, FALSE);
551 		g_object_set (display_page_tree->priv->expander_renderer,
552 			      "expander-style",
553 			      GTK_EXPANDER_EXPANDED,
554 			      NULL);
555 	}
556 
557 	gtk_tree_path_free (path);
558 }
559 
560 static gboolean
selection_check_cb(GtkTreeSelection * selection,GtkTreeModel * model,GtkTreePath * path,gboolean currently_selected,RBDisplayPageTree * display_page_tree)561 selection_check_cb (GtkTreeSelection *selection,
562 		    GtkTreeModel *model,
563 		    GtkTreePath *path,
564 		    gboolean currently_selected,
565 		    RBDisplayPageTree *display_page_tree)
566 {
567 	GtkTreeIter iter;
568 	gboolean result = TRUE;
569 
570 	if (currently_selected) {
571 		/* do anything? */
572 	} else if (gtk_tree_model_get_iter (model, &iter, path)) {
573 		RBDisplayPage *page;
574 		gtk_tree_model_get (model, &iter, RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page, -1);
575 
576 		/* figure out if page can be selected */
577 		result = rb_display_page_selectable (page);
578 
579 		g_object_unref (page);
580 	}
581 	return result;
582 }
583 
584 static void
selection_changed_cb(GtkTreeSelection * selection,RBDisplayPageTree * display_page_tree)585 selection_changed_cb (GtkTreeSelection *selection,
586 		      RBDisplayPageTree *display_page_tree)
587 {
588 	RBDisplayPage *page;
589 
590 	page = get_selected_page (display_page_tree);
591 	if (page != NULL) {
592 		g_signal_emit (display_page_tree, signals[SELECTED], 0, page);
593 
594 		if (RB_IS_DEVICE_SOURCE (page) && rb_device_source_can_eject (RB_DEVICE_SOURCE (page))) {
595 			g_simple_action_set_enabled (display_page_tree->priv->eject_action, TRUE);
596 		} else {
597 			g_simple_action_set_enabled (display_page_tree->priv->eject_action, FALSE);
598 		}
599 
600 		g_simple_action_set_enabled (display_page_tree->priv->remove_action, rb_display_page_can_remove (page));
601 		g_object_unref (page);
602 	} else {
603 		g_simple_action_set_enabled (display_page_tree->priv->remove_action, FALSE);
604 		g_simple_action_set_enabled (display_page_tree->priv->eject_action, FALSE);
605 	}
606 }
607 
608 static void
source_name_edited_cb(GtkCellRendererText * renderer,const char * pathstr,const char * text,RBDisplayPageTree * display_page_tree)609 source_name_edited_cb (GtkCellRendererText *renderer,
610 		       const char          *pathstr,
611 		       const char          *text,
612 		       RBDisplayPageTree   *display_page_tree)
613 {
614 	GtkTreePath *path;
615 	GtkTreeIter iter;
616 	RBDisplayPage *page;
617 
618 	if (text[0] == '\0')
619 		return;
620 
621 	path = gtk_tree_path_new_from_string (pathstr);
622 	g_return_if_fail (gtk_tree_model_get_iter (GTK_TREE_MODEL (display_page_tree->priv->page_model), &iter, path));
623 	gtk_tree_path_free (path);
624 
625 	gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model),
626 			    &iter,
627 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
628 			    -1);
629 	if (page == NULL || RB_IS_SOURCE (page) == FALSE) {
630 		g_object_set (renderer, "editable", FALSE, NULL);
631 		return;
632 	}
633 
634 	g_object_set (page, "name", text, NULL);
635 	g_object_unref (page);
636 }
637 
638 static void
remove_action_cb(GSimpleAction * action,GVariant * parameter,gpointer user_data)639 remove_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
640 {
641 	RBDisplayPage *page = get_selected_page (RB_DISPLAY_PAGE_TREE (user_data));
642 	if (page) {
643 		rb_display_page_remove (page);
644 		g_object_unref (page);
645 	}
646 }
647 
648 static void
eject_action_cb(GSimpleAction * action,GVariant * parameter,gpointer user_data)649 eject_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
650 {
651 	RBDisplayPage *page = get_selected_page (RB_DISPLAY_PAGE_TREE (user_data));
652 	if (page == NULL) {
653 		/* nothing */
654 	} else if (RB_IS_DEVICE_SOURCE (page) && rb_device_source_can_eject (RB_DEVICE_SOURCE (page))) {
655 		rb_device_source_eject (RB_DEVICE_SOURCE (page));
656 		g_object_unref (page);
657 	} else {
658 		rb_debug ("why are we here?");
659 		g_object_unref (page);
660 	}
661 }
662 
663 
664 static gboolean
display_page_search_equal_func(GtkTreeModel * model,gint column,const char * key,GtkTreeIter * iter,RBDisplayPageTree * display_page_tree)665 display_page_search_equal_func (GtkTreeModel *model,
666 				gint column,
667 				const char *key,
668 				GtkTreeIter *iter,
669 				RBDisplayPageTree *display_page_tree)
670 {
671 	RBDisplayPage *page;
672 	gboolean result = TRUE;
673 	char *folded_key;
674 	char *name;
675 	char *folded_name;
676 
677 	gtk_tree_model_get (model,
678 			    iter,
679 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
680 			    -1);
681 	g_object_get (page, "name", &name, NULL);
682 
683 	folded_key = rb_search_fold (key);
684 	folded_name = rb_search_fold (name);
685 
686 	if (folded_key != NULL && folded_name != NULL) {
687 		result = (strncmp (folded_key, folded_name, strlen (folded_key)) != 0);
688 	}
689 
690 	g_free (folded_key);
691 	g_free (folded_name);
692 	g_free (name);
693 	g_object_unref (page);
694 	return result;
695 }
696 
697 /**
698  * rb_display_page_tree_new:
699  * @shell: the #RBShell instance
700  *
701  * Creates the display page tree widget.
702  *
703  * Return value: the display page tree widget.
704  */
705 RBDisplayPageTree *
rb_display_page_tree_new(RBShell * shell)706 rb_display_page_tree_new (RBShell *shell)
707 {
708 	return RB_DISPLAY_PAGE_TREE (g_object_new (RB_TYPE_DISPLAY_PAGE_TREE,
709 						   "shell", shell,
710 						   NULL));
711 }
712 
713 static void
impl_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)714 impl_set_property (GObject      *object,
715 		   guint         prop_id,
716 		   const GValue *value,
717 		   GParamSpec   *pspec)
718 {
719 	RBDisplayPageTree *display_page_tree = RB_DISPLAY_PAGE_TREE (object);
720 	switch (prop_id)
721 	{
722 	case PROP_SHELL:
723 		display_page_tree->priv->shell = g_value_get_object (value);
724 		break;
725 	default:
726 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
727 		break;
728 	}
729 }
730 
731 static void
impl_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)732 impl_get_property (GObject    *object,
733 		   guint       prop_id,
734 		   GValue     *value,
735 		   GParamSpec *pspec)
736 {
737 	RBDisplayPageTree *display_page_tree = RB_DISPLAY_PAGE_TREE (object);
738 	switch (prop_id)
739 	{
740 	case PROP_SHELL:
741 		g_value_set_object (value, display_page_tree->priv->shell);
742 		break;
743 	case PROP_MODEL:
744 		g_value_set_object (value, display_page_tree->priv->page_model);
745 		break;
746 	default:
747 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
748 		break;
749 	}
750 }
751 
752 static void
impl_dispose(GObject * object)753 impl_dispose (GObject *object)
754 {
755 	RBDisplayPageTree *display_page_tree = RB_DISPLAY_PAGE_TREE (object);
756 
757 	g_clear_object (&display_page_tree->priv->blank_pixbuf);
758 
759 	G_OBJECT_CLASS (rb_display_page_tree_parent_class)->dispose (object);
760 }
761 
762 static void
impl_finalize(GObject * object)763 impl_finalize (GObject *object)
764 {
765 	RBDisplayPageTree *display_page_tree = RB_DISPLAY_PAGE_TREE (object);
766 
767 	g_object_unref (display_page_tree->priv->page_model);
768 
769 	if (display_page_tree->priv->expand_rows_id != 0) {
770 		g_source_remove (display_page_tree->priv->expand_rows_id);
771 		display_page_tree->priv->expand_rows_id = 0;
772 	}
773 
774 	rb_list_destroy_free (display_page_tree->priv->expand_rows, (GDestroyNotify) gtk_tree_row_reference_free);
775 
776 	G_OBJECT_CLASS (rb_display_page_tree_parent_class)->finalize (object);
777 }
778 
779 static void
impl_constructed(GObject * object)780 impl_constructed (GObject *object)
781 {
782 	RBDisplayPageTree *display_page_tree;
783 	GtkCellRenderer *renderer;
784 	GtkWidget *scrolled;
785 	GtkStyleContext *context;
786 	GtkWidget *box;
787 	GtkToolItem *tool_item;
788 	GtkWidget *button;
789 	GtkWidget *image;
790 	GIcon *icon;
791 	GMenuModel *menu;
792 	GtkBuilder *builder;
793 	GApplication *app;
794 	GtkAccelGroup *accel_group;
795 	int pixbuf_width, pixbuf_height;
796 
797 	GActionEntry actions[] = {
798 		{ "display-page-remove", remove_action_cb },
799 		{ "display-page-eject", eject_action_cb }
800 	};
801 
802 	RB_CHAIN_GOBJECT_METHOD (rb_display_page_tree_parent_class, constructed, object);
803 	display_page_tree = RB_DISPLAY_PAGE_TREE (object);
804 
805 
806 	scrolled = gtk_scrolled_window_new (NULL, NULL);
807 	context = gtk_widget_get_style_context (scrolled);
808 	gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM);
809 	g_object_set (scrolled,
810 		      "hscrollbar_policy", GTK_POLICY_AUTOMATIC,
811 		      "vscrollbar_policy", GTK_POLICY_AUTOMATIC,
812 		      "hexpand", TRUE,
813 		      "vexpand", TRUE,
814 		      NULL);
815 	gtk_grid_attach (GTK_GRID (display_page_tree), scrolled, 0, 0, 1, 1);
816 
817 	display_page_tree->priv->page_model = rb_display_page_model_new ();
818 	g_signal_connect_object (display_page_tree->priv->page_model,
819 				 "drop-received",
820 				 G_CALLBACK (drop_received_cb),
821 				 display_page_tree, 0);
822 	g_signal_connect_object (display_page_tree->priv->page_model,
823 				 "row-inserted",
824 				 G_CALLBACK (model_row_inserted_cb),
825 				 display_page_tree, 0);
826 
827 	display_page_tree->priv->treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (display_page_tree->priv->page_model));
828 	gtk_style_context_add_class (gtk_widget_get_style_context (display_page_tree->priv->treeview), GTK_STYLE_CLASS_SIDEBAR);
829 
830 	g_object_set (display_page_tree->priv->treeview,
831 		      "headers-visible", FALSE,
832 		      "reorderable", TRUE,
833 		      "enable-search", TRUE,
834 		      "search-column", RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE,
835 		      NULL);
836 	gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (display_page_tree->priv->treeview),
837 					     (GtkTreeViewSearchEqualFunc) display_page_search_equal_func,
838 					     display_page_tree,
839 					     NULL);
840 
841 	rb_display_page_model_set_dnd_targets (display_page_tree->priv->page_model,
842 					       GTK_TREE_VIEW (display_page_tree->priv->treeview));
843 
844 	g_signal_connect_object (display_page_tree->priv->treeview,
845 				 "row_activated",
846 				 G_CALLBACK (row_activated_cb),
847 				 display_page_tree, 0);
848 	g_signal_connect_object (display_page_tree->priv->treeview,
849 				 "key_release_event",
850 				 G_CALLBACK (key_release_cb),
851 				 display_page_tree, 0);
852 
853 	display_page_tree->priv->main_column = gtk_tree_view_column_new ();
854 	gtk_tree_view_column_set_clickable (display_page_tree->priv->main_column, FALSE);
855 
856 	gtk_tree_view_append_column (GTK_TREE_VIEW (display_page_tree->priv->treeview),
857 				     display_page_tree->priv->main_column);
858 
859 	gtk_icon_size_lookup (RB_DISPLAY_PAGE_ICON_SIZE, &pixbuf_width, &pixbuf_height);
860 	display_page_tree->priv->blank_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, pixbuf_width, pixbuf_height);
861 	gdk_pixbuf_fill (display_page_tree->priv->blank_pixbuf, 0);
862 
863 	/* initial padding */
864 	renderer = gtk_cell_renderer_text_new ();
865 	gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
866 	g_object_set (renderer, "xpad", 3, NULL);
867 
868 	/* headings */
869 	renderer = gtk_cell_renderer_text_new ();
870 	gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
871 	gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
872 						 renderer,
873 						 (GtkTreeCellDataFunc) heading_cell_data_func,
874 						 display_page_tree,
875 						 NULL);
876 	g_object_set (renderer,
877 		      "weight", PANGO_WEIGHT_BOLD,
878 		      "weight-set", TRUE,
879 		      "ypad", 6,
880 		      "xpad", 0,
881 		      NULL);
882 
883 	/* icon padding */
884 	renderer = gtk_cell_renderer_text_new ();
885 	gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
886 	gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
887 						 renderer,
888 						 (GtkTreeCellDataFunc) padding_cell_data_func,
889 						 display_page_tree,
890 						 NULL);
891 
892 	/* padding for second level */
893 	renderer = gtk_cell_renderer_text_new ();
894 	gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
895 	gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
896 						 renderer,
897 						 (GtkTreeCellDataFunc) padding2_cell_data_func,
898 						 display_page_tree,
899 						 NULL);
900 
901 	/* Set up the pixbuf column */
902 	renderer = gtk_cell_renderer_pixbuf_new ();
903 	gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
904 	gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
905 						 renderer,
906 						 (GtkTreeCellDataFunc) pixbuf_cell_data_func,
907 						 display_page_tree,
908 						 NULL);
909 	if (gtk_check_version (3, 16, 0) != NULL) {
910 		g_object_set (renderer, "follow-state", TRUE, NULL);
911 	}
912 
913 	/* Set up the name column */
914 	renderer = gtk_cell_renderer_text_new ();
915 	g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
916 	gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, TRUE);
917 	gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
918 						 renderer,
919 						 (GtkTreeCellDataFunc) title_cell_data_func,
920 						 display_page_tree,
921 						 NULL);
922 	g_signal_connect_object (renderer, "edited", G_CALLBACK (source_name_edited_cb), display_page_tree, 0);
923 
924 	g_object_set (display_page_tree->priv->treeview, "show-expanders", FALSE, NULL);
925 	display_page_tree->priv->title_renderer = renderer;
926 
927 	/* Expander */
928 	renderer = gossip_cell_renderer_expander_new ();
929 	gtk_tree_view_column_pack_end (display_page_tree->priv->main_column, renderer, FALSE);
930 	gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
931 						 renderer,
932 						 (GtkTreeCellDataFunc) expander_cell_data_func,
933 						 display_page_tree,
934 						 NULL);
935 	display_page_tree->priv->expander_renderer = renderer;
936 
937 	/* toolbar actions */
938 	app = g_application_get_default ();
939 	g_action_map_add_action_entries (G_ACTION_MAP (app), actions, G_N_ELEMENTS (actions), display_page_tree);
940 
941 	/* disable the remove and eject actions initially */
942 	display_page_tree->priv->remove_action = G_SIMPLE_ACTION (g_action_map_lookup_action (G_ACTION_MAP (app), "display-page-remove"));
943 	display_page_tree->priv->eject_action = G_SIMPLE_ACTION (g_action_map_lookup_action (G_ACTION_MAP (app), "display-page-eject"));
944 	g_simple_action_set_enabled (display_page_tree->priv->remove_action, FALSE);
945 	g_simple_action_set_enabled (display_page_tree->priv->eject_action, FALSE);
946 
947 	/* toolbar */
948 	display_page_tree->priv->toolbar = gtk_toolbar_new ();
949 	gtk_toolbar_set_style (GTK_TOOLBAR (display_page_tree->priv->toolbar), GTK_TOOLBAR_ICONS);
950 	gtk_toolbar_set_icon_size (GTK_TOOLBAR (display_page_tree->priv->toolbar), GTK_ICON_SIZE_MENU);
951 
952 	context = gtk_widget_get_style_context (display_page_tree->priv->toolbar);
953 	gtk_style_context_set_junction_sides (context, GTK_JUNCTION_TOP);
954 	gtk_style_context_add_class (context, GTK_STYLE_CLASS_INLINE_TOOLBAR);
955 	gtk_style_context_add_class (context, "sidebar-toolbar");
956 
957 	gtk_grid_attach (GTK_GRID (display_page_tree), display_page_tree->priv->toolbar, 0, 1, 1, 1);
958 
959 	tool_item = gtk_tool_item_new ();
960 	box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
961 	gtk_container_add (GTK_CONTAINER (tool_item), box);
962 	gtk_toolbar_insert (GTK_TOOLBAR (display_page_tree->priv->toolbar), tool_item, -1);
963 
964 	display_page_tree->priv->add_menubutton = gtk_menu_button_new ();
965 	icon = g_themed_icon_new_with_default_fallbacks ("list-add-symbolic");
966 	image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU);
967 	gtk_button_set_image (GTK_BUTTON (display_page_tree->priv->add_menubutton), image);
968 	gtk_box_pack_start (GTK_BOX (box), display_page_tree->priv->add_menubutton, FALSE, FALSE, 0);
969 	g_object_unref (icon);
970 
971 	g_object_get (display_page_tree->priv->shell, "accel-group", &accel_group, NULL);
972 	gtk_widget_add_accelerator (display_page_tree->priv->add_menubutton,
973 				    "activate",
974 				    accel_group,
975 				    GDK_KEY_A,
976 				    GDK_MOD1_MASK,
977 				    GTK_ACCEL_VISIBLE);
978 	g_object_unref (accel_group);
979 
980 	builder = rb_builder_load ("display-page-add-menu.ui", NULL);
981 	menu = G_MENU_MODEL (gtk_builder_get_object (builder, "display-page-add-menu"));
982 	rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), G_MENU (menu));
983 	gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (display_page_tree->priv->add_menubutton), menu);
984 	g_object_unref (builder);
985 
986 	button = gtk_button_new ();
987 	icon = g_themed_icon_new_with_default_fallbacks ("list-remove-symbolic");
988 	image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU);
989 	gtk_button_set_image (GTK_BUTTON (button), image);
990 	gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
991 	g_object_unref (icon);
992 
993 	gtk_actionable_set_action_name (GTK_ACTIONABLE (button), "app.display-page-remove");
994 
995 	/* maybe this should be a column in the tree instead.. */
996 	button = gtk_button_new ();
997 	icon = g_themed_icon_new_with_default_fallbacks ("media-eject-symbolic");
998 	image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU);
999 	gtk_button_set_image (GTK_BUTTON (button), image);
1000 	gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
1001 	g_object_unref (icon);
1002 
1003 	gtk_actionable_set_action_name (GTK_ACTIONABLE (button), "app.display-page-eject");
1004 
1005 	display_page_tree->priv->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (display_page_tree->priv->treeview));
1006 	g_signal_connect_object (display_page_tree->priv->selection,
1007 			         "changed",
1008 			         G_CALLBACK (selection_changed_cb),
1009 			         display_page_tree,
1010 				 0);
1011 	gtk_tree_selection_set_select_function (display_page_tree->priv->selection,
1012 						(GtkTreeSelectionFunc) selection_check_cb,
1013 						display_page_tree,
1014 						NULL);
1015 
1016 	gtk_container_add (GTK_CONTAINER (scrolled), display_page_tree->priv->treeview);
1017 }
1018 
1019 static void
rb_display_page_tree_init(RBDisplayPageTree * display_page_tree)1020 rb_display_page_tree_init (RBDisplayPageTree *display_page_tree)
1021 {
1022 	display_page_tree->priv =
1023 		G_TYPE_INSTANCE_GET_PRIVATE (display_page_tree,
1024 					     RB_TYPE_DISPLAY_PAGE_TREE,
1025 					     RBDisplayPageTreePrivate);
1026 }
1027 
1028 static void
rb_display_page_tree_class_init(RBDisplayPageTreeClass * class)1029 rb_display_page_tree_class_init (RBDisplayPageTreeClass *class)
1030 {
1031 	GObjectClass   *o_class;
1032 
1033 	o_class = (GObjectClass *) class;
1034 
1035 	o_class->constructed = impl_constructed;
1036 	o_class->dispose = impl_dispose;
1037 	o_class->finalize = impl_finalize;
1038 	o_class->set_property = impl_set_property;
1039 	o_class->get_property = impl_get_property;
1040 
1041 	/**
1042 	 * RBDisplayPageTree:shell:
1043 	 *
1044 	 * The #RBShell instance
1045 	 */
1046 	g_object_class_install_property (o_class,
1047 					 PROP_SHELL,
1048 					 g_param_spec_object ("shell",
1049 							      "RBShell",
1050 							      "RBShell object",
1051 							      RB_TYPE_SHELL,
1052 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1053 
1054 	/**
1055 	 * RBDisplayPageTree:model:
1056 	 *
1057 	 * The #GtkTreeModel for the display page tree
1058 	 */
1059 	g_object_class_install_property (o_class,
1060 					 PROP_MODEL,
1061 					 g_param_spec_object ("model",
1062 							      "GtkTreeModel",
1063 							      "GtkTreeModel object",
1064 							      GTK_TYPE_TREE_MODEL,
1065 							      G_PARAM_READABLE));
1066 	/**
1067 	 * RBDisplayPageTree::selected:
1068 	 * @tree: the #RBDisplayPageTree
1069 	 * @page: the newly selected #RBDisplayPage
1070 	 *
1071 	 * Emitted when a page is selected from the tree
1072 	 */
1073 	signals[SELECTED] =
1074 		g_signal_new ("selected",
1075 			      G_OBJECT_CLASS_TYPE (o_class),
1076 			      G_SIGNAL_RUN_LAST,
1077 			      G_STRUCT_OFFSET (RBDisplayPageTreeClass, selected),
1078 			      NULL, NULL,
1079 			      NULL,
1080 			      G_TYPE_NONE,
1081 			      1,
1082 			      G_TYPE_OBJECT);
1083 
1084 	/**
1085 	 * RBDisplayPageTree::drop-received:
1086 	 * @tree: the #RBDisplayPageTree
1087 	 * @page: the #RBDisplagePage receiving the drop
1088 	 * @data: the drop data
1089 	 *
1090 	 * Emitted when a drag and drop to the tree completes.
1091 	 */
1092 	signals[DROP_RECEIVED] =
1093 		g_signal_new ("drop-received",
1094 			      G_OBJECT_CLASS_TYPE (o_class),
1095 			      G_SIGNAL_RUN_LAST,
1096 			      G_STRUCT_OFFSET (RBDisplayPageTreeClass, drop_received),
1097 			      NULL, NULL,
1098 			      NULL,
1099 			      G_TYPE_NONE,
1100 			      2,
1101 			      G_TYPE_POINTER, G_TYPE_POINTER);
1102 
1103 	g_type_class_add_private (class, sizeof (RBDisplayPageTreePrivate));
1104 }
1105