hythmbox-2.98/sources/rb-display-page-tree.c

No issues found

   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-stock-icons.h"
  46 #include "rb-marshal.h"
  47 #include "rb-cell-renderer-pixbuf.h"
  48 #include "gossip-cell-renderer-expander.h"
  49 #include "rb-tree-dnd.h"
  50 #include "rb-util.h"
  51 #include "rb-auto-playlist-source.h"
  52 #include "rb-static-playlist-source.h"
  53 
  54 /**
  55  * SECTION:rb-display-page-tree
  56  * @short_description: widget displaying the tree of #RBDisplayPage instances
  57  *
  58  * The display page tree widget is a GtkTreeView backed by a GtkListStore
  59  * containing the display page instances (sources and other things).  Display
  60  * pages include sources, such as the library and playlists, and other things
  61  * like the visualization display.
  62  *
  63  * Display pages are shown in the list with an icon and the name.  The current
  64  * playing source is displayed in bold.
  65  *
  66  * Sources are divided into groups - library, stores, playlists, devices,
  67  * network shares.  Groups are displayed as headers, with expanders for hiding
  68  * and showing the sources in the group.  Sources themselves may also have
  69  * child sources, such as playlists on portable audio players.
  70  */
  71 
  72 struct _RBDisplayPageTreePrivate
  73 {
  74 	GtkWidget *treeview;
  75 	GtkCellRenderer *title_renderer;
  76 	GtkCellRenderer *expander_renderer;
  77 
  78 	RBDisplayPageModel *page_model;
  79 	GtkTreeSelection *selection;
  80 
  81 	int child_source_count;
  82 	GtkTreeViewColumn *main_column;
  83 
  84 	RBShell *shell;
  85 
  86 	GList *expand_rows;
  87 	GtkTreeRowReference *expand_select_row;
  88 	guint expand_rows_id;
  89 
  90 	GSettings *settings;
  91 };
  92 
  93 
  94 enum
  95 {
  96 	PROP_0,
  97 	PROP_SHELL,
  98 	PROP_MODEL
  99 };
 100 
 101 enum
 102 {
 103 	SELECTED,
 104 	DROP_RECEIVED,
 105 	LAST_SIGNAL
 106 };
 107 
 108 static guint signals[LAST_SIGNAL] = { 0 };
 109 
 110 G_DEFINE_TYPE (RBDisplayPageTree, rb_display_page_tree, GTK_TYPE_SCROLLED_WINDOW)
 111 
 112 
 113 static gboolean
 114 retrieve_expander_state (RBDisplayPageTree *display_page_tree, RBDisplayPageGroup *group)
 115 {
 116 	char **groups;
 117 	char *id;
 118 	gboolean collapsed;
 119 
 120 	groups = g_settings_get_strv (display_page_tree->priv->settings, "collapsed-groups");
 121 	g_object_get (group, "id", &id, NULL);
 122 	collapsed = rb_str_in_strv (id, (const char **)groups);
 123 	g_free (id);
 124 	g_strfreev (groups);
 125 
 126 	return (collapsed == FALSE);
 127 }
 128 
 129 static void
 130 store_expander_state (RBDisplayPageTree *display_page_tree, RBDisplayPageGroup *group, gboolean expanded)
 131 {
 132 	char **newgroups = NULL;
 133 	char **groups;
 134 	char *id;
 135 	int num;
 136 	int i;
 137 	int p;
 138 
 139 	groups = g_settings_get_strv (display_page_tree->priv->settings, "collapsed-groups");
 140 	g_object_get (group, "id", &id, NULL);
 141 
 142 	num = g_strv_length (groups);
 143 	p = 0;
 144 	if (rb_str_in_strv (id, (const char **)groups) && expanded) {
 145 		newgroups = g_new0(char *, num);
 146 		for (i = 0; i < num; i++) {
 147 			if (g_strcmp0 (groups[i], id) != 0) {
 148 				newgroups[p++] = g_strdup (groups[i]);
 149 			}
 150 		}
 151 	} else if (expanded == FALSE) {
 152 		newgroups = g_new0(char *, num + 2);
 153 		for (i = 0; i < num; i++) {
 154 			newgroups[i] = g_strdup (groups[i]);
 155 		}
 156 		newgroups[i] = g_strdup (id);
 157 	}
 158 
 159 	if (newgroups != NULL) {
 160 		g_settings_set_strv (display_page_tree->priv->settings, "collapsed-groups", (const char * const *)newgroups);
 161 		g_strfreev (newgroups);
 162 	}
 163 	g_strfreev (groups);
 164 	g_free (id);
 165 }
 166 
 167 static void
 168 set_cell_background (RBDisplayPageTree  *display_page_tree,
 169 		     GtkCellRenderer    *cell,
 170 		     gboolean            is_group,
 171 		     gboolean            is_active)
 172 {
 173 	GdkRGBA color;
 174 
 175 	g_return_if_fail (display_page_tree != NULL);
 176 	g_return_if_fail (cell != NULL);
 177 
 178 	gtk_style_context_get_color (gtk_widget_get_style_context (GTK_WIDGET (display_page_tree)),
 179 				     GTK_STATE_SELECTED,
 180 				     &color);
 181 
 182 	if (!is_group) {
 183 		if (is_active) {
 184 			/* Here we take the current theme colour and add it to
 185 			 * the colour for white and average the two. This
 186 			 * gives a colour which is inline with the theme but
 187 			 * slightly whiter.
 188 			 */
 189 			color.red = (color.red + 1.0) / 2;
 190 			color.green = (color.green + 1.0) / 2;
 191 			color.blue = (color.blue + 1.0) / 2;
 192 
 193 			g_object_set (cell,
 194 				      "cell-background-rgba", &color,
 195 				      NULL);
 196 		} else {
 197 			g_object_set (cell,
 198 				      "cell-background-rgba", NULL,
 199 				      NULL);
 200 		}
 201 	} else {
 202 		/* don't set background for group heading */
 203 	}
 204 }
 205 
 206 static void
 207 indent_level1_cell_data_func (GtkTreeViewColumn *tree_column,
 208 			      GtkCellRenderer   *cell,
 209 			      GtkTreeModel      *model,
 210 			      GtkTreeIter       *iter,
 211 			      RBDisplayPageTree *display_page_tree)
 212 {
 213 	GtkTreePath *path;
 214 	int          depth;
 215 
 216 	path = gtk_tree_model_get_path (model, iter);
 217 	depth = gtk_tree_path_get_depth (path);
 218 	gtk_tree_path_free (path);
 219 	g_object_set (cell,
 220 		      "text", "    ",
 221 		      "visible", depth > 1,
 222 		      NULL);
 223 }
 224 
 225 static void
 226 indent_level2_cell_data_func (GtkTreeViewColumn *tree_column,
 227 			      GtkCellRenderer   *cell,
 228 			      GtkTreeModel      *model,
 229 			      GtkTreeIter       *iter,
 230 			      RBDisplayPageTree *display_page_tree)
 231 {
 232 	GtkTreePath *path;
 233 	int          depth;
 234 
 235 	path = gtk_tree_model_get_path (model, iter);
 236 	depth = gtk_tree_path_get_depth (path);
 237 	gtk_tree_path_free (path);
 238 	g_object_set (cell,
 239 		      "text", "    ",
 240 		      "visible", depth > 2,
 241 		      NULL);
 242 }
 243 
 244 static void
 245 pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
 246 		       GtkCellRenderer   *cell,
 247 		       GtkTreeModel      *model,
 248 		       GtkTreeIter       *iter,
 249 		       RBDisplayPageTree *display_page_tree)
 250 {
 251 	RBDisplayPage *page;
 252 	GdkPixbuf *pixbuf;
 253 
 254 	gtk_tree_model_get (model, iter,
 255 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
 256 			    -1);
 257 	g_object_get (page, "pixbuf", &pixbuf, NULL);
 258 
 259 	if (pixbuf == NULL) {
 260 		g_object_set (cell,
 261 			      "visible", FALSE,
 262 			      "pixbuf", NULL,
 263 			      NULL);
 264 	} else {
 265 		g_object_set (cell,
 266 			      "visible", TRUE,
 267 			      "pixbuf", pixbuf,
 268 			      NULL);
 269 		g_object_unref (pixbuf);
 270 	}
 271 
 272 	set_cell_background (display_page_tree, cell, RB_IS_DISPLAY_PAGE_GROUP (page), FALSE);
 273 	g_object_unref (page);
 274 }
 275 
 276 static void
 277 title_cell_data_func (GtkTreeViewColumn *column,
 278 		      GtkCellRenderer   *renderer,
 279 		      GtkTreeModel      *tree_model,
 280 		      GtkTreeIter       *iter,
 281 		      RBDisplayPageTree *display_page_tree)
 282 {
 283 	RBDisplayPage *page;
 284 	char    *name;
 285 	gboolean playing;
 286 
 287 	gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model), iter,
 288 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
 289 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING, &playing,
 290 			    -1);
 291 
 292 	g_object_get (page, "name", &name, NULL);
 293 
 294 	g_object_set (renderer,
 295 		      "text", name,
 296 		      "weight", playing ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
 297 		      NULL);
 298 
 299 	set_cell_background (display_page_tree, renderer, RB_IS_DISPLAY_PAGE_GROUP (page), FALSE);
 300 
 301 	g_free (name);
 302 	g_object_unref (page);
 303 }
 304 
 305 static void
 306 expander_cell_data_func (GtkTreeViewColumn *column,
 307 			 GtkCellRenderer   *cell,
 308 			 GtkTreeModel      *model,
 309 			 GtkTreeIter       *iter,
 310 			 RBDisplayPageTree *display_page_tree)
 311 {
 312 	RBDisplayPage *page;
 313 
 314 	if (gtk_tree_model_iter_has_child (model, iter)) {
 315 		GtkTreePath *path;
 316 		gboolean     row_expanded;
 317 
 318 		path = gtk_tree_model_get_path (model, iter);
 319 		row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (display_page_tree->priv->treeview),
 320 							   path);
 321 		gtk_tree_path_free (path);
 322 
 323 		g_object_set (cell,
 324 			      "visible", TRUE,
 325 			      "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
 326 			      NULL);
 327 	} else {
 328 		g_object_set (cell, "visible", FALSE, NULL);
 329 	}
 330 
 331 	gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model), iter,
 332 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
 333 			    -1);
 334 	set_cell_background (display_page_tree, cell, RB_IS_DISPLAY_PAGE_GROUP (page), FALSE);
 335 	g_object_unref (page);
 336 }
 337 
 338 static void
 339 row_activated_cb (GtkTreeView       *treeview,
 340 		  GtkTreePath       *path,
 341 		  GtkTreeViewColumn *column,
 342 		  RBDisplayPageTree *display_page_tree)
 343 {
 344 	GtkTreeModel *model;
 345 	GtkTreeIter   iter;
 346 	RBDisplayPage *page;
 347 
 348 	model = gtk_tree_view_get_model (treeview);
 349 
 350 	g_return_if_fail (gtk_tree_model_get_iter (model, &iter, path));
 351 
 352 	gtk_tree_model_get (model, &iter,
 353 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
 354 			    -1);
 355 
 356 	if (page != NULL) {
 357 		rb_debug ("page %p activated", page);
 358 		rb_display_page_activate (page);
 359 		g_object_unref (page);
 360 	}
 361 }
 362 
 363 static void
 364 update_expanded_state (RBDisplayPageTree *display_page_tree,
 365 		       GtkTreeIter *iter,
 366 		       gboolean expanded)
 367 {
 368 	GtkTreeModel *model;
 369 	RBDisplayPage *page;
 370 
 371 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (display_page_tree->priv->treeview));
 372 	gtk_tree_model_get (model, iter,
 373 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
 374 			    -1);
 375 	if (RB_IS_DISPLAY_PAGE_GROUP (page)) {
 376 		store_expander_state (display_page_tree, RB_DISPLAY_PAGE_GROUP (page), expanded);
 377 	}
 378 }
 379 
 380 static void
 381 row_expanded_cb (GtkTreeView *treeview,
 382 		 GtkTreeIter *iter,
 383 		 GtkTreePath *path,
 384 		 RBDisplayPageTree *display_page_tree)
 385 {
 386 	update_expanded_state (display_page_tree, iter, TRUE);
 387 }
 388 
 389 static void
 390 row_collapsed_cb (GtkTreeView *treeview,
 391 		  GtkTreeIter *iter,
 392 		  GtkTreePath *path,
 393 		  RBDisplayPageTree *display_page_tree)
 394 {
 395 	update_expanded_state (display_page_tree, iter, FALSE);
 396 }
 397 
 398 static void
 399 drop_received_cb (RBDisplayPageModel     *model,
 400 		  RBDisplayPage          *page,
 401 		  GtkTreeViewDropPosition pos,
 402 		  GtkSelectionData       *data,
 403 		  RBDisplayPageTree      *display_page_tree)
 404 {
 405 	rb_debug ("drop received");
 406 	g_signal_emit (display_page_tree, signals[DROP_RECEIVED], 0, page, data);
 407 }
 408 
 409 static gboolean
 410 expand_rows_cb (RBDisplayPageTree *display_page_tree)
 411 {
 412 	GList *l;
 413 	rb_debug ("expanding %d rows", g_list_length (display_page_tree->priv->expand_rows));
 414 	display_page_tree->priv->expand_rows_id = 0;
 415 
 416 	for (l = display_page_tree->priv->expand_rows; l != NULL; l = l->next) {
 417 		GtkTreePath *path;
 418 		path = gtk_tree_row_reference_get_path (l->data);
 419 		if (path != NULL) {
 420 			gtk_tree_view_expand_to_path (GTK_TREE_VIEW (display_page_tree->priv->treeview), path);
 421 			if (l->data == display_page_tree->priv->expand_select_row) {
 422 				GtkTreeSelection *selection;
 423 				GtkTreeIter iter;
 424 
 425 				selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (display_page_tree->priv->treeview));
 426 				if (gtk_tree_model_get_iter (GTK_TREE_MODEL (display_page_tree->priv->page_model), &iter, path)) {
 427 					rb_debug ("selecting one of the expanded rows");
 428 					gtk_tree_selection_select_iter (selection, &iter);
 429 				}
 430 			}
 431 			gtk_tree_path_free (path);
 432 		}
 433 	}
 434 
 435 	rb_list_destroy_free (display_page_tree->priv->expand_rows, (GDestroyNotify) gtk_tree_row_reference_free);
 436 	display_page_tree->priv->expand_rows = NULL;
 437 	return FALSE;
 438 }
 439 
 440 static void
 441 model_row_inserted_cb (GtkTreeModel *model,
 442 		       GtkTreePath *path,
 443 		       GtkTreeIter *iter,
 444 		       RBDisplayPageTree *display_page_tree)
 445 {
 446 	gboolean expand = FALSE;
 447 	if (gtk_tree_path_get_depth (path) == 2) {
 448 		GtkTreeIter group_iter;
 449 		expand = TRUE;
 450 		if (gtk_tree_model_iter_parent (model, &group_iter, iter)) {
 451 			gboolean loaded;
 452 			RBDisplayPage *page;
 453 			RBDisplayPageGroupCategory category;
 454 
 455 			gtk_tree_model_get (model, &group_iter,
 456 					    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
 457 					    -1);
 458 			g_object_get (page, "loaded", &loaded, "category", &category, NULL);
 459 			if (category == RB_DISPLAY_PAGE_GROUP_CATEGORY_TRANSIENT || loaded == FALSE) {
 460 				expand = retrieve_expander_state (display_page_tree, RB_DISPLAY_PAGE_GROUP (page));
 461 			}
 462 			g_object_unref (page);
 463 		}
 464 	} else if (gtk_tree_path_get_depth (path) == 1) {
 465 		RBDisplayPage *page;
 466 
 467 		gtk_tree_model_get (model, iter,
 468 				    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
 469 				    -1);
 470 		expand = retrieve_expander_state (display_page_tree, RB_DISPLAY_PAGE_GROUP (page));
 471 	}
 472 
 473 	if (expand) {
 474 		display_page_tree->priv->expand_rows = g_list_append (display_page_tree->priv->expand_rows,
 475 								      gtk_tree_row_reference_new (model, path));
 476 		if (display_page_tree->priv->expand_rows_id == 0) {
 477 			display_page_tree->priv->expand_rows_id = g_idle_add ((GSourceFunc)expand_rows_cb, display_page_tree);
 478 		}
 479 	}
 480 
 481 	gtk_tree_view_columns_autosize (GTK_TREE_VIEW (display_page_tree->priv->treeview));
 482 }
 483 
 484 static gboolean
 485 emit_show_popup (GtkTreeView *treeview,
 486 		 RBDisplayPageTree *display_page_tree)
 487 {
 488 	GtkTreeIter iter;
 489 	RBDisplayPage *page;
 490 
 491 	if (!gtk_tree_selection_get_selected (gtk_tree_view_get_selection (treeview),
 492 					      NULL, &iter))
 493 		return FALSE;
 494 
 495 	gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model),
 496 			    &iter,
 497 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
 498 			    -1);
 499 	if (page == NULL)
 500 		return FALSE;
 501 
 502 	g_return_val_if_fail (RB_IS_DISPLAY_PAGE (page), FALSE);
 503 
 504 	rb_display_page_show_popup (page);
 505 	g_object_unref (page);
 506 	return TRUE;
 507 }
 508 
 509 static gboolean
 510 button_press_cb (GtkTreeView *treeview,
 511 		 GdkEventButton *event,
 512 		 RBDisplayPageTree *display_page_tree)
 513 {
 514 	GtkTreeIter  iter;
 515 	GtkTreePath *path;
 516 	gboolean     res;
 517 
 518 	if (event->button != 3) {
 519 		return FALSE;
 520 	}
 521 
 522 	res = gtk_tree_view_get_path_at_pos (treeview,
 523 					     event->x,
 524 					     event->y,
 525 					     &path,
 526 					     NULL,
 527 					     NULL,
 528 					     NULL);
 529 	if (! res) {
 530 		/* pointer is over empty space */
 531 		GtkUIManager *uimanager;
 532 		g_object_get (display_page_tree->priv->shell, "ui-manager", &uimanager, NULL);
 533 		rb_gtk_action_popup_menu (uimanager, "/DisplayPageTreePopup");
 534 		g_object_unref (uimanager);
 535 		return TRUE;
 536 	}
 537 
 538 	res = gtk_tree_model_get_iter (GTK_TREE_MODEL (display_page_tree->priv->page_model),
 539 				       &iter,
 540 				       path);
 541 	gtk_tree_path_free (path);
 542 	if (res) {
 543 		gtk_tree_selection_select_iter (gtk_tree_view_get_selection (treeview), &iter);
 544 	}
 545 
 546 	return emit_show_popup (treeview, display_page_tree);
 547 }
 548 
 549 static gboolean
 550 key_release_cb (GtkTreeView *treeview,
 551 		GdkEventKey *event,
 552 		RBDisplayPageTree *display_page_tree)
 553 {
 554 	GtkTreeIter iter;
 555 	RBDisplayPage *page;
 556 	gboolean res;
 557 
 558 	/* F2 = rename playlist */
 559 	if (event->keyval != GDK_KEY_F2) {
 560 		return FALSE;
 561 	}
 562 
 563 	if (!gtk_tree_selection_get_selected (display_page_tree->priv->selection, NULL, &iter)) {
 564 		return FALSE;
 565 	}
 566 
 567 	gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model),
 568 			    &iter,
 569 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
 570 			    -1);
 571 	if (page == NULL || RB_IS_SOURCE (page) == FALSE) {
 572 		return FALSE;
 573 	}
 574 
 575 	res = FALSE;
 576 	if (rb_source_can_rename (RB_SOURCE (page))) {
 577 		rb_display_page_tree_edit_source_name (display_page_tree, RB_SOURCE (page));
 578 		res = TRUE;
 579 	}
 580 
 581 	g_object_unref (page);
 582 	return res;
 583 }
 584 
 585 static gboolean
 586 popup_menu_cb (GtkTreeView *treeview,
 587 	       RBDisplayPageTree *display_page_tree)
 588 {
 589 	return emit_show_popup (treeview, display_page_tree);
 590 }
 591 
 592 
 593 /**
 594  * rb_display_page_tree_edit_source_name:
 595  * @display_page_tree: the #RBDisplayPageTree
 596  * @source: the #RBSource to edit
 597  *
 598  * Initiates editing of the name of the specified source.  The row for the source
 599  * is selected and given input focus, allowing the user to edit the name.
 600  * source_name_edited_cb is called when the user finishes editing.
 601  */
 602 void
 603 rb_display_page_tree_edit_source_name (RBDisplayPageTree *display_page_tree,
 604 				       RBSource *source)
 605 {
 606 	GtkTreeIter  iter;
 607 	GtkTreePath *path;
 608 
 609 	g_assert (rb_display_page_model_find_page (display_page_tree->priv->page_model,
 610 						   RB_DISPLAY_PAGE (source),
 611 						   &iter));
 612 	path = gtk_tree_model_get_path (GTK_TREE_MODEL (display_page_tree->priv->page_model),
 613 					&iter);
 614 	gtk_tree_view_expand_to_path (GTK_TREE_VIEW (display_page_tree->priv->treeview), path);
 615 
 616 	/* Make cell editable just for the moment.
 617 	   We'll turn it off once editing is done. */
 618 	g_object_set (display_page_tree->priv->title_renderer, "editable", TRUE, NULL);
 619 
 620 	gtk_tree_view_set_cursor_on_cell (GTK_TREE_VIEW (display_page_tree->priv->treeview),
 621 					  path, display_page_tree->priv->main_column,
 622 					  display_page_tree->priv->title_renderer,
 623 					  TRUE);
 624 	gtk_tree_path_free (path);
 625 }
 626 
 627 /**
 628  * rb_display_page_tree_select:
 629  * @display_page_tree: the #RBDisplayPageTree
 630  * @page: the #RBDisplayPage to select
 631  *
 632  * Selects the specified page in the tree.  This will result in the 'selected'
 633  * signal being emitted.
 634  */
 635 void
 636 rb_display_page_tree_select (RBDisplayPageTree *display_page_tree,
 637 			     RBDisplayPage *page)
 638 {
 639 	GtkTreeIter iter;
 640 	GtkTreePath *path;
 641 	GList *l;
 642 	gboolean defer = FALSE;
 643 
 644 	g_assert (rb_display_page_model_find_page (display_page_tree->priv->page_model,
 645 						   page,
 646 						   &iter));
 647 
 648 	/* if this is a path we're trying to expand to, wait until we've done that first */
 649 	path = gtk_tree_model_get_path (GTK_TREE_MODEL (display_page_tree->priv->page_model), &iter);
 650 	for (l = display_page_tree->priv->expand_rows; l != NULL; l = l->next) {
 651 		GtkTreePath *expand_path;
 652 
 653 		expand_path = gtk_tree_row_reference_get_path (l->data);
 654 		if (expand_path != NULL) {
 655 			defer = (gtk_tree_path_compare (expand_path, path) == 0);
 656 			gtk_tree_path_free (expand_path);
 657 		}
 658 
 659 		if (defer) {
 660 			display_page_tree->priv->expand_select_row = l->data;
 661 			break;
 662 		}
 663 	}
 664 
 665 	if (defer == FALSE) {
 666 		gtk_tree_selection_select_iter (display_page_tree->priv->selection, &iter);
 667 	}
 668 
 669 	gtk_tree_path_free (path);
 670 }
 671 
 672 /**
 673  * rb_display_page_tree_toggle_expanded:
 674  * @display_page_tree: the #RBDisplayPageTree
 675  * @page: the #RBDisplayPage to toggle
 676  *
 677  * If @page is expanded (children visible), collapses it, otherwise expands it.
 678  */
 679 void
 680 rb_display_page_tree_toggle_expanded (RBDisplayPageTree *display_page_tree,
 681 				      RBDisplayPage *page)
 682 {
 683 	GtkTreeIter iter;
 684 	GtkTreePath *path;
 685 
 686 	g_assert (rb_display_page_model_find_page (display_page_tree->priv->page_model,
 687 						   page,
 688 						   &iter));
 689 	path = gtk_tree_model_get_path (GTK_TREE_MODEL (display_page_tree->priv->page_model),
 690 					&iter);
 691 	if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (display_page_tree->priv->treeview), path)) {
 692 		rb_debug ("collapsing page %p", page);
 693 		gtk_tree_view_collapse_row (GTK_TREE_VIEW (display_page_tree->priv->treeview), path);
 694 		g_object_set (display_page_tree->priv->expander_renderer,
 695 			      "expander-style",
 696 			      GTK_EXPANDER_COLLAPSED,
 697 			      NULL);
 698 	} else {
 699 		rb_debug ("expanding page %p", page);
 700 		gtk_tree_view_expand_row (GTK_TREE_VIEW (display_page_tree->priv->treeview), path, FALSE);
 701 		g_object_set (display_page_tree->priv->expander_renderer,
 702 			      "expander-style",
 703 			      GTK_EXPANDER_EXPANDED,
 704 			      NULL);
 705 	}
 706 
 707 	gtk_tree_path_free (path);
 708 }
 709 
 710 static gboolean
 711 selection_check_cb (GtkTreeSelection *selection,
 712 		    GtkTreeModel *model,
 713 		    GtkTreePath *path,
 714 		    gboolean currently_selected,
 715 		    RBDisplayPageTree *display_page_tree)
 716 {
 717 	GtkTreeIter iter;
 718 	gboolean result = TRUE;
 719 
 720 	if (currently_selected) {
 721 		/* do anything? */
 722 	} else if (gtk_tree_model_get_iter (model, &iter, path)) {
 723 		RBDisplayPage *page;
 724 		gtk_tree_model_get (model, &iter, RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page, -1);
 725 
 726 		/* figure out if page can be selected */
 727 		result = rb_display_page_selectable (page);
 728 
 729 		g_object_unref (page);
 730 	}
 731 	return result;
 732 }
 733 
 734 static void
 735 selection_changed_cb (GtkTreeSelection *selection,
 736 		      RBDisplayPageTree *display_page_tree)
 737 {
 738 	GtkTreeIter iter;
 739 	GtkTreeModel *model;
 740 	RBDisplayPage *page;
 741 
 742 	if (!gtk_tree_selection_get_selected (display_page_tree->priv->selection, &model, &iter))
 743 		return;
 744 
 745 	gtk_tree_model_get (model,
 746 			    &iter,
 747 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
 748 			    -1);
 749 	if (page == NULL)
 750 		return;
 751 	g_signal_emit (display_page_tree, signals[SELECTED], 0, page);
 752 	g_object_unref (page);
 753 }
 754 
 755 static void
 756 source_name_edited_cb (GtkCellRendererText *renderer,
 757 		       const char          *pathstr,
 758 		       const char          *text,
 759 		       RBDisplayPageTree   *display_page_tree)
 760 {
 761 	GtkTreePath *path;
 762 	GtkTreeIter iter;
 763 	RBDisplayPage *page;
 764 
 765 	if (text[0] == '\0')
 766 		return;
 767 
 768 	path = gtk_tree_path_new_from_string (pathstr);
 769 	g_return_if_fail (gtk_tree_model_get_iter (GTK_TREE_MODEL (display_page_tree->priv->page_model), &iter, path));
 770 	gtk_tree_path_free (path);
 771 
 772 	gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model),
 773 			    &iter,
 774 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
 775 			    -1);
 776 	if (page == NULL || RB_IS_SOURCE (page) == FALSE) {
 777 		g_object_set (renderer, "editable", FALSE, NULL);
 778 		return;
 779 	}
 780 
 781 	g_object_set (page, "name", text, NULL);
 782 	g_object_unref (page);
 783 }
 784 
 785 static gboolean
 786 display_page_search_equal_func (GtkTreeModel *model,
 787 				gint column,
 788 				const char *key,
 789 				GtkTreeIter *iter,
 790 				RBDisplayPageTree *display_page_tree)
 791 {
 792 	RBDisplayPage *page;
 793 	gboolean result = TRUE;
 794 	char *folded_key;
 795 	char *name;
 796 	char *folded_name;
 797 
 798 	gtk_tree_model_get (model,
 799 			    iter,
 800 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
 801 			    -1);
 802 	g_object_get (page, "name", &name, NULL);
 803 
 804 	folded_key = rb_search_fold (key);
 805 	folded_name = rb_search_fold (name);
 806 
 807 	if (folded_key != NULL && folded_name != NULL) {
 808 		result = (strncmp (folded_key, folded_name, strlen (folded_key)) != 0);
 809 	}
 810 
 811 	g_free (folded_key);
 812 	g_free (folded_name);
 813 	g_free (name);
 814 	g_object_unref (page);
 815 	return result;
 816 }
 817 
 818 /**
 819  * rb_display_page_tree_new:
 820  * @shell: the #RBShell instance
 821  *
 822  * Creates the display page tree widget.
 823  *
 824  * Return value: the display page tree widget.
 825  */
 826 RBDisplayPageTree *
 827 rb_display_page_tree_new (RBShell *shell)
 828 {
 829 	return RB_DISPLAY_PAGE_TREE (g_object_new (RB_TYPE_DISPLAY_PAGE_TREE,
 830 						   "hadjustment", NULL,
 831 						   "vadjustment", NULL,
 832 						   "hscrollbar_policy", GTK_POLICY_AUTOMATIC,
 833 						   "vscrollbar_policy", GTK_POLICY_AUTOMATIC,
 834 						   "shadow_type", GTK_SHADOW_IN,
 835 						   "shell", shell,
 836 						   NULL));
 837 }
 838 
 839 static void
 840 impl_set_property (GObject      *object,
 841 		   guint         prop_id,
 842 		   const GValue *value,
 843 		   GParamSpec   *pspec)
 844 {
 845 	RBDisplayPageTree *display_page_tree = RB_DISPLAY_PAGE_TREE (object);
 846 	switch (prop_id)
 847 	{
 848 	case PROP_SHELL:
 849 		display_page_tree->priv->shell = g_value_get_object (value);
 850 		break;
 851 	default:
 852 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 853 		break;
 854 	}
 855 }
 856 
 857 static void
 858 impl_get_property (GObject    *object,
 859 		   guint       prop_id,
 860 		   GValue     *value,
 861 		   GParamSpec *pspec)
 862 {
 863 	RBDisplayPageTree *display_page_tree = RB_DISPLAY_PAGE_TREE (object);
 864 	switch (prop_id)
 865 	{
 866 	case PROP_SHELL:
 867 		g_value_set_object (value, display_page_tree->priv->shell);
 868 		break;
 869 	case PROP_MODEL:
 870 		g_value_set_object (value, display_page_tree->priv->page_model);
 871 		break;
 872 	default:
 873 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 874 		break;
 875 	}
 876 }
 877 
 878 static void
 879 impl_finalize (GObject *object)
 880 {
 881 	RBDisplayPageTree *display_page_tree = RB_DISPLAY_PAGE_TREE (object);
 882 
 883 	g_object_unref (display_page_tree->priv->page_model);
 884 
 885 	if (display_page_tree->priv->expand_rows_id != 0) {
 886 		g_source_remove (display_page_tree->priv->expand_rows_id);
 887 		display_page_tree->priv->expand_rows_id = 0;
 888 	}
 889 
 890 	rb_list_destroy_free (display_page_tree->priv->expand_rows, (GDestroyNotify) gtk_tree_row_reference_free);
 891 
 892 	G_OBJECT_CLASS (rb_display_page_tree_parent_class)->finalize (object);
 893 }
 894 
 895 static void
 896 impl_constructed (GObject *object)
 897 {
 898 	RBDisplayPageTree *display_page_tree;
 899 
 900 	RB_CHAIN_GOBJECT_METHOD (rb_display_page_tree_parent_class, constructed, object);
 901 	display_page_tree = RB_DISPLAY_PAGE_TREE (object);
 902 
 903 	gtk_container_add (GTK_CONTAINER (display_page_tree), display_page_tree->priv->treeview);
 904 
 905 	display_page_tree->priv->settings = g_settings_new ("org.gnome.rhythmbox.display-page-tree");
 906 }
 907 
 908 static void
 909 rb_display_page_tree_init (RBDisplayPageTree *display_page_tree)
 910 {
 911 	GtkCellRenderer *renderer;
 912 
 913 	display_page_tree->priv =
 914 		G_TYPE_INSTANCE_GET_PRIVATE (display_page_tree,
 915 					     RB_TYPE_DISPLAY_PAGE_TREE,
 916 					     RBDisplayPageTreePrivate);
 917 
 918 	display_page_tree->priv->page_model = rb_display_page_model_new ();
 919 	g_signal_connect_object (display_page_tree->priv->page_model,
 920 				 "drop-received",
 921 				 G_CALLBACK (drop_received_cb),
 922 				 display_page_tree, 0);
 923 	g_signal_connect_object (display_page_tree->priv->page_model,
 924 				 "row-inserted",
 925 				 G_CALLBACK (model_row_inserted_cb),
 926 				 display_page_tree, 0);
 927 
 928 	display_page_tree->priv->treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (display_page_tree->priv->page_model));
 929 
 930 	g_object_set (display_page_tree->priv->treeview,
 931 		      "headers-visible", FALSE,
 932 		      "reorderable", TRUE,
 933 		      "enable-search", TRUE,
 934 		      "search-column", RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE,
 935 		      NULL);
 936 	gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (display_page_tree->priv->treeview),
 937 					     (GtkTreeViewSearchEqualFunc) display_page_search_equal_func,
 938 					     display_page_tree,
 939 					     NULL);
 940 
 941 	rb_display_page_model_set_dnd_targets (display_page_tree->priv->page_model,
 942 					       GTK_TREE_VIEW (display_page_tree->priv->treeview));
 943 
 944 	g_signal_connect_object (display_page_tree->priv->treeview,
 945 				 "row_activated",
 946 				 G_CALLBACK (row_activated_cb),
 947 				 display_page_tree, 0);
 948 	g_signal_connect_object (display_page_tree->priv->treeview,
 949 				 "row-collapsed",
 950 				 G_CALLBACK (row_collapsed_cb),
 951 				 display_page_tree, 0);
 952 	g_signal_connect_object (display_page_tree->priv->treeview,
 953 				 "row-expanded",
 954 				 G_CALLBACK (row_expanded_cb),
 955 				 display_page_tree, 0);
 956 	g_signal_connect_object (display_page_tree->priv->treeview,
 957 				 "button_press_event",
 958 				 G_CALLBACK (button_press_cb),
 959 				 display_page_tree, 0);
 960 	g_signal_connect_object (display_page_tree->priv->treeview,
 961 				 "key_release_event",
 962 				 G_CALLBACK (key_release_cb),
 963 				 display_page_tree, 0);
 964 
 965 	g_signal_connect_object (display_page_tree->priv->treeview,
 966 				 "popup_menu",
 967 				 G_CALLBACK (popup_menu_cb),
 968 				 display_page_tree, 0);
 969 
 970 	display_page_tree->priv->main_column = gtk_tree_view_column_new ();
 971 	gtk_tree_view_column_set_clickable (display_page_tree->priv->main_column, FALSE);
 972 
 973 	gtk_tree_view_append_column (GTK_TREE_VIEW (display_page_tree->priv->treeview),
 974 				     display_page_tree->priv->main_column);
 975 
 976 	/* Set up the indent level1 column */
 977 	renderer = gtk_cell_renderer_text_new ();
 978 	gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
 979 	gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
 980 						 renderer,
 981 						 (GtkTreeCellDataFunc) indent_level1_cell_data_func,
 982 						 display_page_tree,
 983 						 NULL);
 984 	g_object_set (renderer,
 985 		      "xpad", 0,
 986 		      "visible", FALSE,
 987 		      NULL);
 988 
 989 	/* Set up the indent level2 column */
 990 	renderer = gtk_cell_renderer_text_new ();
 991 	gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
 992 	gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
 993 						 renderer,
 994 						 (GtkTreeCellDataFunc) indent_level2_cell_data_func,
 995 						 display_page_tree,
 996 						 NULL);
 997 	g_object_set (renderer,
 998 		      "xpad", 0,
 999 		      "visible", FALSE,
1000 		      NULL);
1001 
1002 	/* Set up the pixbuf column */
1003 	renderer = gtk_cell_renderer_pixbuf_new ();
1004 	gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
1005 	gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
1006 						 renderer,
1007 						 (GtkTreeCellDataFunc) pixbuf_cell_data_func,
1008 						 display_page_tree,
1009 						 NULL);
1010 
1011 	g_object_set (renderer,
1012 		      "xpad", 8,
1013 		      "ypad", 1,
1014 		      "visible", FALSE,
1015 		      NULL);
1016 
1017 
1018 	/* Set up the name column */
1019 	renderer = gtk_cell_renderer_text_new ();
1020 	g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1021 	gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, TRUE);
1022 	gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
1023 						 renderer,
1024 						 (GtkTreeCellDataFunc) title_cell_data_func,
1025 						 display_page_tree,
1026 						 NULL);
1027 	g_signal_connect_object (renderer, "edited", G_CALLBACK (source_name_edited_cb), display_page_tree, 0);
1028 
1029 	g_object_set (display_page_tree->priv->treeview, "show-expanders", FALSE, NULL);
1030 	display_page_tree->priv->title_renderer = renderer;
1031 
1032 	/* Expander */
1033 	renderer = gossip_cell_renderer_expander_new ();
1034 	gtk_tree_view_column_pack_end (display_page_tree->priv->main_column, renderer, FALSE);
1035 	gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
1036 						 renderer,
1037 						 (GtkTreeCellDataFunc) expander_cell_data_func,
1038 						 display_page_tree,
1039 						 NULL);
1040 	display_page_tree->priv->expander_renderer = renderer;
1041 
1042 	display_page_tree->priv->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (display_page_tree->priv->treeview));
1043 	g_signal_connect_object (display_page_tree->priv->selection,
1044 			         "changed",
1045 			         G_CALLBACK (selection_changed_cb),
1046 			         display_page_tree,
1047 				 0);
1048 	gtk_tree_selection_set_select_function (display_page_tree->priv->selection,
1049 						(GtkTreeSelectionFunc) selection_check_cb,
1050 						display_page_tree,
1051 						NULL);
1052 }
1053 
1054 static void
1055 rb_display_page_tree_class_init (RBDisplayPageTreeClass *class)
1056 {
1057 	GObjectClass   *o_class;
1058 
1059 	o_class = (GObjectClass *) class;
1060 
1061 	o_class->constructed = impl_constructed;
1062 	o_class->finalize = impl_finalize;
1063 	o_class->set_property = impl_set_property;
1064 	o_class->get_property = impl_get_property;
1065 
1066 	/**
1067 	 * RBDisplayPageTree:shell:
1068 	 *
1069 	 * The #RBShell instance
1070 	 */
1071 	g_object_class_install_property (o_class,
1072 					 PROP_SHELL,
1073 					 g_param_spec_object ("shell",
1074 							      "RBShell",
1075 							      "RBShell object",
1076 							      RB_TYPE_SHELL,
1077 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1078 
1079 	/**
1080 	 * RBDisplayPageTree:model:
1081 	 *
1082 	 * The #GtkTreeModel for the display page tree
1083 	 */
1084 	g_object_class_install_property (o_class,
1085 					 PROP_MODEL,
1086 					 g_param_spec_object ("model",
1087 							      "GtkTreeModel",
1088 							      "GtkTreeModel object",
1089 							      GTK_TYPE_TREE_MODEL,
1090 							      G_PARAM_READABLE));
1091 	/**
1092 	 * RBDisplayPageTree::selected:
1093 	 * @tree: the #RBDisplayPageTree
1094 	 * @page: the newly selected #RBDisplayPage
1095 	 *
1096 	 * Emitted when a page is selected from the tree
1097 	 */
1098 	signals[SELECTED] =
1099 		g_signal_new ("selected",
1100 			      G_OBJECT_CLASS_TYPE (o_class),
1101 			      G_SIGNAL_RUN_LAST,
1102 			      G_STRUCT_OFFSET (RBDisplayPageTreeClass, selected),
1103 			      NULL, NULL,
1104 			      g_cclosure_marshal_VOID__OBJECT,
1105 			      G_TYPE_NONE,
1106 			      1,
1107 			      G_TYPE_OBJECT);
1108 
1109 	/**
1110 	 * RBDisplayPageTree::drop-received:
1111 	 * @tree: the #RBDisplayPageTree
1112 	 * @page: the #RBDisplagePage receiving the drop
1113 	 * @data: the drop data
1114 	 *
1115 	 * Emitted when a drag and drop to the tree completes.
1116 	 */
1117 	signals[DROP_RECEIVED] =
1118 		g_signal_new ("drop-received",
1119 			      G_OBJECT_CLASS_TYPE (o_class),
1120 			      G_SIGNAL_RUN_LAST,
1121 			      G_STRUCT_OFFSET (RBDisplayPageTreeClass, drop_received),
1122 			      NULL, NULL,
1123 			      rb_marshal_VOID__POINTER_POINTER,
1124 			      G_TYPE_NONE,
1125 			      2,
1126 			      G_TYPE_POINTER, G_TYPE_POINTER);
1127 
1128 	g_type_class_add_private (class, sizeof (RBDisplayPageTreePrivate));
1129 }