hythmbox-2.98/widgets/rb-entry-view.c

No issues found

   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 modify
   6  *  it under the terms of the GNU General Public License as published by
   7  *  the Free Software Foundation; either version 2 of the License, or
   8  *  (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
  21  *  GNU General Public License for more details.
  22  *
  23  *  You should have received a copy of the GNU General Public License
  24  *  along with this program; if not, write to the Free Software
  25  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
  26  *
  27  */
  28 
  29 /**
  30  * SECTION:rb-entry-view
  31  * @short_description: a #GtkTreeView for displaying track listings
  32  *
  33  * This class provides a predefined set of columns for displaying the
  34  * common set of #RhythmDBEntry properties, but also allows custom columns
  35  * to be appended.  The 'playing' column is always created as the first
  36  * column in the tree view, displaying a playing or paused image next to
  37  * the currently playing entry, and also an error image next to entries for
  38  * which a playback error message has been set.  Clicking on the error
  39  * image opens a dialog displaying the full message.
  40  *
  41  * All columns added to entry view columns should be expandable, or have a fixed
  42  * minimum width set.  Otherwise, the tree view must measure the contents of each
  43  * row to assign sizes, which is very slow for large track lists.  All the predefined
  44  * column types handle this correctly.
  45  */
  46 
  47 /**
  48  * RBEntryViewColumn:
  49  * @RB_ENTRY_VIEW_COL_TRACK_NUMBER: the track number column
  50  * @RB_ENTRY_VIEW_COL_TITLE: the title column
  51  * @RB_ENTRY_VIEW_COL_ARTIST: the artist column
  52  * @RB_ENTRY_VIEW_COL_ALBUM: the album column
  53  * @RB_ENTRY_VIEW_COL_GENRE: the genre column
  54  * @RB_ENTRY_VIEW_COL_DURATION: the duration column
  55  * @RB_ENTRY_VIEW_COL_QUALITY: the quality (bitrate) column
  56  * @RB_ENTRY_VIEW_COL_RATING: the rating column
  57  * @RB_ENTRY_VIEW_COL_PLAY_COUNT: the play count column
  58  * @RB_ENTRY_VIEW_COL_YEAR: the year (release date) column
  59  * @RB_ENTRY_VIEW_COL_LAST_PLAYED: the last played time column
  60  * @RB_ENTRY_VIEW_COL_FIRST_SEEN: the first seen (imported) column
  61  * @RB_ENTRY_VIEW_COL_LAST_SEEN: the last seen column
  62  * @RB_ENTRY_VIEW_COL_LOCATION: the location column
  63  * @RB_ENTRY_VIEW_COL_BPM: the BPM column
  64  * @RB_ENTRY_VIEW_COL_COMMENT: the comment column
  65  * @RB_ENTRY_VIEW_COL_ERROR: the error column
  66  *
  67  * Predefined column types to use in #RBEntryView<!-- -->s.  Use
  68  * #rb_entry_view_append_column to add these to an entry view.
  69  * The predefined column names map directly to the #RhythmDBEntry properties
  70  * the columns display.
  71  */
  72 
  73 #include "config.h"
  74 
  75 #include <string.h>
  76 #include <stdlib.h>
  77 #include <math.h>
  78 #include <time.h>
  79 
  80 #include <glib/gi18n.h>
  81 #include <gtk/gtk.h>
  82 #include <gio/gio.h>
  83 #include <glib.h>
  84 
  85 #include "rb-tree-dnd.h"
  86 #include "rb-entry-view.h"
  87 #include "rb-dialog.h"
  88 #include "rb-debug.h"
  89 #include "rb-util.h"
  90 #include "rhythmdb.h"
  91 #include "rhythmdb-query-model.h"
  92 #include "rb-cell-renderer-pixbuf.h"
  93 #include "rb-cell-renderer-rating.h"
  94 #include "rb-stock-icons.h"
  95 #include "rb-shell-player.h"
  96 #include "rb-cut-and-paste-code.h"
  97 
  98 static const GtkTargetEntry rb_entry_view_drag_types[] = {
  99 	{ "application/x-rhythmbox-entry", 0, 0 },
 100 	{ "text/uri-list", 0, 1 }
 101 };
 102 
 103 struct RBEntryViewColumnSortData
 104 {
 105 	GCompareDataFunc func;
 106 	gpointer data;
 107 	GDestroyNotify data_destroy;
 108 };
 109 
 110 /* GObject data item used to associate cell renderers with property IDs */
 111 #define CELL_PROPID_ITEM "rb-cell-propid"
 112 
 113 static void rb_entry_view_class_init (RBEntryViewClass *klass);
 114 static void rb_entry_view_init (RBEntryView *view);
 115 static void rb_entry_view_constructed (GObject *object);
 116 static void rb_entry_view_dispose (GObject *object);
 117 static void rb_entry_view_finalize (GObject *object);
 118 static void rb_entry_view_sort_data_finalize (gpointer column,
 119 					      gpointer sort_data,
 120 					      gpointer user_data);
 121 static void rb_entry_view_set_property (GObject *object,
 122 				       guint prop_id,
 123 				       const GValue *value,
 124 				       GParamSpec *pspec);
 125 static void rb_entry_view_get_property (GObject *object,
 126 				       guint prop_id,
 127 				       GValue *value,
 128 				       GParamSpec *pspec);
 129 static void rb_entry_view_selection_changed_cb (GtkTreeSelection *selection,
 130 				               RBEntryView *view);
 131 static void rb_entry_view_grab_focus (GtkWidget *widget);
 132 static void rb_entry_view_row_activated_cb (GtkTreeView *treeview,
 133 			                   GtkTreePath *path,
 134 			                   GtkTreeViewColumn *column,
 135 			                   RBEntryView *view);
 136 static void rb_entry_view_row_inserted_cb (GtkTreeModel *model,
 137 					   GtkTreePath *path,
 138 					   GtkTreeIter *iter,
 139 					   RBEntryView *view);
 140 static void rb_entry_view_row_deleted_cb (GtkTreeModel *model,
 141 					  GtkTreePath *path,
 142 					  RBEntryView *view);
 143 static void rb_entry_view_rows_reordered_cb (GtkTreeModel *model,
 144 					     GtkTreePath *path,
 145 					     GtkTreeIter *iter,
 146 					     gint *order,
 147 					     RBEntryView *view);
 148 static void rb_entry_view_sync_columns_visible (RBEntryView *view);
 149 static void rb_entry_view_rated_cb (RBCellRendererRating *cellrating,
 150 				   const char *path,
 151 				   double rating,
 152 				   RBEntryView *view);
 153 static void rb_entry_view_pixbuf_clicked_cb (RBEntryView *view,
 154 					     const char *path,
 155 					     RBCellRendererPixbuf *cellpixbuf);
 156 static gboolean rb_entry_view_button_press_cb (GtkTreeView *treeview,
 157 					      GdkEventButton *event,
 158 					      RBEntryView *view);
 159 static gboolean rb_entry_view_popup_menu_cb (GtkTreeView *treeview,
 160 					     RBEntryView *view);
 161 static void rb_entry_view_entry_is_visible (RBEntryView *view, RhythmDBEntry *entry,
 162 					    gboolean *realized, gboolean *visible,
 163 					    GtkTreeIter *iter);
 164 static void rb_entry_view_scroll_to_iter (RBEntryView *view,
 165 					  GtkTreeIter *iter);
 166 static gboolean rb_entry_view_emit_row_changed (RBEntryView *view,
 167 						RhythmDBEntry *entry);
 168 static void rb_entry_view_playing_song_changed (RBShellPlayer *player,
 169 						RhythmDBEntry *entry,
 170 						RBEntryView *view);
 171 
 172 struct RBEntryViewPrivate
 173 {
 174 	RhythmDB *db;
 175 	RBShellPlayer *shell_player;
 176 
 177 	RhythmDBQueryModel *model;
 178 
 179 	GtkWidget *treeview;
 180 	GtkTreeSelection *selection;
 181 
 182 	RBEntryViewState playing_state;
 183 	RhythmDBQueryModel *playing_model;
 184 	RhythmDBEntry *playing_entry;
 185 	gboolean playing_entry_in_view;
 186 	guint selection_changed_id;
 187 
 188 	gboolean is_drag_source;
 189 	gboolean is_drag_dest;
 190 
 191 	char *sorting_key;
 192 	GtkTreeViewColumn *sorting_column;
 193 	gint sorting_order;
 194 	char *sorting_column_name;
 195 	RhythmDBPropType type_ahead_propid;
 196 	char **visible_columns;
 197 
 198 	gboolean have_selection, have_complete_selection;
 199 
 200 	GHashTable *column_key_map;
 201 
 202 	GHashTable *propid_column_map;
 203 	GHashTable *column_sort_data_map;
 204 };
 205 
 206 
 207 enum
 208 {
 209 	ENTRY_ADDED,
 210 	ENTRY_DELETED,
 211 	ENTRIES_REPLACED,
 212 	SELECTION_CHANGED,
 213 	ENTRY_ACTIVATED,
 214 	SHOW_POPUP,
 215 	HAVE_SEL_CHANGED,
 216 	LAST_SIGNAL
 217 };
 218 
 219 enum
 220 {
 221 	PROP_0,
 222 	PROP_DB,
 223 	PROP_SHELL_PLAYER,
 224 	PROP_MODEL,
 225 	PROP_SORT_ORDER,
 226 	PROP_IS_DRAG_SOURCE,
 227 	PROP_IS_DRAG_DEST,
 228 	PROP_PLAYING_STATE,
 229 	PROP_VISIBLE_COLUMNS
 230 };
 231 
 232 G_DEFINE_TYPE (RBEntryView, rb_entry_view, GTK_TYPE_SCROLLED_WINDOW)
 233 
 234 static guint rb_entry_view_signals[LAST_SIGNAL] = { 0 };
 235 
 236 static GQuark rb_entry_view_column_always_visible;
 237 
 238 static gboolean
 239 type_ahead_search_func (GtkTreeModel *model,
 240 			gint column,
 241 			const gchar *key,
 242 			GtkTreeIter *iter,
 243 			gpointer search_data)
 244 {
 245 	RBEntryView *view = RB_ENTRY_VIEW (search_data);
 246 	RhythmDBEntry *entry;
 247 	gchar *folded;
 248 	const gchar *entry_folded;
 249 	gboolean res;
 250 
 251 	gtk_tree_model_get (model, iter, 0, &entry, -1);
 252 	folded = rb_search_fold (key);
 253 	entry_folded = rb_refstring_get_folded (rhythmdb_entry_get_refstring (entry, view->priv->type_ahead_propid));
 254 	rhythmdb_entry_unref (entry);
 255 
 256 	if (entry_folded == NULL || folded == NULL)
 257 		return 1;
 258 
 259 	res = (strstr (entry_folded, folded) == NULL);
 260 	g_free (folded);
 261 
 262 	return res;
 263 }
 264 
 265 static void
 266 rb_entry_view_class_init (RBEntryViewClass *klass)
 267 {
 268 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 269 	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 270 
 271 	object_class->dispose = rb_entry_view_dispose;
 272 	object_class->finalize = rb_entry_view_finalize;
 273 	object_class->constructed = rb_entry_view_constructed;
 274 
 275 	object_class->set_property = rb_entry_view_set_property;
 276 	object_class->get_property = rb_entry_view_get_property;
 277 
 278 	widget_class->grab_focus = rb_entry_view_grab_focus;
 279 
 280 	/**
 281 	 * RBEntryView:db:
 282 	 *
 283 	 * #RhythmDB instance
 284 	 */
 285 	g_object_class_install_property (object_class,
 286 					 PROP_DB,
 287 					 g_param_spec_object ("db",
 288 							      "RhythmDB",
 289 							      "RhythmDB database",
 290 							      RHYTHMDB_TYPE,
 291 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 292 	/**
 293 	 * RBEntryView:shell-player:
 294 	 *
 295 	 * #RBShellPlayer instance
 296 	 */
 297 	g_object_class_install_property (object_class,
 298 					 PROP_SHELL_PLAYER,
 299 					 g_param_spec_object ("shell-player",
 300 							      "RBShellPlayer",
 301 							      "RBShellPlayer object",
 302 							      RB_TYPE_SHELL_PLAYER,
 303 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 304 	/**
 305 	 * RBEntryView:model:
 306 	 *
 307 	 * The #RhythmDBQueryModel backing the view
 308 	 */
 309 	g_object_class_install_property (object_class,
 310 					 PROP_MODEL,
 311 					 g_param_spec_object ("model",
 312 							      "RhythmDBQueryModel",
 313 							      "RhythmDBQueryModel",
 314 							      RHYTHMDB_TYPE_QUERY_MODEL,
 315 							      G_PARAM_READWRITE));
 316 	/**
 317 	 * RBEntryView:sort-order:
 318 	 *
 319 	 * The sort order for the track listing.
 320 	 */
 321 	g_object_class_install_property (object_class,
 322 					 PROP_SORT_ORDER,
 323 					 g_param_spec_string ("sort-order",
 324 							      "sorting order",
 325 							      "sorting order",
 326 							      NULL,
 327 							      G_PARAM_READWRITE));
 328 	/**
 329 	 * RBEntryView:is-drag-source:
 330 	 *
 331 	 * If TRUE, the view acts as a data source for drag and drop operations.
 332 	 */
 333 	g_object_class_install_property (object_class,
 334 					 PROP_IS_DRAG_SOURCE,
 335 					 g_param_spec_boolean ("is-drag-source",
 336 							       "is drag source",
 337 							       "whether or not this is a drag source",
 338 							       FALSE,
 339 							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 340 	/**
 341 	 * RBEntryView:is-drag-dest:
 342 	 *
 343 	 * If TRUE, the view acts as a destination for drag and drop operations.
 344 	 */
 345 	g_object_class_install_property (object_class,
 346 					 PROP_IS_DRAG_DEST,
 347 					 g_param_spec_boolean ("is-drag-dest",
 348 							       "is drag dest",
 349 							       "whether or not this is a drag dest",
 350 							       FALSE,
 351 							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 352 	/**
 353 	 * RBEntryView:playing-state:
 354 	 *
 355 	 * Determines the icon to show in the 'playing' column next to the current
 356 	 * playing entry.
 357 	 */
 358 	g_object_class_install_property (object_class,
 359 					 PROP_PLAYING_STATE,
 360 					 g_param_spec_int ("playing-state",
 361 						 	   "playing state",
 362 							   "playback state for this entry view",
 363 							   RB_ENTRY_VIEW_NOT_PLAYING,
 364 							   RB_ENTRY_VIEW_PAUSED,
 365 							   RB_ENTRY_VIEW_NOT_PLAYING,
 366 							   G_PARAM_READWRITE));
 367 	/**
 368 	 * RBEntryView:visible-columns:
 369 	 *
 370 	 * An array containing the names of the visible columns.
 371 	 */
 372 	g_object_class_install_property (object_class,
 373 					 PROP_VISIBLE_COLUMNS,
 374 					 g_param_spec_boxed ("visible-columns",
 375 							     "visible columns",
 376 							     "visible columns",
 377 							     G_TYPE_STRV,
 378 							     G_PARAM_READWRITE));
 379 	/**
 380 	 * RBEntryView::entry-added:
 381 	 * @view: the #RBEntryView
 382 	 * @entry: the #RhythmDBEntry that was added
 383 	 *
 384 	 * Emitted when an entry is added to the view
 385 	 */
 386 	rb_entry_view_signals[ENTRY_ADDED] =
 387 		g_signal_new ("entry-added",
 388 			      G_OBJECT_CLASS_TYPE (object_class),
 389 			      G_SIGNAL_RUN_LAST,
 390 			      G_STRUCT_OFFSET (RBEntryViewClass, entry_added),
 391 			      NULL, NULL,
 392 			      g_cclosure_marshal_VOID__BOXED,
 393 			      G_TYPE_NONE,
 394 			      1,
 395 			      RHYTHMDB_TYPE_ENTRY);
 396 	/**
 397 	 * RBEntryView::entry-deleted:
 398 	 * @view: the #RBEntryView
 399 	 * @entry: the #RhythmDBEntry that was removed
 400 	 *
 401 	 * Emitted when an entry has been removed from the view
 402 	 */
 403 	rb_entry_view_signals[ENTRY_DELETED] =
 404 		g_signal_new ("entry-deleted",
 405 			      G_OBJECT_CLASS_TYPE (object_class),
 406 			      G_SIGNAL_RUN_LAST,
 407 			      G_STRUCT_OFFSET (RBEntryViewClass, entry_deleted),
 408 			      NULL, NULL,
 409 			      g_cclosure_marshal_VOID__BOXED,
 410 			      G_TYPE_NONE,
 411 			      1,
 412 			      RHYTHMDB_TYPE_ENTRY);
 413 	/**
 414 	 * RBEntryView::entries-replaced:
 415 	 * @view: the #RBEntryView
 416 	 *
 417 	 * Emitted when the model backing the entry view is replaced.
 418 	 */
 419 	rb_entry_view_signals[ENTRIES_REPLACED] =
 420 		g_signal_new ("entries-replaced",
 421 			      G_OBJECT_CLASS_TYPE (object_class),
 422 			      G_SIGNAL_RUN_LAST,
 423 			      G_STRUCT_OFFSET (RBEntryViewClass, entries_replaced),
 424 			      NULL, NULL,
 425 			      g_cclosure_marshal_VOID__VOID,
 426 			      G_TYPE_NONE,
 427 			      0);
 428 	/**
 429 	 * RBEntryView::entry-activated:
 430 	 * @view: the #RBEntryView
 431 	 * @entry: the #RhythmDBEntry that was activated
 432 	 *
 433 	 * Emitted when an entry in the view is activated (by double clicking
 434 	 * or by various key presses)
 435 	 */
 436 	rb_entry_view_signals[ENTRY_ACTIVATED] =
 437 		g_signal_new ("entry-activated",
 438 			      G_OBJECT_CLASS_TYPE (object_class),
 439 			      G_SIGNAL_RUN_LAST,
 440 			      G_STRUCT_OFFSET (RBEntryViewClass, entry_activated),
 441 			      NULL, NULL,
 442 			      g_cclosure_marshal_VOID__BOXED,
 443 			      G_TYPE_NONE,
 444 			      1,
 445 			      RHYTHMDB_TYPE_ENTRY);
 446 	/**
 447 	 * RBEntryView::selection-changed:
 448 	 * @view: the #RBEntryView
 449 	 *
 450 	 * Emitted when the set of selected entries changes
 451 	 */
 452 	rb_entry_view_signals[SELECTION_CHANGED] =
 453 		g_signal_new ("selection-changed",
 454 			      G_OBJECT_CLASS_TYPE (object_class),
 455 			      G_SIGNAL_RUN_LAST,
 456 			      G_STRUCT_OFFSET (RBEntryViewClass, selection_changed),
 457 			      NULL, NULL,
 458 			      g_cclosure_marshal_VOID__VOID,
 459 			      G_TYPE_NONE,
 460 			      0);
 461 	/**
 462 	 * RBEntryView::show-popup:
 463 	 * @view: the #RBEntryView
 464 	 * @over_entry: if TRUE, the popup request was made while pointing
 465 	 * at an entry in the view
 466 	 *
 467 	 * Emitted when the user performs an action that should result in a
 468 	 * popup menu appearing.  If the action was a mouse button click, 
 469 	 * over_entry is FALSE if the mouse pointer was in the blank space after
 470 	 * the last row in the view.  If the action was a key press, over_entry
 471 	 * is FALSE if no rows in the view are selected.
 472 	 */
 473 	rb_entry_view_signals[SHOW_POPUP] =
 474 		g_signal_new ("show_popup",
 475 			      G_OBJECT_CLASS_TYPE (object_class),
 476 			      G_SIGNAL_RUN_LAST,
 477 			      G_STRUCT_OFFSET (RBEntryViewClass, show_popup),
 478 			      NULL, NULL,
 479 			      g_cclosure_marshal_VOID__BOOLEAN,
 480 			      G_TYPE_NONE,
 481 			      1,
 482 			      G_TYPE_BOOLEAN);
 483 	/**
 484 	 * RBEntryView::have-selection-changed:
 485 	 * @view: the #RBEntryView
 486 	 * @have_selection: TRUE if one or more rows are selected
 487 	 *
 488 	 * Emitted when the user first selects a row, or when no rows are selected
 489 	 * any more.
 490 	 */
 491 	rb_entry_view_signals[HAVE_SEL_CHANGED] =
 492 		g_signal_new ("have_selection_changed",
 493 			      G_OBJECT_CLASS_TYPE (object_class),
 494 			      G_SIGNAL_RUN_LAST,
 495 			      G_STRUCT_OFFSET (RBEntryViewClass, have_selection_changed),
 496 			      NULL, NULL,
 497 			      g_cclosure_marshal_VOID__BOOLEAN,
 498 			      G_TYPE_NONE,
 499 			      1,
 500 			      G_TYPE_BOOLEAN);
 501 
 502 	g_type_class_add_private (klass, sizeof (RBEntryViewPrivate));
 503 
 504 	rb_entry_view_column_always_visible = g_quark_from_static_string ("rb_entry_view_column_always_visible");
 505 }
 506 
 507 static void
 508 rb_entry_view_init (RBEntryView *view)
 509 {
 510 	view->priv = G_TYPE_INSTANCE_GET_PRIVATE (view, RB_TYPE_ENTRY_VIEW, RBEntryViewPrivate);
 511 
 512 	view->priv->propid_column_map = g_hash_table_new (NULL, NULL);
 513 	view->priv->column_sort_data_map = g_hash_table_new_full (NULL, NULL, NULL, g_free);
 514 	view->priv->column_key_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
 515 	view->priv->type_ahead_propid = RHYTHMDB_PROP_TITLE;
 516 }
 517 
 518 static void
 519 rb_entry_view_dispose (GObject *object)
 520 {
 521 	RBEntryView *view;
 522 
 523 	g_return_if_fail (object != NULL);
 524 	g_return_if_fail (RB_IS_ENTRY_VIEW (object));
 525 
 526 	view = RB_ENTRY_VIEW (object);
 527 
 528 	g_return_if_fail (view->priv != NULL);
 529 
 530 	if (view->priv->selection_changed_id > 0) {
 531 		g_source_remove (view->priv->selection_changed_id);
 532 		view->priv->selection_changed_id = 0;
 533 	}
 534 
 535 	if (view->priv->playing_model != NULL) {
 536 		g_object_unref (view->priv->playing_model);
 537 		view->priv->playing_model = NULL;
 538 	}
 539 
 540 	if (view->priv->model != NULL) {
 541 		/* remove the model from the treeview so
 542 		 * atk-bridge doesn't have to emit deletion events
 543 		 * for each cell in the view.
 544 		 */
 545 		gtk_tree_view_set_model (GTK_TREE_VIEW (view->priv->treeview), NULL);
 546 
 547 		g_object_unref (view->priv->model);
 548 		view->priv->model = NULL;
 549 	}
 550 
 551 	G_OBJECT_CLASS (rb_entry_view_parent_class)->dispose (object);
 552 }
 553 
 554 static void
 555 rb_entry_view_finalize (GObject *object)
 556 {
 557 	RBEntryView *view;
 558 
 559 	g_return_if_fail (object != NULL);
 560 	g_return_if_fail (RB_IS_ENTRY_VIEW (object));
 561 
 562 	view = RB_ENTRY_VIEW (object);
 563 
 564 	g_return_if_fail (view->priv != NULL);
 565 
 566 	g_hash_table_destroy (view->priv->propid_column_map);
 567 	g_hash_table_foreach (view->priv->column_sort_data_map,
 568 			      rb_entry_view_sort_data_finalize, NULL);
 569 	g_hash_table_destroy (view->priv->column_sort_data_map);
 570 	g_hash_table_destroy (view->priv->column_key_map);
 571 
 572 	g_free (view->priv->sorting_column_name);
 573 
 574 	G_OBJECT_CLASS (rb_entry_view_parent_class)->finalize (object);
 575 }
 576 
 577 static void
 578 rb_entry_view_sort_data_finalize (gpointer column,
 579 				  gpointer gsort_data,
 580 				  gpointer user_data)
 581 {
 582 	struct RBEntryViewColumnSortData *sort_data = gsort_data;
 583 
 584 	if (sort_data->data_destroy) {
 585 		sort_data->data_destroy (sort_data->data);
 586 
 587 		sort_data->data_destroy = NULL;
 588 		sort_data->data = NULL;
 589 		sort_data->func = NULL;
 590 	}
 591 }
 592 
 593 static void
 594 rb_entry_view_set_shell_player_internal (RBEntryView   *view,
 595 					 RBShellPlayer *player)
 596 {
 597 	if (view->priv->shell_player != NULL) {
 598 		g_signal_handlers_disconnect_by_func (view->priv->shell_player,
 599 						      G_CALLBACK (rb_entry_view_playing_song_changed),
 600 						      view);
 601 	}
 602 
 603 	view->priv->shell_player = player;
 604 
 605 	g_signal_connect_object (view->priv->shell_player,
 606 				 "playing-song-changed",
 607 				 G_CALLBACK (rb_entry_view_playing_song_changed),
 608 				 view, 0);
 609 }
 610 
 611 static void
 612 rb_entry_view_set_model_internal (RBEntryView        *view,
 613 				  RhythmDBQueryModel *model)
 614 {
 615 	if (view->priv->model != NULL) {
 616 		g_signal_handlers_disconnect_by_func (view->priv->model,
 617 						      G_CALLBACK (rb_entry_view_row_inserted_cb),
 618 						      view);
 619 		g_signal_handlers_disconnect_by_func (view->priv->model,
 620 						      G_CALLBACK (rb_entry_view_row_deleted_cb),
 621 						      view);
 622 		g_signal_handlers_disconnect_by_func (view->priv->model,
 623 						      G_CALLBACK (rb_entry_view_rows_reordered_cb),
 624 						      view);
 625 		g_object_unref (view->priv->model);
 626 	}
 627 
 628 	gtk_tree_selection_unselect_all (view->priv->selection);
 629 
 630 	view->priv->model = model;
 631 	if (view->priv->model != NULL) {
 632 		g_object_ref (view->priv->model);
 633 		g_signal_connect_object (view->priv->model,
 634 					 "row_inserted",
 635 					 G_CALLBACK (rb_entry_view_row_inserted_cb),
 636 					 view,
 637 					 0);
 638 		g_signal_connect_object (view->priv->model,
 639 					 "row_deleted",
 640 					 G_CALLBACK (rb_entry_view_row_deleted_cb),
 641 					 view,
 642 					 0);
 643 		g_signal_connect_object (view->priv->model,
 644 					 "rows_reordered",
 645 					 G_CALLBACK (rb_entry_view_rows_reordered_cb),
 646 					 view,
 647 					 0);
 648 
 649 		if (view->priv->sorting_column != NULL) {
 650 			rb_entry_view_resort_model (view);
 651 		}
 652 
 653 		gtk_tree_view_set_model (GTK_TREE_VIEW (view->priv->treeview),
 654 					 GTK_TREE_MODEL (view->priv->model));
 655 	}
 656 
 657 	view->priv->have_selection = FALSE;
 658 	view->priv->have_complete_selection = FALSE;
 659 
 660 	g_signal_emit (G_OBJECT (view), rb_entry_view_signals[ENTRIES_REPLACED], 0);
 661 }
 662 
 663 static void
 664 rb_entry_view_set_property (GObject *object,
 665 			    guint prop_id,
 666 			    const GValue *value,
 667 			    GParamSpec *pspec)
 668 {
 669 	RBEntryView *view = RB_ENTRY_VIEW (object);
 670 
 671 	switch (prop_id) {
 672 	case PROP_DB:
 673 		view->priv->db = g_value_get_object (value);
 674 		break;
 675 	case PROP_SHELL_PLAYER:
 676 		rb_entry_view_set_shell_player_internal (view, g_value_get_object (value));
 677 		break;
 678 	case PROP_SORT_ORDER:
 679 		rb_entry_view_set_sorting_type (view, g_value_get_string (value));
 680 		break;
 681 	case PROP_MODEL:
 682 		rb_entry_view_set_model_internal (view, g_value_get_object (value));
 683 		break;
 684 	case PROP_IS_DRAG_SOURCE:
 685 		view->priv->is_drag_source = g_value_get_boolean (value);
 686 		break;
 687 	case PROP_IS_DRAG_DEST:
 688 		view->priv->is_drag_dest = g_value_get_boolean (value);
 689 		break;
 690 	case PROP_PLAYING_STATE:
 691 		view->priv->playing_state = g_value_get_int (value);
 692 
 693 		/* redraw the playing entry, as the icon will have changed */
 694 		if (view->priv->playing_entry != NULL) {
 695 			rb_entry_view_emit_row_changed (view, view->priv->playing_entry);
 696 		}
 697 		break;
 698 	case PROP_VISIBLE_COLUMNS:
 699 		g_strfreev (view->priv->visible_columns);
 700 		view->priv->visible_columns = g_value_dup_boxed (value);
 701 		rb_entry_view_sync_columns_visible (view);
 702 		break;
 703 	default:
 704 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 705 		break;
 706 	}
 707 }
 708 
 709 static void
 710 rb_entry_view_get_property (GObject *object,
 711 			    guint prop_id,
 712 			    GValue *value,
 713 			    GParamSpec *pspec)
 714 {
 715 	RBEntryView *view = RB_ENTRY_VIEW (object);
 716 
 717 	switch (prop_id) {
 718 	case PROP_DB:
 719 		g_value_set_object (value, view->priv->db);
 720 		break;
 721 	case PROP_SHELL_PLAYER:
 722 		g_value_set_object (value, view->priv->shell_player);
 723 		break;
 724 	case PROP_SORT_ORDER:
 725 		g_value_take_string (value, rb_entry_view_get_sorting_type (view));
 726 		break;
 727 	case PROP_MODEL:
 728 		g_value_set_object (value, view->priv->model);
 729 		break;
 730 	case PROP_IS_DRAG_SOURCE:
 731 		g_value_set_boolean (value, view->priv->is_drag_source);
 732 		break;
 733 	case PROP_IS_DRAG_DEST:
 734 		g_value_set_boolean (value, view->priv->is_drag_dest);
 735 		break;
 736 	case PROP_PLAYING_STATE:
 737 		g_value_set_int (value, view->priv->playing_state);
 738 		break;
 739 	case PROP_VISIBLE_COLUMNS:
 740 		g_value_set_boxed (value, view->priv->visible_columns);
 741 		break;
 742 	default:
 743 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 744 		break;
 745 	}
 746 }
 747 
 748 /**
 749  * rb_entry_view_new:
 750  * @db: the #RhythmDB instance
 751  * @shell_player: the #RBShellPlayer instance
 752  * @is_drag_source: if TRUE, the view should act as a drag and drop data source
 753  * @is_drag_dest: if TRUE, the view should act as a drag and drop destination
 754  *
 755  * Creates a new entry view.  If it makes sense to allow the user to drag entries
 756  * from this entry view to other sources, @is_drag_source should be TRUE.  If it
 757  * makes sense to allow the user to drag entries from other sources to this view,
 758  * @is_drag_dest should be TRUE.  Drag and drop in this sense is used for two purposes:
 759  * to transfer tracks between the filesystem and removable devices, and to add tracks
 760  * to playlists.
 761  *
 762  * Return value: the new entry view
 763  */
 764 RBEntryView *
 765 rb_entry_view_new (RhythmDB *db,
 766 		   GObject *shell_player,
 767 		   gboolean is_drag_source,
 768 		   gboolean is_drag_dest)
 769 {
 770 	RBEntryView *view;
 771 
 772 	view = RB_ENTRY_VIEW (g_object_new (RB_TYPE_ENTRY_VIEW,
 773 					   "hadjustment", NULL,
 774 					   "vadjustment", NULL,
 775 					   "hscrollbar_policy", GTK_POLICY_AUTOMATIC,
 776 					   "vscrollbar_policy", GTK_POLICY_AUTOMATIC,
 777 					   "hexpand", TRUE,
 778 					   "vexpand", TRUE,
 779 					   "shadow_type", GTK_SHADOW_IN,
 780 					   "db", db,
 781 					   "shell-player", RB_SHELL_PLAYER (shell_player),
 782 					   "is-drag-source", is_drag_source,
 783 					   "is-drag-dest", is_drag_dest,
 784 					   NULL));
 785 
 786 	g_return_val_if_fail (view->priv != NULL, NULL);
 787 
 788 	return view;
 789 }
 790 
 791 /**
 792  * rb_entry_view_set_model:
 793  * @view: the #RBEntryView
 794  * @model: the new #RhythmDBQueryModel to use for the view
 795  *
 796  * Replaces the model backing the entry view.
 797  */
 798 void
 799 rb_entry_view_set_model (RBEntryView *view,
 800 			 RhythmDBQueryModel *model)
 801 {
 802 	g_object_set (view, "model", model, NULL);
 803 }
 804 
 805 /* Sweet name, eh? */
 806 struct RBEntryViewCellDataFuncData {
 807 	RBEntryView *view;
 808 	RhythmDBPropType propid;
 809 };
 810 
 811 static void
 812 rb_entry_view_playing_cell_data_func (GtkTreeViewColumn *column,
 813 				      GtkCellRenderer *renderer,
 814 				      GtkTreeModel *tree_model,
 815 				      GtkTreeIter *iter,
 816 				      RBEntryView *view)
 817 {
 818 	RhythmDBEntry *entry;
 819 	const char *name = NULL;
 820 
 821 	entry = rhythmdb_query_model_iter_to_entry (view->priv->model, iter);
 822 
 823 	if (entry == NULL) {
 824 		return;
 825 	}
 826 
 827 	if (entry == view->priv->playing_entry) {
 828 		switch (view->priv->playing_state) {
 829 		case RB_ENTRY_VIEW_PLAYING:
 830 			name = "media-playback-start-symbolic";
 831 			break;
 832 		case RB_ENTRY_VIEW_PAUSED:
 833 			name = "media-playback-pause-symbolic";
 834 			break;
 835 		default:
 836 			name = NULL;
 837 			break;
 838 		}
 839 	}
 840 
 841 	if (name == NULL && rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_PLAYBACK_ERROR)) {
 842 		name = "dialog-error-symbolic";
 843 	}
 844 
 845 	g_object_set (renderer, "icon-name", name, NULL);
 846 
 847 	rhythmdb_entry_unref (entry);
 848 }
 849 
 850 static void
 851 rb_entry_view_rating_cell_data_func (GtkTreeViewColumn *column,
 852 				     GtkCellRenderer *renderer,
 853 				     GtkTreeModel *tree_model,
 854 				     GtkTreeIter *iter,
 855 				     RBEntryView *view)
 856 {
 857 	RhythmDBEntry *entry;
 858 
 859 	entry = rhythmdb_query_model_iter_to_entry (view->priv->model, iter);
 860 
 861 	g_object_set (renderer,
 862 		      "rating", rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_RATING),
 863 		      NULL);
 864 
 865 	rhythmdb_entry_unref (entry);
 866 }
 867 
 868 static void
 869 rb_entry_view_bpm_cell_data_func (GtkTreeViewColumn *column,
 870 				   GtkCellRenderer *renderer,
 871 				   GtkTreeModel *tree_model,
 872 				   GtkTreeIter *iter,
 873 				   struct RBEntryViewCellDataFuncData *data)
 874 {
 875 	RhythmDBEntry *entry;
 876 	char *str;
 877 	gdouble val;
 878 
 879 	entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
 880 
 881 	val = rhythmdb_entry_get_double (entry, data->propid);
 882 
 883 	if (val > 0.001)
 884 		str = g_strdup_printf ("%.2f", val);
 885 	else
 886 		str = g_strdup ("");
 887 
 888 	g_object_set (renderer, "text", str, NULL);
 889 	g_free (str);
 890 	rhythmdb_entry_unref (entry);
 891 }
 892 
 893 static void
 894 rb_entry_view_long_cell_data_func (GtkTreeViewColumn *column,
 895 				   GtkCellRenderer *renderer,
 896 				   GtkTreeModel *tree_model,
 897 				   GtkTreeIter *iter,
 898 				   struct RBEntryViewCellDataFuncData *data)
 899 {
 900 	RhythmDBEntry *entry;
 901 	char *str;
 902 	gulong val;
 903 
 904 	entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
 905 
 906 	val = rhythmdb_entry_get_ulong (entry, data->propid);
 907 
 908 	if (val > 0)
 909 		str = g_strdup_printf ("%lu", val);
 910 	else
 911 		str = g_strdup ("");
 912 
 913 	g_object_set (renderer, "text", str, NULL);
 914 	g_free (str);
 915 	rhythmdb_entry_unref (entry);
 916 }
 917 
 918 static void
 919 rb_entry_view_play_count_cell_data_func (GtkTreeViewColumn *column,
 920 					 GtkCellRenderer *renderer,
 921 					 GtkTreeModel *tree_model,
 922 					 GtkTreeIter * iter,
 923 					 struct RBEntryViewCellDataFuncData *data)
 924 {
 925 	RhythmDBEntry *entry;
 926 	gulong i;
 927 	char *str;
 928 
 929 	entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
 930 
 931 	i = rhythmdb_entry_get_ulong (entry, data->propid);
 932 	if (i == 0)
 933 		str = _("Never");
 934 	else
 935 		str = g_strdup_printf ("%ld", i);
 936 
 937 	g_object_set (renderer, "text", str, NULL);
 938 	if (i != 0)
 939 		g_free (str);
 940 
 941 	rhythmdb_entry_unref (entry);
 942 }
 943 
 944 static void
 945 rb_entry_view_duration_cell_data_func (GtkTreeViewColumn *column,
 946 				       GtkCellRenderer *renderer,
 947 				       GtkTreeModel *tree_model,
 948 				       GtkTreeIter *iter,
 949 				       struct RBEntryViewCellDataFuncData *data)
 950 {
 951 	RhythmDBEntry *entry;
 952 	gulong duration;
 953 	char *str;
 954 
 955 	entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
 956 	duration = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
 957 
 958 	str = rb_make_duration_string (duration);
 959 	g_object_set (renderer, "text", str, NULL);
 960 	g_free (str);
 961 	rhythmdb_entry_unref (entry);
 962 }
 963 
 964 static void
 965 rb_entry_view_year_cell_data_func (GtkTreeViewColumn *column,
 966 				   GtkCellRenderer *renderer,
 967 				   GtkTreeModel *tree_model,
 968 				   GtkTreeIter *iter,
 969 				   struct RBEntryViewCellDataFuncData *data)
 970 {
 971 	RhythmDBEntry *entry;
 972 	char str[255];
 973 	int julian;
 974 	GDate *date;
 975 
 976 	entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
 977 	julian = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DATE);
 978 
 979 	if (julian > 0) {
 980 		date = g_date_new_julian (julian);
 981 		g_date_strftime (str, sizeof (str), "%Y", date);
 982 		g_object_set (renderer, "text", str, NULL);
 983 		g_date_free (date);
 984 	} else {
 985 		g_object_set (renderer, "text", _("Unknown"), NULL);
 986 	}
 987 
 988 	rhythmdb_entry_unref (entry);
 989 }
 990 
 991 static void
 992 rb_entry_view_quality_cell_data_func (GtkTreeViewColumn *column,
 993 				      GtkCellRenderer *renderer,
 994 				      GtkTreeModel *tree_model,
 995 				      GtkTreeIter *iter,
 996 				      struct RBEntryViewCellDataFuncData *data)
 997 {
 998 	RhythmDBEntry *entry;
 999 	gulong bitrate;
1000 
1001 	entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
1002 	bitrate = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_BITRATE);
1003 
1004 	if (rhythmdb_entry_is_lossless (entry)) {
1005 		g_object_set (renderer, "text", _("Lossless"), NULL);
1006 	} else if (bitrate == 0) {
1007 		g_object_set (renderer, "text", _("Unknown"), NULL);
1008 	} else {
1009 		char *s;
1010 	       
1011 		s = g_strdup_printf (_("%lu kbps"), bitrate);
1012 		g_object_set (renderer, "text", s, NULL);
1013 		g_free (s);
1014 	}
1015 
1016 	rhythmdb_entry_unref (entry);
1017 }
1018 
1019 static void
1020 rb_entry_view_location_cell_data_func (GtkTreeViewColumn *column,
1021 				       GtkCellRenderer *renderer,
1022 				       GtkTreeModel *tree_model,
1023 				       GtkTreeIter *iter,
1024 				       struct RBEntryViewCellDataFuncData *data)
1025 {
1026 	RhythmDBEntry *entry;
1027 	const char *location;
1028 	char *str;
1029 
1030 	entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
1031 
1032 	location = rhythmdb_entry_get_string (entry, data->propid);
1033 	str = g_uri_unescape_string (location, NULL);
1034 
1035 	g_object_set (renderer, "text", str, NULL);
1036 	g_free (str);
1037 
1038 	rhythmdb_entry_unref (entry);
1039 }
1040 
1041 static void
1042 rb_entry_view_string_cell_data_func (GtkTreeViewColumn *column,
1043 				     GtkCellRenderer *renderer,
1044 				     GtkTreeModel *tree_model,
1045 				     GtkTreeIter *iter,
1046 				     struct RBEntryViewCellDataFuncData *data)
1047 {
1048 	RhythmDBEntry *entry;
1049 	const char *str;
1050 
1051 	entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
1052 
1053 	str = rhythmdb_entry_get_string (entry, data->propid);
1054 	if (str != NULL) {
1055 		g_object_set (renderer, "text", str, NULL);
1056 	}
1057 
1058 	rhythmdb_entry_unref (entry);
1059 }
1060 
1061 static void
1062 rb_entry_view_sync_sorting (RBEntryView *view)
1063 {
1064 	GtkTreeViewColumn *column;
1065 	gint direction;
1066 	char *column_name;
1067 	RhythmDBPropType type_ahead_propid;
1068 	GList *renderers;
1069 
1070 	direction = GTK_SORT_ASCENDING;
1071 	column_name = NULL;
1072 	rb_entry_view_get_sorting_order (view, &column_name, &direction);
1073 
1074 	if (column_name == NULL) {
1075 		return;
1076 	}
1077 
1078 	column = g_hash_table_lookup (view->priv->column_key_map, column_name);
1079 	if (column == NULL) {
1080 		rb_debug ("couldn't find column %s", column_name);
1081 		g_free (column_name);
1082 		return;
1083 	}
1084 
1085 	rb_debug ("Updating EntryView sort order to %s:%d", column_name, direction);
1086 
1087 	/* remove the old sorting indicator */
1088 	if (view->priv->sorting_column)
1089 		gtk_tree_view_column_set_sort_indicator (view->priv->sorting_column, FALSE);
1090 
1091 	/* set the sorting order and indicator of the new sorting column */
1092 	view->priv->sorting_column = column;
1093 	gtk_tree_view_column_set_sort_indicator (column, TRUE);
1094 	gtk_tree_view_column_set_sort_order (column, direction);
1095 
1096 	/* set the property id to use for the typeahead search */
1097 	renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
1098 	type_ahead_propid = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (renderers->data), CELL_PROPID_ITEM));
1099 	g_list_free (renderers);
1100 	if (type_ahead_propid != 0 && rhythmdb_get_property_type (view->priv->db, type_ahead_propid) == G_TYPE_STRING)
1101 		view->priv->type_ahead_propid = type_ahead_propid;
1102 	else
1103 		view->priv->type_ahead_propid = RHYTHMDB_PROP_TITLE;
1104 
1105 	g_free (column_name);
1106 }
1107 
1108 /**
1109  * rb_entry_view_get_sorting_type:
1110  * @view: an #RBEntryView
1111  *
1112  * Constructs a string that describes the sort settings for the entry view.
1113  * This consists of a column name and an order ('ascending' or 'descending')
1114  * separated by a comma.
1115  *
1116  * Return value: (transfer full): sort order description
1117  */
1118 char *
1119 rb_entry_view_get_sorting_type (RBEntryView *view)
1120 {
1121 	char *sorttype;
1122 	GString *key = g_string_new (view->priv->sorting_column_name);
1123 
1124 	g_string_append_c (key, ',');
1125 
1126 	switch (view->priv->sorting_order)
1127 	{
1128 	case GTK_SORT_ASCENDING:
1129 		g_string_append (key, "ascending");
1130 		break;
1131 	case GTK_SORT_DESCENDING:
1132 		g_string_append (key, "descending");
1133 		break;
1134 	default:
1135 		g_assert_not_reached ();
1136 	}
1137 
1138 	sorttype = key->str;
1139 	g_string_free (key, FALSE);
1140 
1141 	return sorttype;
1142 }
1143 
1144 /**
1145  * rb_entry_view_set_sorting_type:
1146  * @view: a #RBEntryView
1147  * @sorttype: sort order description
1148  *
1149  * Changes the sort order for the entry view.  The sort order
1150  * description must be a column name, followed by a comma, followed
1151  * by an order description ('ascending' or 'descending').
1152  */
1153 void
1154 rb_entry_view_set_sorting_type (RBEntryView *view,
1155 				const char *sorttype)
1156 {
1157 	char **strs;
1158 
1159 	if (!sorttype || !strchr (sorttype, ',')) {
1160 		rb_debug ("malformed sort data: %s", (sorttype) ? sorttype : "(null)");
1161 		return;
1162 	}
1163 
1164 	strs = g_strsplit (sorttype, ",", 0);
1165 
1166 	g_free (view->priv->sorting_column_name);
1167 	view->priv->sorting_column_name = g_strdup(strs[0]);
1168 
1169 	if (!strcmp ("ascending", strs[1]))
1170 		view->priv->sorting_order = GTK_SORT_ASCENDING;
1171 	else if (!strcmp ("descending", strs[1]))
1172 		view->priv->sorting_order = GTK_SORT_DESCENDING;
1173 	else {
1174 		g_warning ("atttempting to sort in unknown direction");
1175 		view->priv->sorting_order = GTK_SORT_ASCENDING;
1176 	}
1177 
1178 	g_strfreev (strs);
1179 
1180 	rb_entry_view_sync_sorting (view);
1181 	g_object_notify (G_OBJECT (view), "sort-order");
1182 }
1183 
1184 /**
1185  * rb_entry_view_get_sorting_order:
1186  * @view: a #RBEntryView
1187  * @column_name: (out callee-allocates) (allow-none) (transfer full): returns the sort column name
1188  * @sort_order: (out) (allow-none) returns the sort ordering as a #GtkSortType value
1189  *
1190  * Retrieves the sort settings for the view.
1191  */
1192 void
1193 rb_entry_view_get_sorting_order (RBEntryView *view,
1194 				 char **column_name,
1195 				 gint *sort_order)
1196 {
1197  	g_return_if_fail (RB_IS_ENTRY_VIEW (view));
1198 
1199 	if (column_name != NULL) {
1200 		*column_name = g_strdup (view->priv->sorting_column_name);
1201 	}
1202 
1203 	if (sort_order != NULL) {
1204 		*sort_order = view->priv->sorting_order;
1205 	}
1206 }
1207 
1208 /**
1209  * rb_entry_view_set_sorting_order:
1210  * @view: a #RBEntryView
1211  * @column_name: name of the column to sort on
1212  * @sort_order: order to sort in, as a #GtkSortType
1213  *
1214  * Sets the sort order for the entry view.
1215  */
1216 void
1217 rb_entry_view_set_sorting_order (RBEntryView *view,
1218 				 const char *column_name,
1219 				 gint sort_order)
1220 {
1221 	if (column_name == NULL)
1222 		return;
1223 
1224 	g_free (view->priv->sorting_column_name);
1225 	view->priv->sorting_column_name = g_strdup (column_name);
1226 	view->priv->sorting_order = sort_order;
1227 
1228 	rb_entry_view_sync_sorting (view);
1229 	g_object_notify (G_OBJECT (view), "sort-order");
1230 }
1231 
1232 static void
1233 rb_entry_view_column_clicked_cb (GtkTreeViewColumn *column, RBEntryView *view)
1234 {
1235 	gint sort_order;
1236 	char *clicked_column;
1237 
1238 	rb_debug ("sorting on column %p", column);
1239 
1240 	/* identify the clicked column, and then update the sorting order */
1241 	clicked_column = (char*) g_object_get_data (G_OBJECT (column), "rb-entry-view-key");
1242 	sort_order = view->priv->sorting_order;
1243 
1244 	if (view->priv->sorting_column_name
1245 	    && !strcmp(clicked_column, view->priv->sorting_column_name)
1246 	    && (sort_order == GTK_SORT_ASCENDING))
1247 		sort_order = GTK_SORT_DESCENDING;
1248 	else
1249 		sort_order = GTK_SORT_ASCENDING;
1250 
1251 	rb_entry_view_set_sorting_order (view, clicked_column, sort_order);
1252 }
1253 
1254 /**
1255  * rb_entry_view_get_column:
1256  * @view: a #RBEntryView
1257  * @coltype: type of column to retrieve
1258  *
1259  * Retrieves a predefined column from the entry view.  This can be used
1260  * to insert additional cell renderers into the column.
1261  *
1262  * Return value: (transfer none): a #GtkTreeViewColumn instance, or NULL
1263  */
1264 GtkTreeViewColumn *
1265 rb_entry_view_get_column (RBEntryView *view, RBEntryViewColumn coltype)
1266 {
1267 	RhythmDBPropType propid;
1268 
1269 	/* convert column type to property ID */
1270 	switch (coltype) {
1271 	case RB_ENTRY_VIEW_COL_TRACK_NUMBER:
1272 		propid = RHYTHMDB_PROP_TRACK_NUMBER;
1273 		break;
1274 	case RB_ENTRY_VIEW_COL_TITLE:
1275 		propid = RHYTHMDB_PROP_TITLE;
1276 		break;
1277 	case RB_ENTRY_VIEW_COL_ARTIST:
1278 		propid = RHYTHMDB_PROP_ARTIST;
1279 		break;
1280 	case RB_ENTRY_VIEW_COL_ALBUM:
1281 		propid = RHYTHMDB_PROP_ALBUM;
1282 		break;
1283 	case RB_ENTRY_VIEW_COL_GENRE:
1284 		propid = RHYTHMDB_PROP_GENRE;
1285 		break;
1286 	case RB_ENTRY_VIEW_COL_COMMENT:
1287 		propid = RHYTHMDB_PROP_COMMENT;
1288 		break;
1289 	case RB_ENTRY_VIEW_COL_DURATION:
1290 		propid = RHYTHMDB_PROP_DURATION;
1291 		break;
1292 	case RB_ENTRY_VIEW_COL_YEAR:
1293 		propid = RHYTHMDB_PROP_DATE;
1294 		break;
1295 	case RB_ENTRY_VIEW_COL_QUALITY:
1296 		propid = RHYTHMDB_PROP_BITRATE;
1297 		break;
1298 	case RB_ENTRY_VIEW_COL_RATING:
1299 		propid = RHYTHMDB_PROP_RATING;
1300 		break;
1301 	case RB_ENTRY_VIEW_COL_PLAY_COUNT:
1302 		propid = RHYTHMDB_PROP_PLAY_COUNT;
1303 		break;
1304 	case RB_ENTRY_VIEW_COL_LAST_PLAYED:
1305 		propid = RHYTHMDB_PROP_LAST_PLAYED;
1306 		break;
1307 	case RB_ENTRY_VIEW_COL_FIRST_SEEN:
1308 		propid = RHYTHMDB_PROP_FIRST_SEEN;
1309 		break;
1310 	case RB_ENTRY_VIEW_COL_LAST_SEEN:
1311 		propid = RHYTHMDB_PROP_LAST_SEEN;
1312 		break;
1313 	case RB_ENTRY_VIEW_COL_LOCATION:
1314 		propid = RHYTHMDB_PROP_LOCATION;
1315 		break;
1316 	case RB_ENTRY_VIEW_COL_ERROR:
1317 		propid = RHYTHMDB_PROP_PLAYBACK_ERROR;
1318 		break;
1319 	default:
1320 		g_assert_not_reached ();
1321 		propid = -1;
1322 		break;
1323 	}
1324 
1325 	/* find the column */
1326 	return (GtkTreeViewColumn *)g_hash_table_lookup (view->priv->propid_column_map, GINT_TO_POINTER (propid));
1327 }
1328 
1329 static void
1330 rb_entry_view_cell_edited_cb (GtkCellRendererText *renderer,
1331 			      char *path_str,
1332 			      char *new_text,
1333 			      RBEntryView *view)
1334 {
1335 	RhythmDBPropType propid;
1336 	RhythmDBEntry *entry;
1337 	GValue value = {0,};
1338 	GtkTreePath *path;
1339 
1340 	/* get the property corresponding to the cell, filter out properties we can't edit */
1341 	propid = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (renderer), CELL_PROPID_ITEM));
1342 	switch (propid) {
1343 	case RHYTHMDB_PROP_TITLE:
1344 	case RHYTHMDB_PROP_GENRE:
1345 	case RHYTHMDB_PROP_ARTIST:
1346 	case RHYTHMDB_PROP_ALBUM:
1347 	case RHYTHMDB_PROP_COMMENT:
1348 	case RHYTHMDB_PROP_ARTIST_SORTNAME:
1349 	case RHYTHMDB_PROP_ALBUM_SORTNAME:
1350 		break;
1351 
1352 	default:
1353 		rb_debug ("can't edit property %s", rhythmdb_nice_elt_name_from_propid (view->priv->db, propid));
1354 		return;
1355 	}
1356 
1357 	/* find entry */
1358 	path = gtk_tree_path_new_from_string (path_str);
1359 	entry = rhythmdb_query_model_tree_path_to_entry (view->priv->model, path);
1360 	gtk_tree_path_free (path);
1361 
1362 	if (entry != NULL) {
1363 		/* update it */
1364 		g_value_init (&value, G_TYPE_STRING);
1365 		g_value_set_string (&value, new_text);
1366 		rhythmdb_entry_set (view->priv->db, entry, propid, &value);
1367 		g_value_unset (&value);
1368 
1369 		rhythmdb_commit (view->priv->db);
1370 		rhythmdb_entry_unref (entry);
1371 	}
1372 }
1373 
1374 
1375 /**
1376  * rb_entry_view_append_column:
1377  * @view: a #RBEntryView
1378  * @coltype: type of column to append
1379  * @always_visible: if TRUE, ignore the user's column visibility settings
1380  *
1381  * Appends a predefined column type to the set of columns already present
1382  * in the entry view.  If @always_visible is TRUE, the column will ignore
1383  * the user's coulmn visibility settings and will always be visible.
1384  * This should only be used when it is vital for the purpose of the 
1385  * source that the column be visible.
1386  */
1387 void
1388 rb_entry_view_append_column (RBEntryView *view,
1389 			     RBEntryViewColumn coltype,
1390 			     gboolean always_visible)
1391 {
1392 	GtkTreeViewColumn *column;
1393 	GtkCellRenderer *renderer = NULL;
1394 	struct RBEntryViewCellDataFuncData *cell_data;
1395 	const char *title = NULL;
1396 	const char *key = NULL;
1397 	const char *strings[5] = {0};
1398 	GtkTreeCellDataFunc cell_data_func = NULL;
1399 	GCompareDataFunc sort_func = NULL;
1400 	RhythmDBPropType propid;
1401 	RhythmDBPropType sort_propid = RHYTHMDB_NUM_PROPERTIES;
1402 	gboolean ellipsize = FALSE;
1403 	gboolean resizable = TRUE;
1404 	gint column_width = -1;
1405 
1406 	column = gtk_tree_view_column_new ();
1407 
1408 	cell_data = g_new0 (struct RBEntryViewCellDataFuncData, 1);
1409 	cell_data->view = view;
1410 
1411 	switch (coltype) {
1412 	case RB_ENTRY_VIEW_COL_TRACK_NUMBER:
1413 		propid = RHYTHMDB_PROP_TRACK_NUMBER;
1414 		cell_data->propid = propid;
1415 		cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_long_cell_data_func;
1416 		sort_func = (GCompareDataFunc) rhythmdb_query_model_track_sort_func;
1417 		title = _("Track");
1418 		key = "Track";
1419 		strings[0] = title;
1420 		strings[1] = "9999";
1421 		break;
1422 	case RB_ENTRY_VIEW_COL_TITLE:
1423 		propid = RHYTHMDB_PROP_TITLE;
1424 		cell_data->propid = propid;
1425 		cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1426 		sort_propid = RHYTHMDB_PROP_TITLE_SORT_KEY;
1427 		sort_func = (GCompareDataFunc) rhythmdb_query_model_string_sort_func;
1428 		title = _("Title");
1429 		key = "Title";
1430 		ellipsize = TRUE;
1431 		break;
1432 	case RB_ENTRY_VIEW_COL_ARTIST:
1433 		propid = RHYTHMDB_PROP_ARTIST;
1434 		cell_data->propid = propid;
1435 		cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1436 		sort_propid = RHYTHMDB_PROP_ARTIST_SORT_KEY;
1437 		sort_func = (GCompareDataFunc) rhythmdb_query_model_artist_sort_func;
1438 		title = _("Artist");
1439 		key = "Artist";
1440 		ellipsize = TRUE;
1441 		break;
1442 	case RB_ENTRY_VIEW_COL_ALBUM:
1443 		propid = RHYTHMDB_PROP_ALBUM;
1444 		cell_data->propid = propid;
1445 		cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1446 		sort_propid = RHYTHMDB_PROP_ALBUM_SORT_KEY;
1447 		sort_func = (GCompareDataFunc) rhythmdb_query_model_album_sort_func;
1448 		title = _("Album");
1449 		key = "Album";
1450 		ellipsize = TRUE;
1451 		break;
1452 	case RB_ENTRY_VIEW_COL_GENRE:
1453 		propid = RHYTHMDB_PROP_GENRE;
1454 		cell_data->propid = propid;
1455 		cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1456 		sort_propid = RHYTHMDB_PROP_GENRE_SORT_KEY;
1457 		sort_func = (GCompareDataFunc) rhythmdb_query_model_genre_sort_func;
1458 		title = _("Genre");
1459 		key = "Genre";
1460 		ellipsize = TRUE;
1461 		break;
1462 	case RB_ENTRY_VIEW_COL_COMMENT:
1463 		propid = RHYTHMDB_PROP_COMMENT;
1464 		cell_data->propid = propid;
1465 		cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1466 		sort_propid = cell_data->propid;
1467 		sort_func = (GCompareDataFunc) rhythmdb_query_model_string_sort_func;
1468 		title = _("Comment");
1469 		key = "Comment";
1470 		ellipsize = TRUE;
1471 		break;
1472 	case RB_ENTRY_VIEW_COL_DURATION:
1473 		propid = RHYTHMDB_PROP_DURATION;
1474 		cell_data->propid = propid;
1475 		cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_duration_cell_data_func;
1476 		sort_propid = cell_data->propid;
1477 		sort_func = (GCompareDataFunc) rhythmdb_query_model_ulong_sort_func;
1478 		title = _("Time");
1479 		key = "Time";
1480 		strings[0] = title;
1481 		strings[1] = "000:00";
1482 		strings[2] = _("Unknown");
1483 		break;
1484 	case RB_ENTRY_VIEW_COL_YEAR:
1485 		propid = RHYTHMDB_PROP_DATE;
1486 		cell_data->propid = propid;
1487 		cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_year_cell_data_func;
1488 		sort_propid = cell_data->propid;
1489 		sort_func = (GCompareDataFunc) rhythmdb_query_model_date_sort_func;
1490 		title = _("Year");
1491 		key = "Year";
1492 		strings[0] = title;
1493 		strings[1] = "0000";
1494 		strings[2] = _("Unknown");
1495 		break;
1496 	case RB_ENTRY_VIEW_COL_QUALITY:
1497 		propid = RHYTHMDB_PROP_BITRATE;
1498 		cell_data->propid = propid;
1499 		cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_quality_cell_data_func;
1500 		sort_propid = cell_data->propid;
1501 		sort_func = (GCompareDataFunc) rhythmdb_query_model_bitrate_sort_func;
1502 		title = _("Quality");
1503 		key = "Quality";
1504 		strings[0] = title;
1505 		strings[1] = _("000 kbps");
1506 		strings[2] = _("Unknown");
1507 		strings[3] = _("Lossless");
1508 		break;
1509 	case RB_ENTRY_VIEW_COL_RATING:
1510 		propid = RHYTHMDB_PROP_RATING;
1511 		sort_func = (GCompareDataFunc) rhythmdb_query_model_double_ceiling_sort_func;
1512 
1513 		gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &column_width, NULL);
1514 		column_width = column_width * 5 + 5;
1515 		resizable = FALSE;
1516 		title = _("Rating");
1517 		key = "Rating";
1518 
1519 		renderer = rb_cell_renderer_rating_new ();
1520 		gtk_tree_view_column_pack_start (column, renderer, TRUE);
1521 		gtk_tree_view_column_set_cell_data_func (column, renderer,
1522 							 (GtkTreeCellDataFunc)
1523 							 rb_entry_view_rating_cell_data_func,
1524 							 view,
1525 							 NULL);
1526 		g_signal_connect_object (renderer,
1527 					 "rated",
1528 					 G_CALLBACK (rb_entry_view_rated_cb),
1529 					 view,
1530 					 0);
1531 		break;
1532 	case RB_ENTRY_VIEW_COL_PLAY_COUNT:
1533 		propid = RHYTHMDB_PROP_PLAY_COUNT;
1534 		cell_data->propid = propid;
1535 		cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_play_count_cell_data_func;
1536 		sort_propid = cell_data->propid;
1537 		sort_func = (GCompareDataFunc) rhythmdb_query_model_ulong_sort_func;
1538 		title = _("Play Count");
1539 		key = "PlayCount";
1540 		strings[0] = title;
1541 		strings[1] = _("Never");
1542 		strings[2] = "9999";
1543 		break;
1544 	case RB_ENTRY_VIEW_COL_LAST_PLAYED:
1545 		propid = RHYTHMDB_PROP_LAST_PLAYED;
1546 		cell_data->propid = RHYTHMDB_PROP_LAST_PLAYED_STR;
1547 		cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1548 		sort_propid = RHYTHMDB_PROP_LAST_PLAYED;
1549 		sort_func = (GCompareDataFunc) rhythmdb_query_model_ulong_sort_func;
1550 		title = _("Last Played");
1551 		key = "LastPlayed";
1552 		strings[0] = title;
1553 		strings[1] = rb_entry_view_get_time_date_column_sample ();
1554 		strings[2] = _("Never");
1555 		break;
1556 	case RB_ENTRY_VIEW_COL_FIRST_SEEN:
1557 		propid = RHYTHMDB_PROP_FIRST_SEEN;
1558 		cell_data->propid = RHYTHMDB_PROP_FIRST_SEEN_STR;
1559 		cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1560 		sort_propid = RHYTHMDB_PROP_FIRST_SEEN;
1561 		sort_func = (GCompareDataFunc) rhythmdb_query_model_ulong_sort_func;
1562 		title = _("Date Added");
1563 		key = "FirstSeen";
1564 		strings[0] = title;
1565 		strings[1] = rb_entry_view_get_time_date_column_sample ();
1566 		break;
1567 	case RB_ENTRY_VIEW_COL_LAST_SEEN:
1568 		propid = RHYTHMDB_PROP_LAST_SEEN;
1569 		cell_data->propid = RHYTHMDB_PROP_LAST_SEEN_STR;
1570 		cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1571 		sort_propid = RHYTHMDB_PROP_LAST_SEEN;
1572 		sort_func = (GCompareDataFunc) rhythmdb_query_model_ulong_sort_func;
1573 		title = _("Last Seen");
1574 		key = "LastSeen";
1575 		strings[0] = title;
1576 		strings[1] = rb_entry_view_get_time_date_column_sample ();
1577 		break;
1578 	case RB_ENTRY_VIEW_COL_LOCATION:
1579 		propid = RHYTHMDB_PROP_LOCATION;
1580 		cell_data->propid = RHYTHMDB_PROP_LOCATION;
1581 		cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_location_cell_data_func;
1582 		sort_propid = RHYTHMDB_PROP_LOCATION;
1583 		sort_func = (GCompareDataFunc) rhythmdb_query_model_location_sort_func;
1584 		title = _("Location");
1585 		key = "Location";
1586 		ellipsize = TRUE;
1587 		break;
1588 	case RB_ENTRY_VIEW_COL_BPM:
1589 		propid = RHYTHMDB_PROP_BPM;
1590 		cell_data->propid = propid;
1591 		cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_bpm_cell_data_func;
1592 		sort_func = (GCompareDataFunc) rhythmdb_query_model_double_ceiling_sort_func;
1593 		title = _("BPM");
1594 		key = "BPM";
1595 		strings[0] = title;
1596 		strings[1] = "999.99";
1597 		break;
1598 	case RB_ENTRY_VIEW_COL_ERROR:
1599 		propid = RHYTHMDB_PROP_PLAYBACK_ERROR;
1600 		cell_data->propid = RHYTHMDB_PROP_PLAYBACK_ERROR;
1601 		cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1602 		title = _("Error");
1603 		key = "Error";
1604 		ellipsize = TRUE;
1605 		break;
1606 	default:
1607 		g_assert_not_reached ();
1608 		propid = -1;
1609 		break;
1610 	}
1611 
1612 	if (sort_propid == RHYTHMDB_NUM_PROPERTIES)
1613 		sort_propid = propid;
1614 
1615 	if (renderer == NULL) {
1616 		renderer = gtk_cell_renderer_text_new ();
1617 		gtk_tree_view_column_pack_start (column, renderer, TRUE);
1618 		gtk_tree_view_column_set_cell_data_func (column, renderer,
1619 							 cell_data_func, cell_data, g_free);
1620 
1621 		g_object_set_data (G_OBJECT (renderer), CELL_PROPID_ITEM, GINT_TO_POINTER (propid));
1622 		g_signal_connect_object (renderer, "edited",
1623 					 G_CALLBACK (rb_entry_view_cell_edited_cb),
1624 					 view, 0);
1625 		g_object_set (renderer, "single-paragraph-mode", TRUE, NULL);
1626 	} else {
1627 		g_free (cell_data);
1628 	}
1629 
1630 	/*
1631 	 * Columns must either be expanding (ellipsized) or have a
1632 	 * fixed minimum width specified.  Otherwise, gtk+ gives them a
1633 	 * width of 0.
1634 	 */
1635 	if (ellipsize) {
1636 		g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1637 		gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column), TRUE);
1638 	} else if (column_width != -1) {
1639 		gtk_tree_view_column_set_fixed_width (column, column_width);
1640 	} else {
1641 		rb_entry_view_set_fixed_column_width (view, column, renderer, strings);
1642 	}
1643 
1644 	if (resizable)
1645 		gtk_tree_view_column_set_resizable (column, TRUE);
1646 
1647 	gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
1648 	gtk_tree_view_column_set_clickable (column, TRUE);
1649 
1650 	if (always_visible)
1651 		g_object_set_qdata (G_OBJECT (column),
1652 				    rb_entry_view_column_always_visible,
1653 				    GINT_TO_POINTER (1));
1654 
1655 	g_hash_table_insert (view->priv->propid_column_map, GINT_TO_POINTER (propid), column);
1656 
1657 	rb_entry_view_append_column_custom (view, column, title, key, sort_func, GINT_TO_POINTER (sort_propid), NULL);
1658 }
1659 
1660 /**
1661  * rb_entry_view_append_column_custom:
1662  * @view: a #RBEntryView
1663  * @column: (transfer full): a #GtkTreeViewColumn to append
1664  * @title: title for the column (translated)
1665  * @key: sort key for the column (not translated)
1666  * @sort_func: comparison function to use for sorting on the column
1667  * @data: (closure) (scope notified): data to pass to the sort function
1668  * @data_destroy: function to use to destroy the sort data
1669  *
1670  * Appends a custom column to the entry view.  
1671  */
1672 void
1673 rb_entry_view_append_column_custom (RBEntryView *view,
1674 				    GtkTreeViewColumn *column,
1675 				    const char *title,
1676 				    const char *key,
1677 				    GCompareDataFunc sort_func,
1678 				    gpointer data,
1679 				    GDestroyNotify data_destroy)
1680 {
1681 	rb_entry_view_insert_column_custom (view, column, title, key, sort_func, data, data_destroy, -1);
1682 }
1683 
1684 /**
1685  * rb_entry_view_insert_column_custom:
1686  * @view: a #RBEntryView
1687  * @column: (transfer full): a #GtkTreeViewColumn to append
1688  * @title: title for the column (translated)
1689  * @key: sort key for the column (not translated)
1690  * @sort_func: comparison function to use for sorting on the column
1691  * @data: (closure) (scope notified): data to pass to the sort function
1692  * @data_destroy: function to use to destroy the sort data
1693  * @position: position at which to insert the column (-1 to insert at the end)
1694  *
1695  * Inserts a custom column at the specified position.
1696  */
1697 void
1698 rb_entry_view_insert_column_custom (RBEntryView *view,
1699 				    GtkTreeViewColumn *column,
1700 				    const char *title,
1701 				    const char *key,
1702 				    GCompareDataFunc sort_func,
1703 				    gpointer data,
1704 				    GDestroyNotify data_destroy,
1705 				    gint position)
1706 {
1707 	struct RBEntryViewColumnSortData *sortdata;
1708 
1709 	gtk_tree_view_column_set_title (column, title);
1710 	gtk_tree_view_column_set_reorderable (column, FALSE);
1711 
1712 
1713 	g_object_set_data_full (G_OBJECT (column), "rb-entry-view-key",
1714 				g_strdup (key), g_free);
1715 
1716 	rb_debug ("appending column: %p (title: %s, key: %s)", column, title, key);
1717 
1718 	gtk_tree_view_insert_column (GTK_TREE_VIEW (view->priv->treeview), column, position);
1719 
1720 	if (sort_func != NULL) {
1721 		sortdata = g_new (struct RBEntryViewColumnSortData, 1);
1722 		sortdata->func = (GCompareDataFunc) sort_func;
1723 		sortdata->data = data;
1724 		sortdata->data_destroy = data_destroy;
1725 		g_hash_table_insert (view->priv->column_sort_data_map, column, sortdata);
1726 
1727 		g_signal_connect_object (column, "clicked",
1728 					 G_CALLBACK (rb_entry_view_column_clicked_cb),
1729 					 view, 0);
1730 	}
1731 	g_hash_table_insert (view->priv->column_key_map, g_strdup (key), column);
1732 
1733 	rb_entry_view_sync_columns_visible (view);
1734 	rb_entry_view_sync_sorting (view);
1735 }
1736 
1737 /**
1738  * rb_entry_view_set_columns_clickable:
1739  * @view: a #RBEntryView
1740  * @clickable: if TRUE, sortable columns will be made clickable
1741  *
1742  * Makes the headers for sortable columns (those for which a sort function was
1743  * provided) clickable, so the user can set the sort order.
1744  */
1745 void
1746 rb_entry_view_set_columns_clickable (RBEntryView *view,
1747 				     gboolean clickable)
1748 {
1749 	GList *columns, *tem;
1750 
1751   	columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (view->priv->treeview));
1752 	for (tem = columns; tem; tem = tem->next) {
1753 		/* only columns we can sort on should be clickable */
1754 		GtkTreeViewColumn *column = (GtkTreeViewColumn *) tem->data;
1755 		if (g_hash_table_lookup (view->priv->column_sort_data_map, column) != NULL)
1756 			gtk_tree_view_column_set_clickable (tem->data, clickable);
1757 	}
1758 	g_list_free (columns);
1759 }
1760 
1761 static void
1762 rb_entry_view_constructed (GObject *object)
1763 {
1764 	RBEntryView *view;
1765 
1766 	RB_CHAIN_GOBJECT_METHOD (rb_entry_view_parent_class, constructed, object);
1767 
1768 	view = RB_ENTRY_VIEW (object);
1769 
1770 	view->priv->treeview = GTK_WIDGET (gtk_tree_view_new ());
1771 	gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (view->priv->treeview), TRUE);
1772 
1773 	gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view->priv->treeview),
1774 					     type_ahead_search_func,
1775 					     view, NULL);
1776 
1777 	g_signal_connect_object (view->priv->treeview,
1778 			         "button_press_event",
1779 			         G_CALLBACK (rb_entry_view_button_press_cb),
1780 			         view,
1781 				 0);
1782 	g_signal_connect_object (view->priv->treeview,
1783 			         "row_activated",
1784 			         G_CALLBACK (rb_entry_view_row_activated_cb),
1785 			         view,
1786 				 0);
1787 	g_signal_connect_object (view->priv->treeview,
1788 				 "popup_menu",
1789 				 G_CALLBACK (rb_entry_view_popup_menu_cb),
1790 				 view,
1791 				 0);
1792 	view->priv->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->priv->treeview));
1793 	g_signal_connect_object (view->priv->selection,
1794 			         "changed",
1795 			         G_CALLBACK (rb_entry_view_selection_changed_cb),
1796 			         view,
1797 				 0);
1798 
1799 	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view->priv->treeview), TRUE);
1800 	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view->priv->treeview), TRUE);
1801 	gtk_tree_selection_set_mode (view->priv->selection, GTK_SELECTION_MULTIPLE);
1802 
1803 	if (view->priv->is_drag_source) {
1804 		rb_tree_dnd_add_drag_source_support (GTK_TREE_VIEW (view->priv->treeview),
1805 						     GDK_BUTTON1_MASK,
1806 						     rb_entry_view_drag_types,
1807 						     G_N_ELEMENTS (rb_entry_view_drag_types),
1808 						     GDK_ACTION_COPY);
1809 	}
1810 
1811 	if (view->priv->is_drag_dest) {
1812 		rb_tree_dnd_add_drag_dest_support (GTK_TREE_VIEW (view->priv->treeview),
1813 						   RB_TREE_DEST_CAN_DROP_BETWEEN | RB_TREE_DEST_EMPTY_VIEW_DROP,
1814 						   rb_entry_view_drag_types,
1815 						   G_N_ELEMENTS (rb_entry_view_drag_types),
1816 						   GDK_ACTION_COPY | GDK_ACTION_MOVE);
1817 	}
1818 
1819 	gtk_container_add (GTK_CONTAINER (view), view->priv->treeview);
1820 
1821 	{
1822 		GtkTreeViewColumn *column;
1823 		GtkCellRenderer *renderer;
1824 		GtkWidget *image_widget;
1825 		gint width;
1826 
1827 		/* Playing icon column */
1828 		column = GTK_TREE_VIEW_COLUMN (gtk_tree_view_column_new ());
1829 		renderer = rb_cell_renderer_pixbuf_new ();
1830 		g_object_set (renderer, "stock-size", GTK_ICON_SIZE_MENU, NULL);
1831 		gtk_tree_view_column_pack_start (column, renderer, TRUE);
1832 		gtk_tree_view_column_set_cell_data_func (column, renderer,
1833 							 (GtkTreeCellDataFunc)
1834 							 rb_entry_view_playing_cell_data_func,
1835 							 view,
1836 							 NULL);
1837 
1838 		image_widget = gtk_image_new_from_icon_name ("audio-volume-high", GTK_ICON_SIZE_MENU);
1839 		gtk_tree_view_column_set_widget (column, image_widget);
1840 		gtk_widget_show (image_widget);
1841 
1842 		gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
1843 		gtk_tree_view_column_set_clickable (column, FALSE);
1844 		gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, NULL);
1845 		gtk_tree_view_column_set_fixed_width (column, width + 5);
1846 		gtk_tree_view_append_column (GTK_TREE_VIEW (view->priv->treeview), column);
1847 		g_signal_connect_swapped (renderer,
1848 					  "pixbuf-clicked",
1849 					  G_CALLBACK (rb_entry_view_pixbuf_clicked_cb),
1850 					  view);
1851 
1852 		gtk_widget_set_tooltip_text (gtk_tree_view_column_get_widget (column),
1853 					     _("Now Playing"));
1854 	}
1855 
1856 	{
1857 		RhythmDBQueryModel *query_model;
1858 		query_model = rhythmdb_query_model_new_empty (view->priv->db);
1859 		rb_entry_view_set_model (view, RHYTHMDB_QUERY_MODEL (query_model));
1860 		g_object_unref (query_model);
1861 	}
1862 }
1863 
1864 static void
1865 rb_entry_view_rated_cb (RBCellRendererRating *cellrating,
1866 			const char *path_string,
1867 			double rating,
1868 			RBEntryView *view)
1869 {
1870 	GtkTreePath *path;
1871 	RhythmDBEntry *entry;
1872 	GValue value = { 0, };
1873 
1874 	g_return_if_fail (rating >= 0 && rating <= 5 );
1875 	g_return_if_fail (path_string != NULL);
1876 
1877 	path = gtk_tree_path_new_from_string (path_string);
1878 	entry = rhythmdb_query_model_tree_path_to_entry (view->priv->model, path);
1879 	gtk_tree_path_free (path);
1880 
1881 	g_value_init (&value, G_TYPE_DOUBLE);
1882 	g_value_set_double (&value, rating);
1883 	rhythmdb_entry_set (view->priv->db, entry, RHYTHMDB_PROP_RATING, &value);
1884 	g_value_unset (&value);
1885 
1886 	rhythmdb_commit (view->priv->db);
1887 
1888 	rhythmdb_entry_unref (entry);
1889 }
1890 
1891 static void
1892 rb_entry_view_pixbuf_clicked_cb (RBEntryView          *view,
1893 				 const char           *path_string,
1894 				 RBCellRendererPixbuf *cellpixbuf)
1895 {
1896 	GtkTreePath *path;
1897 	RhythmDBEntry *entry;
1898 	const gchar *error;
1899 
1900 	g_return_if_fail (path_string != NULL);
1901 
1902 	path = gtk_tree_path_new_from_string (path_string);
1903 	entry = rhythmdb_query_model_tree_path_to_entry (view->priv->model, path);
1904 
1905 	gtk_tree_path_free (path);
1906 
1907 	error = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_PLAYBACK_ERROR);
1908 	if (error) {
1909 		rb_error_dialog (NULL, _("Playback Error"), "%s", error);
1910 	}
1911 
1912 	rhythmdb_entry_unref (entry);
1913 }
1914 
1915 static void
1916 rb_entry_view_playing_song_changed (RBShellPlayer *player,
1917 				    RhythmDBEntry *entry,
1918 				    RBEntryView *view)
1919 {
1920  	gboolean realized, visible;
1921   	GtkTreeIter iter;
1922 
1923  	g_return_if_fail (RB_IS_ENTRY_VIEW (view));
1924 
1925 	if (view->priv->playing_entry != NULL) {
1926 		if (view->priv->playing_state != RB_ENTRY_VIEW_NOT_PLAYING)
1927 			rb_entry_view_emit_row_changed (view, view->priv->playing_entry);
1928 		g_object_unref (view->priv->playing_model);
1929 	}
1930 
1931  	view->priv->playing_entry = entry;
1932  	view->priv->playing_model = view->priv->model;
1933  	g_object_ref (view->priv->playing_model);
1934 
1935  	if (view->priv->playing_state != RB_ENTRY_VIEW_NOT_PLAYING) {
1936 		if (view->priv->playing_entry != NULL) {
1937 			view->priv->playing_entry_in_view =
1938 				rb_entry_view_emit_row_changed (view, view->priv->playing_entry);
1939 		}
1940 
1941 		if (view->priv->playing_entry
1942 		    && view->priv->playing_entry_in_view) {
1943 		    rb_entry_view_entry_is_visible (view, view->priv->playing_entry,
1944 						    &realized, &visible, &iter);
1945 		    if (realized && !visible)
1946 			    rb_entry_view_scroll_to_iter (view, &iter);
1947 		}
1948 	}
1949 }
1950 
1951 static gboolean
1952 harvest_entries (GtkTreeModel *model,
1953 		 GtkTreePath *path,
1954 		 GtkTreeIter *iter,
1955 		 GList      **list)
1956 {
1957 	RhythmDBEntry *entry;
1958 
1959 	gtk_tree_model_get (model, iter, 0, &entry, -1);
1960 
1961 	*list = g_list_prepend (*list, entry);
1962 
1963 	return FALSE;
1964 }
1965 
1966 /**
1967  * rb_entry_view_get_selected_entries:
1968  * @view: a #RBEntryView
1969  *
1970  * Gathers the selected entries from the view.
1971  *
1972  * Return value: (element-type RhythmDBEntry) (transfer full): a #GList of
1973  * selected entries in the view.
1974  */
1975 GList *
1976 rb_entry_view_get_selected_entries (RBEntryView *view)
1977 {
1978 	GList *list = NULL;
1979 
1980 	gtk_tree_selection_selected_foreach (view->priv->selection,
1981 					     (GtkTreeSelectionForeachFunc) harvest_entries,
1982 					     (gpointer) &list);
1983 
1984 	list = g_list_reverse (list);
1985 	return list;
1986 }
1987 
1988 static gboolean
1989 rb_entry_view_button_press_cb (GtkTreeView *treeview,
1990 			       GdkEventButton *event,
1991 			       RBEntryView *view)
1992 {
1993 	if (event->button == 3) {
1994 		GtkTreePath *path;
1995 		RhythmDBEntry *entry;
1996 
1997 		gtk_tree_view_get_path_at_pos (treeview, event->x, event->y, &path, NULL, NULL, NULL);
1998 		if (path != NULL) {
1999 			GList *selected;
2000 			entry = rhythmdb_query_model_tree_path_to_entry (view->priv->model, path);
2001 
2002 			selected = rb_entry_view_get_selected_entries (view);
2003 
2004 			if (!g_list_find (selected, entry))
2005 				rb_entry_view_select_entry (view, entry);
2006 
2007 			g_list_free (selected);
2008 
2009 			rhythmdb_entry_unref (entry);
2010 		}
2011 		g_signal_emit (G_OBJECT (view), rb_entry_view_signals[SHOW_POPUP], 0, (path != NULL));
2012 		return TRUE;
2013 	}
2014 
2015 	return FALSE;
2016 }
2017 
2018 static gboolean
2019 rb_entry_view_popup_menu_cb (GtkTreeView *treeview,
2020 			     RBEntryView *view)
2021 {
2022 	if (gtk_tree_selection_count_selected_rows (gtk_tree_view_get_selection (treeview)) == 0)
2023 		return FALSE;
2024 
2025 	g_signal_emit (G_OBJECT (view), rb_entry_view_signals[SHOW_POPUP], 0);
2026 	return TRUE;
2027 }
2028 
2029 static gboolean
2030 rb_entry_view_emit_selection_changed (RBEntryView *view)
2031 {
2032 	gboolean available;
2033 	gint sel_count;
2034 
2035 	GDK_THREADS_ENTER ();
2036 	sel_count = gtk_tree_selection_count_selected_rows (view->priv->selection);
2037 	available = (sel_count > 0);
2038 
2039 	if (available != view->priv->have_selection) {
2040 		gint entry_count;
2041 
2042 		entry_count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (view->priv->model), NULL);
2043 		view->priv->have_complete_selection = (sel_count == entry_count);
2044 
2045 		view->priv->have_selection = available;
2046 
2047 		g_signal_emit (G_OBJECT (view), rb_entry_view_signals[HAVE_SEL_CHANGED], 0, available);
2048 	}
2049 
2050 	view->priv->selection_changed_id = 0;
2051 	g_signal_emit (G_OBJECT (view), rb_entry_view_signals[SELECTION_CHANGED], 0);
2052 
2053 	GDK_THREADS_LEAVE ();
2054 	return FALSE;
2055 }
2056 
2057 static void
2058 rb_entry_view_selection_changed_cb (GtkTreeSelection *selection,
2059 				    RBEntryView *view)
2060 {
2061 	if (view->priv->selection_changed_id == 0)
2062 		view->priv->selection_changed_id = g_idle_add ((GSourceFunc)rb_entry_view_emit_selection_changed, view);
2063 }
2064 
2065 /**
2066  * rb_entry_view_have_selection:
2067  * @view: a #RBEntryView
2068  *
2069  * Determines whether there is an active selection in the view.
2070  *
2071  * Return value: TRUE if one or more rows are selected
2072  */
2073 gboolean
2074 rb_entry_view_have_selection (RBEntryView *view)
2075 {
2076 	return view->priv->have_selection;
2077 }
2078 
2079 /**
2080  * rb_entry_view_have_complete_selection:
2081  * @view: a #RBEntryView
2082  *
2083  * Determines whether all entries in the view are selected.
2084  *
2085  * Return value: TRUE if all rows in the view are selected
2086  */
2087 gboolean
2088 rb_entry_view_have_complete_selection (RBEntryView *view)
2089 {
2090 	return view->priv->have_complete_selection;
2091 }
2092 
2093 static void
2094 rb_entry_view_row_activated_cb (GtkTreeView *treeview,
2095 			       GtkTreePath *path,
2096 			       GtkTreeViewColumn *column,
2097 			       RBEntryView *view)
2098 {
2099 	RhythmDBEntry *entry;
2100 
2101 	rb_debug ("row activated");
2102 
2103 	entry = rhythmdb_query_model_tree_path_to_entry (view->priv->model, path);
2104 
2105 	rb_debug ("emitting entry activated");
2106 	g_signal_emit (G_OBJECT (view), rb_entry_view_signals[ENTRY_ACTIVATED], 0, entry);
2107 
2108 	rhythmdb_entry_unref (entry);
2109 }
2110 
2111 static void
2112 rb_entry_view_row_inserted_cb (GtkTreeModel *model,
2113 			       GtkTreePath *path,
2114 			       GtkTreeIter *iter,
2115 			       RBEntryView *view)
2116 {
2117 	RhythmDBEntry *entry = rhythmdb_query_model_tree_path_to_entry (RHYTHMDB_QUERY_MODEL (model), path);
2118 
2119 	rb_debug ("row added");
2120 	g_signal_emit (G_OBJECT (view), rb_entry_view_signals[ENTRY_ADDED], 0, entry);
2121 	rhythmdb_entry_unref (entry);
2122 }
2123 
2124 static void
2125 rb_entry_view_row_deleted_cb (GtkTreeModel *model,
2126 			      GtkTreePath *path,
2127 			      RBEntryView *view)
2128 {
2129 	RhythmDBEntry *entry = rhythmdb_query_model_tree_path_to_entry (RHYTHMDB_QUERY_MODEL (model), path);
2130 
2131 	rb_debug ("row deleted");
2132 	g_signal_emit (G_OBJECT (view), rb_entry_view_signals[ENTRY_DELETED], 0, entry);
2133 	rhythmdb_entry_unref (entry);
2134 }
2135 
2136 static void
2137 rb_entry_view_rows_reordered_cb (GtkTreeModel *model,
2138 				 GtkTreePath *path,
2139 				 GtkTreeIter *iter,
2140 				 gint *order,
2141 				 RBEntryView *view)
2142 {
2143 	GList *selected_rows;
2144 	GList *i;
2145 	gint model_size;
2146 	gboolean scrolled = FALSE;
2147 
2148 	rb_debug ("rows reordered");
2149 
2150 	model_size = gtk_tree_model_iter_n_children (model, NULL);
2151 
2152 	/* check if a selected row was moved; if so, we'll
2153 	 * need to move the selection too.
2154 	 */
2155 	selected_rows = gtk_tree_selection_get_selected_rows (view->priv->selection,
2156 							      NULL);
2157 	for (i = selected_rows; i != NULL; i = i->next) {
2158 		GtkTreePath *path = (GtkTreePath *)i->data;
2159 		gint index = gtk_tree_path_get_indices (path)[0];
2160 		gint newindex;
2161 		if (order[index] != index) {
2162 			GtkTreePath *newpath;
2163 			gtk_tree_selection_unselect_path (view->priv->selection, path);
2164 
2165 			for (newindex = 0; newindex < model_size; newindex++) {
2166 				if (order[newindex] == index) {
2167 					newpath = gtk_tree_path_new_from_indices (newindex, -1);
2168 					gtk_tree_selection_select_path (view->priv->selection, newpath);
2169 					if (!scrolled) {
2170 						GtkTreeViewColumn *col;
2171 						GtkTreeView *treeview = GTK_TREE_VIEW (view->priv->treeview);
2172 
2173 						col = gtk_tree_view_get_column (treeview, 0);
2174 						gtk_tree_view_scroll_to_cell (treeview, newpath, col, TRUE, 0.5, 0.0);
2175 						scrolled = TRUE;
2176 					}
2177 					gtk_tree_path_free (newpath);
2178 					break;
2179 				}
2180 			}
2181 
2182 		}
2183 	}
2184 
2185 	g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
2186 	g_list_free (selected_rows);
2187 
2188 	gtk_widget_queue_draw (GTK_WIDGET (view));
2189 }
2190 
2191 /**
2192  * rb_entry_view_select_all:
2193  * @view: a #RBEntryView
2194  *
2195  * Selects all rows in the view
2196  */
2197 void
2198 rb_entry_view_select_all (RBEntryView *view)
2199 {
2200 	gtk_tree_selection_select_all (view->priv->selection);
2201 }
2202 
2203 /**
2204  * rb_entry_view_select_none:
2205  * @view: a #RBEntryView
2206  *
2207  * Deselects all rows in the view.
2208  */
2209 void
2210 rb_entry_view_select_none (RBEntryView *view)
2211 {
2212 	gtk_tree_selection_unselect_all (view->priv->selection);
2213 }
2214 
2215 /**
2216  * rb_entry_view_select_entry:
2217  * @view: a #RBEntryView
2218  * @entry: a #RhythmDBEntry to select
2219  *
2220  * If the specified entry is present in the view, it is added
2221  * to the selection.
2222  */
2223 void
2224 rb_entry_view_select_entry (RBEntryView *view,
2225 			    RhythmDBEntry *entry)
2226 {
2227 	GtkTreeIter iter;
2228 
2229 	if (entry == NULL)
2230 		return;
2231 
2232 	rb_entry_view_select_none (view);
2233 
2234 	if (rhythmdb_query_model_entry_to_iter (view->priv->model,
2235 						entry, &iter)) {
2236 		gtk_tree_selection_select_iter (view->priv->selection, &iter);
2237 	}
2238 }
2239 
2240 /**
2241  * rb_entry_view_scroll_to_entry:
2242  * @view: a #RBEntryView
2243  * @entry: a #RhythmDBEntry to scroll to
2244  *
2245  * If the specified entry is present in the view, the view will be
2246  * scrolled so that the entry is visible.
2247  */
2248 void
2249 rb_entry_view_scroll_to_entry (RBEntryView *view,
2250 			       RhythmDBEntry *entry)
2251 {
2252 	GtkTreeIter iter;
2253 
2254 	if (rhythmdb_query_model_entry_to_iter (view->priv->model,
2255 						entry, &iter)) {
2256 		rb_entry_view_scroll_to_iter (view, &iter);
2257 	}
2258 }
2259 
2260 static void
2261 rb_entry_view_scroll_to_iter (RBEntryView *view,
2262 			      GtkTreeIter *iter)
2263 {
2264 	GtkTreePath *path;
2265 
2266 	/* It's possible to we can be asked to scroll the play queue's entry
2267 	 * view to the playing entry before the view has ever been displayed.
2268 	 * This will result in gtk+ warnings, so we avoid it in this case.
2269 	 */
2270 	if (!gtk_widget_get_realized (GTK_WIDGET (view)))
2271 		return;
2272 
2273 	path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->priv->model), iter);
2274 	gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view->priv->treeview), path,
2275 				      gtk_tree_view_get_column (GTK_TREE_VIEW (view->priv->treeview), 0),
2276 				      TRUE, 0.5, 0.0);
2277 	gtk_tree_view_set_cursor (GTK_TREE_VIEW (view->priv->treeview), path,
2278 				  gtk_tree_view_get_column (GTK_TREE_VIEW (view->priv->treeview), 0), FALSE);
2279 
2280 	gtk_tree_path_free (path);
2281 }
2282 
2283 /**
2284  * rb_entry_view_get_entry_visible:
2285  * @view: a #RBEntryView
2286  * @entry: a #RhythmDBEntry to check
2287  *
2288  * Determines whether a specified entry is present in the view
2289  * and is currently visible.
2290  *
2291  * Return value: TRUE if the entry is visible
2292  */
2293 gboolean
2294 rb_entry_view_get_entry_visible (RBEntryView *view,
2295 				 RhythmDBEntry *entry)
2296 {
2297 	GtkTreeIter unused;
2298 	gboolean realized, visible;
2299 
2300 	if (view->priv->playing_model != view->priv->model)
2301 		return FALSE;
2302 
2303 	rb_entry_view_entry_is_visible (view, entry, &realized, &visible,
2304 					&unused);
2305 	return realized && visible;
2306 }
2307 
2308 /**
2309  * rb_entry_view_get_entry_contained:
2310  * @view: a #RBEntryView
2311  * @entry: a #RhythmDBEntry to check
2312  *
2313  * Determines whether a specified entry is present in the view.
2314  *
2315  * Return value: TRUE if the entry is present in the view
2316  */
2317 gboolean
2318 rb_entry_view_get_entry_contained (RBEntryView *view,
2319 				   RhythmDBEntry *entry)
2320 {
2321 	GtkTreeIter unused;
2322 
2323 	return rhythmdb_query_model_entry_to_iter (view->priv->model,
2324 						   entry, &unused);
2325 }
2326 
2327 static void
2328 rb_entry_view_entry_is_visible (RBEntryView *view,
2329 				RhythmDBEntry *entry,
2330 				gboolean *realized,
2331 				gboolean *visible,
2332 				GtkTreeIter *iter)
2333 {
2334 	GtkTreePath *path;
2335 	GdkRectangle rect;
2336 
2337 	*realized = FALSE;
2338 	*visible = FALSE;
2339 
2340 	g_return_if_fail (entry != NULL);
2341 
2342 	if (!gtk_widget_get_realized (GTK_WIDGET (view)))
2343 		return;
2344 
2345 	*realized = TRUE;
2346 
2347 	if (!rhythmdb_query_model_entry_to_iter (view->priv->model,
2348 						 entry, iter))
2349 		return;
2350 
2351 	path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->priv->model), iter);
2352 	gtk_tree_view_get_cell_area (GTK_TREE_VIEW (view->priv->treeview),
2353 				     path,
2354 				     gtk_tree_view_get_column (GTK_TREE_VIEW (view->priv->treeview), 0),
2355 				     &rect);
2356 
2357 	gtk_tree_path_free (path);
2358 
2359 	*visible = (rect.y != 0 && rect.height != 0);
2360 }
2361 
2362 /**
2363  * rb_entry_view_enable_drag_source:
2364  * @view: a #RBEntryView
2365  * @targets: an array of #GtkTargetEntry structures defining the drag data targets
2366  * @n_targets: the number of entries in the target array
2367  *
2368  * Enables the entry view to act as a data source for drag an drop operations,
2369  * using a specified set of data targets.
2370  */
2371 void
2372 rb_entry_view_enable_drag_source (RBEntryView *view,
2373 				 const GtkTargetEntry *targets,
2374 				 int n_targets)
2375 {
2376 	g_return_if_fail (view != NULL);
2377 
2378 	rb_tree_dnd_add_drag_source_support (GTK_TREE_VIEW (view->priv->treeview),
2379 					 GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
2380 					 targets, n_targets, GDK_ACTION_COPY);
2381 }
2382 
2383 static void
2384 set_column_visibility (guint propid,
2385 		       GtkTreeViewColumn *column,
2386 		       GList *visible_props)
2387 {
2388 	gboolean visible;
2389 
2390 	if (g_object_get_qdata (G_OBJECT (column),
2391 				rb_entry_view_column_always_visible) == GINT_TO_POINTER (1))
2392 		visible = TRUE;
2393 	else
2394 		visible = (g_list_find (visible_props, GINT_TO_POINTER (propid)) != NULL);
2395 
2396 	gtk_tree_view_column_set_visible (column, visible);
2397 }
2398 
2399 static void
2400 rb_entry_view_sync_columns_visible (RBEntryView *view)
2401 {
2402 	GList *visible_properties = NULL;
2403 
2404 	g_return_if_fail (view != NULL);
2405 
2406 	if (view->priv->visible_columns != NULL) {
2407 		int i;
2408 		for (i = 0; view->priv->visible_columns[i] != NULL && *(view->priv->visible_columns[i]); i++) {
2409 			int value = rhythmdb_propid_from_nice_elt_name (view->priv->db, (const xmlChar *)view->priv->visible_columns[i]);
2410 			rb_debug ("visible columns: %s => %d", view->priv->visible_columns[i], value);
2411 
2412 			if ((value >= 0) && (value < RHYTHMDB_NUM_PROPERTIES))
2413 				visible_properties = g_list_prepend (visible_properties, GINT_TO_POINTER (value));
2414 		}
2415 	}
2416 
2417 	g_hash_table_foreach (view->priv->propid_column_map, (GHFunc) set_column_visibility, visible_properties);
2418 	g_list_free (visible_properties);
2419 }
2420 
2421 /**
2422  * rb_entry_view_set_state:
2423  * @view: a #RBEntryView
2424  * @state: the new playing entry state
2425  *
2426  * Sets the icon to be drawn in the 'playing' column next to the
2427  * current playing entry.  RB_ENTRY_VIEW_PLAYING and RB_ENTRY_VIEW_PAUSED
2428  * should be used when the source containing the entry view is playing,
2429  * and RB_ENTRY_VIEW_NOT_PLAYING otherwise.
2430  */
2431 void
2432 rb_entry_view_set_state (RBEntryView *view,
2433 			 RBEntryViewState state)
2434 {
2435 	g_return_if_fail (RB_IS_ENTRY_VIEW (view));
2436 	g_object_set (view, "playing-state", state, NULL);
2437 }
2438 
2439 static void
2440 rb_entry_view_grab_focus (GtkWidget *widget)
2441 {
2442 	RBEntryView *view = RB_ENTRY_VIEW (widget);
2443 
2444 	gtk_widget_grab_focus (GTK_WIDGET (view->priv->treeview));
2445 }
2446 
2447 static gboolean
2448 rb_entry_view_emit_row_changed (RBEntryView *view,
2449 				RhythmDBEntry *entry)
2450 {
2451 	GtkTreeIter iter;
2452 	GtkTreePath *path;
2453 
2454 	if (!rhythmdb_query_model_entry_to_iter (view->priv->model, entry, &iter))
2455 		return FALSE;
2456 
2457 	path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->priv->model),
2458 					&iter);
2459 	gtk_tree_model_row_changed (GTK_TREE_MODEL (view->priv->model),
2460 				    path, &iter);
2461 	gtk_tree_path_free (path);
2462 	return TRUE;
2463 }
2464 
2465 /**
2466  * rb_entry_view_set_fixed_column_width:
2467  * @view: a #RBEntryView
2468  * @column: the column to set the width for
2469  * @renderer: a temporary cell renderer to use
2470  * @strings: (array zero-terminated=1): a NULL-terminated array of strings that will be displayed in the column
2471  *
2472  * Helper function for calling @rb_set_tree_view_column_fixed_width on
2473  * a column.  This is important for performance reasons, as having the
2474  * tree view measure the strings in each of 20000 rows is very slow.
2475  */
2476 void
2477 rb_entry_view_set_fixed_column_width (RBEntryView *view,
2478 				      GtkTreeViewColumn *column,
2479 				      GtkCellRenderer *renderer,
2480 				      const gchar **strings)
2481 {
2482 	rb_set_tree_view_column_fixed_width (view->priv->treeview,
2483 					     column,
2484 					     renderer,
2485 					     strings,
2486 					     5);
2487 }
2488 
2489 /**
2490  * rb_entry_view_get_time_date_column_sample:
2491  *
2492  * Returns a sample string for use in columns displaying times
2493  * and dates in 'friendly' form (see @rb_utf_friendly_time).
2494  * For use with @rb_entry_view_set_fixed_column_width.
2495  *
2496  * Return value: sample date string
2497  */
2498 const char *
2499 rb_entry_view_get_time_date_column_sample ()
2500 {
2501 	static const char *sample = NULL;
2502 	if (sample == NULL) {
2503  		time_t then;
2504 
2505  		/* A reasonable estimate of the widest friendly date
2506  		   is "Yesterday NN:NN PM" */
2507  		then = time (NULL) - 86400;
2508  		sample = rb_utf_friendly_time (then);
2509   	}
2510 
2511 	return sample;
2512 }
2513 
2514 /**
2515  * rb_entry_view_resort_model:
2516  * @view: a #RBEntryView to resort
2517  *
2518  * Resorts the entries in the entry view.  Mostly to be used
2519  * when a new model is associated with the view.
2520  */
2521 void
2522 rb_entry_view_resort_model (RBEntryView *view)
2523 {
2524 	struct RBEntryViewColumnSortData *sort_data;
2525 
2526 	if (view->priv->sorting_column == NULL) {
2527 		rb_debug ("can't sort yet, the sorting column isn't here");
2528 		return;
2529 	}
2530 
2531 	sort_data = g_hash_table_lookup (view->priv->column_sort_data_map,
2532 					 view->priv->sorting_column);
2533 	g_assert (sort_data);
2534 
2535 	rhythmdb_query_model_set_sort_order (view->priv->model,
2536 					     sort_data->func,
2537 					     sort_data->data,
2538 					     NULL,
2539 					     (view->priv->sorting_order == GTK_SORT_DESCENDING));
2540 }
2541 
2542 /**
2543  * rb_entry_view_set_column_editable:
2544  * @view: a #RBEntryView
2545  * @column: a #RBEntryViewColumn to update
2546  * @editable: %TRUE to make the column editable, %FALSE otherwise
2547  *
2548  * Enables in-place editing of the values in a column.
2549  * The underlying %RhythmDBEntry is updated when editing is complete.
2550  */
2551 void
2552 rb_entry_view_set_column_editable (RBEntryView *view,
2553 				   RBEntryViewColumn column_type,
2554 				   gboolean editable)
2555 {
2556 	GtkTreeViewColumn *column;
2557 	GList *renderers;
2558 
2559 	column = rb_entry_view_get_column (view, column_type);
2560 	if (column == NULL)
2561 		return;
2562 
2563 	renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
2564 	g_object_set (renderers->data, "editable", editable, NULL);
2565 	g_list_free (renderers);
2566 }
2567 
2568 /* This should really be standard. */
2569 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
2570 
2571 GType
2572 rb_entry_view_column_get_type (void)
2573 {
2574 	static GType etype = 0;
2575 
2576 	if (etype == 0)	{
2577 		static const GEnumValue values[] = {
2578 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_TRACK_NUMBER, "track-number"),
2579 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_TITLE, "title"),
2580 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_ARTIST, "artist"),
2581 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_ALBUM, "album"),
2582 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_GENRE, "genre"),
2583 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_COMMENT, "comment"),
2584 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_DURATION, "duration"),
2585 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_QUALITY, "quality"),
2586 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_RATING, "rating"),
2587 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_PLAY_COUNT, "play-count"),
2588 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_YEAR, "year"),
2589 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_LAST_PLAYED, "last-played"),
2590 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_FIRST_SEEN, "first-seen"),
2591 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_LAST_SEEN, "last-seen"),
2592 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_LOCATION, "location"),
2593 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_BPM, "bpm"),
2594 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_ERROR, "error"),
2595 			{ 0, 0, 0 }
2596 		};
2597 
2598 		etype = g_enum_register_static ("RBEntryViewColumn", values);
2599 	}
2600 
2601 	return etype;
2602 }
2603 
2604 GType
2605 rb_entry_view_state_get_type (void)
2606 {
2607 	static GType etype = 0;
2608 
2609 	if (etype == 0)	{
2610 		static const GEnumValue values[] = {
2611 			ENUM_ENTRY (RB_ENTRY_VIEW_NOT_PLAYING, "not-playing"),
2612 			ENUM_ENTRY (RB_ENTRY_VIEW_PLAYING, "playing"),
2613 			ENUM_ENTRY (RB_ENTRY_VIEW_PAUSED, "paused"),
2614 			{ 0, 0, 0 }
2615 		};
2616 
2617 		etype = g_enum_register_static ("RBEntryViewState", values);
2618 	}
2619 
2620 	return etype;
2621 }