hythmbox-2.98/plugins/audioscrobbler/rb-audioscrobbler-profile-page.c

No issues found

   1 /*
   2  * rb-audioscrobbler-profile-page.c
   3  *
   4  * Copyright (C) 2010 Jamie Nicol <jamie@thenicols.net>
   5  *
   6  * This program is free software; you can redistribute it and/or modify
   7  * it under the terms of the GNU General Public License as published by
   8  * the Free Software Foundation; either version 2, or (at your option)
   9  * 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
  22  * GNU General Public License for more details.
  23  *
  24  * You should have received a copy of the GNU General Public License
  25  * along with this program; if not, write to the Free Software
  26  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
  27  */
  28 
  29 #ifdef HAVE_CONFIG_H
  30 #include <config.h>
  31 #endif
  32 
  33 #include <glib/gi18n.h>
  34 #include <gtk/gtk.h>
  35 #include <json-glib/json-glib.h>
  36 #include <math.h>
  37 
  38 #include <lib/rb-debug.h>
  39 #include <lib/rb-builder-helpers.h>
  40 #include <lib/rb-file-helpers.h>
  41 #include <lib/rb-util.h>
  42 #include <sources/rb-display-page-tree.h>
  43 #include <sources/rb-display-page-group.h>
  44 #include <widgets/eggwrapbox.h>
  45 #include <widgets/rb-source-toolbar.h>
  46 
  47 #include "rb-audioscrobbler-profile-page.h"
  48 #include "rb-audioscrobbler.h"
  49 #include "rb-audioscrobbler-account.h"
  50 #include "rb-audioscrobbler-user.h"
  51 #include "rb-audioscrobbler-radio-source.h"
  52 #include "rb-audioscrobbler-radio-track-entry-type.h"
  53 
  54 #define AUDIOSCROBBLER_PROFILE_PAGE_POPUP_PATH "/AudioscrobblerProfilePagePopup"
  55 
  56 struct _RBAudioscrobblerProfilePagePrivate {
  57 	RBAudioscrobblerService *service;
  58 	RBAudioscrobblerAccount *account;
  59 	RBAudioscrobbler *audioscrobbler;
  60 	GSettings *settings;
  61 
  62 	/* Used to request the user's profile data */
  63 	RBAudioscrobblerUser *user;
  64 	guint update_timeout_id;
  65 
  66 	/* List of radio stations owned by this page */
  67 	GList *radio_sources;
  68 
  69 	guint scrobbling_enabled_notification_id;
  70 
  71 	GtkWidget *main_vbox;
  72 	RBSourceToolbar *toolbar;
  73 
  74 	/* Login related UI */
  75 	GtkWidget *login_bar;
  76 	GtkWidget *login_status_label;
  77 	GtkWidget *login_response_button;
  78 
  79 	/* Profile UI */
  80 	GtkWidget *profile_window;
  81 
  82 	GtkWidget *user_info_area;
  83 	GtkWidget *profile_image;
  84 	GtkWidget *username_label;
  85 	GtkWidget *playcount_label;
  86 	GtkWidget *scrobbling_enabled_check;
  87 	GtkWidget *view_profile_link;
  88 
  89 	/* Scrobbler statistics */
  90 	GtkWidget *scrobbler_status_msg_label;
  91 	GtkWidget *scrobbler_queue_count_label;
  92 	GtkWidget *scrobbler_submit_count_label;
  93 	GtkWidget *scrobbler_submit_time_label;
  94 
  95 	/* Station creation UI */
  96 	GtkWidget *station_creator_type_combo;
  97 	GtkWidget *station_creator_arg_entry;
  98 
  99 	/* Profile data lists */
 100 	GtkWidget *recent_tracks_area;
 101 	GtkWidget *recent_tracks_wrap_box;
 102 	GtkWidget *top_tracks_area;
 103 	GtkWidget *top_tracks_wrap_box;
 104 	GtkWidget *loved_tracks_area;
 105 	GtkWidget *loved_tracks_wrap_box;
 106 	GtkWidget *top_artists_area;
 107 	GtkWidget *top_artists_wrap_box;
 108 	GtkWidget *recommended_artists_area;
 109 	GtkWidget *recommended_artists_wrap_box;
 110 
 111 	GHashTable *button_to_popup_menu_map;
 112 	GHashTable *popup_menu_to_data_map;
 113 
 114 	guint ui_merge_id;
 115 	GtkActionGroup *profile_action_group;
 116 	GtkActionGroup *service_action_group;
 117 	char *love_action_name;
 118 	char *ban_action_name;
 119 	char *download_action_name;
 120 	char *toolbar_path;
 121 };
 122 
 123 
 124 static void rb_audioscrobbler_profile_page_constructed (GObject *object);
 125 static void rb_audioscrobbler_profile_page_dispose (GObject* object);
 126 static void rb_audioscrobbler_profile_page_finalize (GObject *object);
 127 static void rb_audioscrobbler_profile_page_get_property (GObject *object,
 128                                                          guint prop_id,
 129                                                          GValue *value,
 130                                                          GParamSpec *pspec);
 131 static void rb_audioscrobbler_profile_page_set_property (GObject *object,
 132                                                          guint prop_id,
 133                                                          const GValue *value,
 134                                                          GParamSpec *pspec);
 135 
 136 /* UI initialisation functions */
 137 static void init_login_ui (RBAudioscrobblerProfilePage *page);
 138 static void init_profile_ui (RBAudioscrobblerProfilePage *page);
 139 static void init_actions (RBAudioscrobblerProfilePage *page);
 140 
 141 /* login related callbacks */
 142 static void login_bar_response_cb (GtkInfoBar *info_bar,
 143                                    gint response_id,
 144                                    RBAudioscrobblerProfilePage *page);
 145 void logout_button_clicked_cb (GtkButton *button, RBAudioscrobblerProfilePage *page);
 146 static void login_status_change_cb (RBAudioscrobblerAccount *account,
 147                                     RBAudioscrobblerAccountLoginStatus status,
 148                                     RBAudioscrobblerProfilePage *page);
 149 
 150 /* scrobbling enabled preference */
 151 void scrobbling_enabled_check_toggled_cb (GtkToggleButton *togglebutton,
 152                                           RBAudioscrobblerProfilePage *page);
 153 static void scrobbler_settings_changed_cb (GSettings *settings,
 154 					   const char *key,
 155                                            RBAudioscrobblerProfilePage *page);
 156 
 157 /* callbacks from scrobbler object */
 158 static void scrobbler_authentication_error_cb (RBAudioscrobbler *audioscrobbler,
 159                                                RBAudioscrobblerProfilePage *page);
 160 static void scrobbler_statistics_changed_cb (RBAudioscrobbler *audioscrobbler,
 161                                              const char *status_msg,
 162                                              guint queue_count,
 163                                              guint submit_count,
 164                                              const char *submit_time,
 165                                              RBAudioscrobblerProfilePage *page);
 166 
 167 static void playing_song_changed_cb (RBShellPlayer *player,
 168                                      RhythmDBEntry *entry,
 169                                      RBAudioscrobblerProfilePage *page);
 170 static void update_service_actions_sensitivity (RBAudioscrobblerProfilePage *page, RhythmDBEntry *entry);
 171 
 172 /* GtkAction callbacks */
 173 static void love_track_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page);
 174 static void ban_track_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page);
 175 static void download_track_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page);
 176 static void download_track_batch_complete_cb (RBTrackTransferBatch *batch, RBAudioscrobblerProfilePage *page);
 177 static void refresh_profile_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page);
 178 
 179 /* radio station creation/deletion */
 180 void station_creator_button_clicked_cb (GtkButton *button, RBAudioscrobblerProfilePage *page);
 181 static void load_radio_stations (RBAudioscrobblerProfilePage *page);
 182 static void save_radio_stations (RBAudioscrobblerProfilePage *page);
 183 static RBSource *add_radio_station (RBAudioscrobblerProfilePage *page,
 184                                     const char *url,
 185                                     const char *name);
 186 static void radio_station_name_changed_cb (RBAudioscrobblerRadioSource *radio,
 187                                            GParamSpec *spec,
 188                                            RBAudioscrobblerProfilePage *page);
 189 
 190 /* periodically attempts tp update the profile data */
 191 static gboolean update_timeout_cb (RBAudioscrobblerProfilePage *page);
 192 
 193 /* callbacks from user profile data requests */
 194 static void user_info_updated_cb (RBAudioscrobblerUser *user,
 195                                   RBAudioscrobblerUserData *info,
 196                                   RBAudioscrobblerProfilePage *page);
 197 static void recent_tracks_updated_cb (RBAudioscrobblerUser *user,
 198                                       GPtrArray *recent_tracks,
 199                                       RBAudioscrobblerProfilePage *page);
 200 static void top_tracks_updated_cb (RBAudioscrobblerUser *user,
 201                                    GPtrArray *top_tracks,
 202                                    RBAudioscrobblerProfilePage *page);
 203 static void loved_tracks_updated_cb (RBAudioscrobblerUser *user,
 204                                      GPtrArray *loved_tracks,
 205                                      RBAudioscrobblerProfilePage *page);
 206 static void top_artists_updated_cb (RBAudioscrobblerUser *user,
 207                                     GPtrArray *top_artists,
 208                                     RBAudioscrobblerProfilePage *page);
 209 static void recommended_artists_updated_cb (RBAudioscrobblerUser *user,
 210                                             GPtrArray *recommended_artists,
 211                                             RBAudioscrobblerProfilePage *page);
 212 
 213 /* UI creation for profile data lists, eg top artists, loved tracks */
 214 static void set_user_list (RBAudioscrobblerProfilePage *page,
 215                            GtkWidget *list_wrap_box,
 216                            GPtrArray *list_data);
 217 static GtkWidget *create_list_button (RBAudioscrobblerProfilePage *page,
 218                                       RBAudioscrobblerUserData *data,
 219                                       int max_sibling_image_width);
 220 static GtkWidget *create_popup_menu (RBAudioscrobblerProfilePage *page,
 221                                      RBAudioscrobblerUserData *data);
 222 
 223 /* callbacks from data list buttons and related popup menus */
 224 static void list_item_clicked_cb (GtkButton *button, RBAudioscrobblerProfilePage *page);
 225 static void list_item_view_url_activated_cb (GtkMenuItem *menuitem,
 226                                              RBAudioscrobblerProfilePage *page);
 227 static void list_item_listen_similar_artists_activated_cb (GtkMenuItem *menuitem,
 228                                                            RBAudioscrobblerProfilePage *page);
 229 static void list_item_listen_top_fans_activated_cb (GtkMenuItem *menuitem,
 230                                                     RBAudioscrobblerProfilePage *page);
 231 
 232 /* RBDisplayPage implementations */
 233 static void impl_selected (RBDisplayPage *page);
 234 static void impl_deselected (RBDisplayPage *page);
 235 static gboolean impl_show_popup (RBDisplayPage *page);
 236 static void impl_delete_thyself (RBDisplayPage *page);
 237 
 238 enum {
 239 	PROP_0,
 240 	PROP_SERVICE,
 241 	PROP_TOOLBAR_PATH
 242 };
 243 
 244 static GtkActionEntry profile_actions [] =
 245 {
 246 	{ "AudioscrobblerProfileRefresh", NULL, N_("Refresh Profile"), NULL,
 247 	  N_("Refresh your Profile"),
 248 	  G_CALLBACK (refresh_profile_action_cb) }
 249 };
 250 
 251 
 252 G_DEFINE_DYNAMIC_TYPE (RBAudioscrobblerProfilePage, rb_audioscrobbler_profile_page, RB_TYPE_DISPLAY_PAGE)
 253 
 254 RBDisplayPage *
 255 rb_audioscrobbler_profile_page_new (RBShell *shell, GObject *plugin, RBAudioscrobblerService *service)
 256 {
 257 	RBDisplayPage *page;
 258 	RhythmDB *db;
 259 	char *name;
 260 	gchar *icon_name;
 261 	gchar *icon_path;
 262 	gint icon_size;
 263 	GdkPixbuf *icon_pixbuf;
 264 
 265 	g_object_get (shell, "db", &db, NULL);
 266 	g_object_get (service, "name", &name, NULL);
 267 
 268 	icon_name = g_strconcat (rb_audioscrobbler_service_get_name (service), "-icon.png", NULL);
 269 	icon_path = rb_find_plugin_data_file (plugin, icon_name);
 270 	gtk_icon_size_lookup (GTK_ICON_SIZE_LARGE_TOOLBAR, &icon_size, NULL);
 271 	icon_pixbuf = gdk_pixbuf_new_from_file_at_size (icon_path, icon_size, icon_size, NULL);
 272 
 273 	page = RB_DISPLAY_PAGE (g_object_new (RB_TYPE_AUDIOSCROBBLER_PROFILE_PAGE,
 274 					      "shell", shell,
 275 					      "plugin", plugin,
 276 					      "name", name,
 277 					      "pixbuf", icon_pixbuf,
 278 					      "service", service,
 279 					      NULL));
 280 
 281 	g_object_unref (db);
 282 	g_free (name);
 283 	g_free (icon_name);
 284 	g_free (icon_path);
 285 	g_object_unref (icon_pixbuf);
 286 
 287 	return page;
 288 }
 289 
 290 static void
 291 rb_audioscrobbler_profile_page_class_init (RBAudioscrobblerProfilePageClass *klass)
 292 {
 293 	GObjectClass *object_class;
 294 	RBDisplayPageClass *page_class;
 295 
 296 	object_class = G_OBJECT_CLASS (klass);
 297 	object_class->constructed = rb_audioscrobbler_profile_page_constructed;
 298 	object_class->dispose = rb_audioscrobbler_profile_page_dispose;
 299 	object_class->finalize = rb_audioscrobbler_profile_page_finalize;
 300 	object_class->get_property = rb_audioscrobbler_profile_page_get_property;
 301 	object_class->set_property = rb_audioscrobbler_profile_page_set_property;
 302 
 303 	page_class = RB_DISPLAY_PAGE_CLASS (klass);
 304 	page_class->selected = impl_selected;
 305 	page_class->deselected = impl_deselected;
 306 	page_class->show_popup = impl_show_popup;
 307 	page_class->delete_thyself = impl_delete_thyself;
 308 
 309 	g_object_class_install_property (object_class,
 310 	                                 PROP_SERVICE,
 311 	                                 g_param_spec_object ("service",
 312 	                                                      "Service",
 313 	                                                      "Audioscrobbler service for this page",
 314 	                                                      RB_TYPE_AUDIOSCROBBLER_SERVICE,
 315                                                               G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
 316 	g_object_class_install_property (object_class,
 317 					 PROP_TOOLBAR_PATH,
 318 					 g_param_spec_string ("toolbar-path",
 319 							      "toolbar path",
 320 							      "toolbar UI path",
 321 							      NULL,
 322 							      G_PARAM_READABLE));
 323 
 324 	g_type_class_add_private (klass, sizeof (RBAudioscrobblerProfilePagePrivate));
 325 }
 326 
 327 static void
 328 rb_audioscrobbler_profile_page_class_finalize (RBAudioscrobblerProfilePageClass *klass)
 329 {
 330 }
 331 
 332 static void
 333 rb_audioscrobbler_profile_page_init (RBAudioscrobblerProfilePage *page)
 334 {
 335 	page->priv = G_TYPE_INSTANCE_GET_PRIVATE (page, RB_TYPE_AUDIOSCROBBLER_PROFILE_PAGE, RBAudioscrobblerProfilePagePrivate);
 336 
 337 	page->priv->button_to_popup_menu_map = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
 338 	page->priv->popup_menu_to_data_map = g_hash_table_new (g_direct_hash, g_direct_equal);
 339 }
 340 
 341 static void
 342 rb_audioscrobbler_profile_page_constructed (GObject *object)
 343 {
 344 	RBAudioscrobblerProfilePage *page;
 345 	RBShell *shell;
 346 	char *scrobbler_settings;
 347 	RBShellPlayer *shell_player;
 348 
 349 	RB_CHAIN_GOBJECT_METHOD (rb_audioscrobbler_profile_page_parent_class, constructed, object);
 350 
 351 	page = RB_AUDIOSCROBBLER_PROFILE_PAGE (object);
 352 	g_object_get (page, "shell", &shell, NULL);
 353 
 354 	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (page), RB_DISPLAY_PAGE_GROUP_LIBRARY);
 355 
 356 	g_object_get (shell, "shell-player", &shell_player, NULL);
 357 	g_signal_connect_object (shell_player,
 358 				 "playing-song-changed",
 359 				 G_CALLBACK (playing_song_changed_cb),
 360 				 page, 0);
 361 	g_object_unref (shell_player);
 362 
 363 	/* create the UI */
 364 	page->priv->main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4);
 365 	gtk_box_pack_start (GTK_BOX (page), page->priv->main_vbox, TRUE, TRUE, 0);
 366 	gtk_widget_show (page->priv->main_vbox);
 367 
 368 	init_actions (page);
 369 
 370 	init_login_ui (page);
 371 	init_profile_ui (page);
 372 
 373 
 374 	/* create the user */
 375 	page->priv->user = rb_audioscrobbler_user_new (page->priv->service);
 376 	g_signal_connect (page->priv->user,
 377 	                  "user-info-updated",
 378 	                  G_CALLBACK (user_info_updated_cb),
 379 	                  page);
 380 	g_signal_connect (page->priv->user,
 381 	                  "recent-tracks-updated",
 382 	                  G_CALLBACK (recent_tracks_updated_cb),
 383 	                  page);
 384 	g_signal_connect (page->priv->user,
 385 	                  "top-tracks-updated",
 386 	                  G_CALLBACK (top_tracks_updated_cb),
 387 	                  page);
 388 	g_signal_connect (page->priv->user,
 389 	                  "loved-tracks-updated",
 390 	                  G_CALLBACK (loved_tracks_updated_cb),
 391 	                  page);
 392 	g_signal_connect (page->priv->user,
 393 	                  "top-artists-updated",
 394 	                  G_CALLBACK (top_artists_updated_cb),
 395 	                  page);
 396 	g_signal_connect (page->priv->user,
 397 	                  "recommended-artists-updated",
 398 	                  G_CALLBACK (recommended_artists_updated_cb),
 399 	                  page);
 400 
 401 	/* create the account */
 402 	page->priv->account = rb_audioscrobbler_account_new (page->priv->service);
 403 	g_signal_connect (page->priv->account,
 404 	                  "login-status-changed",
 405 	                  (GCallback)login_status_change_cb,
 406 	                  page);
 407 	/* scrobbler settings */
 408 	scrobbler_settings = g_strconcat (AUDIOSCROBBLER_SETTINGS_PATH "/",
 409 					  rb_audioscrobbler_service_get_name (page->priv->service),
 410 					  NULL);
 411 	page->priv->settings = g_settings_new_with_path (AUDIOSCROBBLER_SETTINGS_SCHEMA,
 412 							 scrobbler_settings);
 413 	login_status_change_cb (page->priv->account,
 414 	                        rb_audioscrobbler_account_get_login_status (page->priv->account),
 415 	                        page);
 416 
 417 	g_signal_connect_object (page->priv->settings,
 418 				 "changed",
 419 				 G_CALLBACK (scrobbler_settings_changed_cb),
 420 				 page, 0);
 421 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (page->priv->scrobbling_enabled_check),
 422 				      g_settings_get_boolean (page->priv->settings,
 423 							      AUDIOSCROBBLER_SCROBBLING_ENABLED_KEY));
 424 
 425 	g_object_unref (shell);
 426 	g_free (scrobbler_settings);
 427 }
 428 
 429 static void
 430 rb_audioscrobbler_profile_page_dispose (GObject* object)
 431 {
 432 	RBAudioscrobblerProfilePage *page;
 433 
 434 	page = RB_AUDIOSCROBBLER_PROFILE_PAGE (object);
 435 
 436 	if (page->priv->service != NULL) {
 437 		g_object_unref (page->priv->service);
 438 		page->priv->service = NULL;
 439 	}
 440 
 441 	if (page->priv->audioscrobbler != NULL) {
 442 		g_object_unref (page->priv->audioscrobbler);
 443 		page->priv->audioscrobbler = NULL;
 444 	}
 445 
 446 	if (page->priv->account != NULL) {
 447 		g_object_unref (page->priv->account);
 448 		page->priv->account = NULL;
 449 	}
 450 
 451 	if (page->priv->user != NULL) {
 452 		g_object_unref (page->priv->user);
 453 		page->priv->user = NULL;
 454 	}
 455 
 456 	if (page->priv->settings != NULL) {
 457 		g_object_unref (page->priv->settings);
 458 		page->priv->settings = NULL;
 459 	}
 460 
 461 	if (page->priv->button_to_popup_menu_map != NULL) {
 462 		g_hash_table_unref (page->priv->button_to_popup_menu_map);
 463 		page->priv->button_to_popup_menu_map = NULL;
 464 	}
 465 
 466 	if (page->priv->popup_menu_to_data_map != NULL) {
 467 		g_hash_table_unref (page->priv->popup_menu_to_data_map);
 468 		page->priv->popup_menu_to_data_map = NULL;
 469 	}
 470 
 471 	G_OBJECT_CLASS (rb_audioscrobbler_profile_page_parent_class)->dispose (object);
 472 }
 473 
 474 static void
 475 rb_audioscrobbler_profile_page_finalize (GObject *object)
 476 {
 477 	RBAudioscrobblerProfilePage *page;
 478 	page = RB_AUDIOSCROBBLER_PROFILE_PAGE (object);
 479 
 480 	g_free (page->priv->love_action_name);
 481 	g_free (page->priv->ban_action_name);
 482 	g_free (page->priv->download_action_name);
 483 	g_free (page->priv->toolbar_path);
 484 
 485 	G_OBJECT_CLASS (rb_audioscrobbler_profile_page_parent_class)->finalize (object);
 486 }
 487 
 488 static void
 489 rb_audioscrobbler_profile_page_get_property (GObject *object,
 490                                                guint prop_id,
 491                                                GValue *value,
 492                                                GParamSpec *pspec)
 493 {
 494 	RBAudioscrobblerProfilePage *page = RB_AUDIOSCROBBLER_PROFILE_PAGE (object);
 495 	switch (prop_id) {
 496 	case PROP_TOOLBAR_PATH:
 497 		g_value_set_string (value, page->priv->toolbar_path);
 498 		break;
 499 	default:
 500 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 501 		break;
 502 	}
 503 }
 504 
 505 static void
 506 rb_audioscrobbler_profile_page_set_property (GObject *object,
 507                                                guint prop_id,
 508                                                const GValue *value,
 509                                                GParamSpec *pspec)
 510 {
 511 	RBAudioscrobblerProfilePage *page = RB_AUDIOSCROBBLER_PROFILE_PAGE (object);
 512 	switch (prop_id) {
 513 	case PROP_SERVICE:
 514 		page->priv->service = g_value_dup_object (value);
 515 		break;
 516 	default:
 517 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 518 		break;
 519 	}
 520 }
 521 
 522 static void
 523 init_login_ui (RBAudioscrobblerProfilePage *page)
 524 {
 525 	GtkWidget *content_area;
 526 
 527 	page->priv->login_bar = gtk_info_bar_new ();
 528 	page->priv->login_status_label = gtk_label_new ("");
 529 	page->priv->login_response_button = gtk_button_new ();
 530 	content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (page->priv->login_bar));
 531 	gtk_container_add (GTK_CONTAINER (content_area), page->priv->login_status_label);
 532 	page->priv->login_response_button =
 533 		gtk_info_bar_add_button (GTK_INFO_BAR (page->priv->login_bar),
 534 		                         "", GTK_RESPONSE_OK);
 535 	g_signal_connect (page->priv->login_bar,
 536 	                  "response",
 537 	                  G_CALLBACK (login_bar_response_cb),
 538 	                  page);
 539 	gtk_box_pack_start (GTK_BOX (page->priv->main_vbox), page->priv->login_bar, FALSE, FALSE, 0);
 540 }
 541 
 542 static void
 543 init_profile_ui (RBAudioscrobblerProfilePage *page)
 544 {
 545 	GObject *plugin;
 546 	char *builder_file;
 547 	GtkBuilder *builder;
 548 	GtkWidget *combo_container;
 549 	int i;
 550 
 551 	g_object_get (page, "plugin", &plugin, NULL);
 552 
 553 	builder_file = rb_find_plugin_data_file (plugin, "audioscrobbler-profile.ui");
 554 	g_assert (builder_file != NULL);
 555 	builder = rb_builder_load (builder_file, page);
 556 
 557 	page->priv->profile_window = GTK_WIDGET (gtk_builder_get_object (builder, "profile_window"));
 558 
 559 	page->priv->user_info_area = GTK_WIDGET (gtk_builder_get_object (builder, "user_info_area"));
 560 	page->priv->profile_image = GTK_WIDGET (gtk_builder_get_object (builder, "profile_image"));
 561 	page->priv->username_label = GTK_WIDGET (gtk_builder_get_object (builder, "username_label"));
 562 	page->priv->playcount_label = GTK_WIDGET (gtk_builder_get_object (builder, "playcount_label"));
 563 	page->priv->scrobbling_enabled_check = GTK_WIDGET (gtk_builder_get_object (builder, "scrobbling_enabled_check"));
 564 	page->priv->view_profile_link = GTK_WIDGET (gtk_builder_get_object (builder, "view_profile_link"));
 565 
 566 	/* scrobbler statistics */
 567 	page->priv->scrobbler_status_msg_label = GTK_WIDGET (gtk_builder_get_object (builder, "scrobbler_status_msg_label"));
 568 	page->priv->scrobbler_queue_count_label = GTK_WIDGET (gtk_builder_get_object (builder, "scrobbler_queue_count_label"));
 569 	page->priv->scrobbler_submit_count_label = GTK_WIDGET (gtk_builder_get_object (builder, "scrobbler_submit_count_label"));
 570 	page->priv->scrobbler_submit_time_label = GTK_WIDGET (gtk_builder_get_object (builder, "scrobbler_submit_time_label"));
 571 
 572 	/* station creator */
 573 	page->priv->station_creator_arg_entry = GTK_WIDGET (gtk_builder_get_object (builder, "station_creator_arg_entry"));
 574 	combo_container = GTK_WIDGET (gtk_builder_get_object (builder, "station_creator_combo_container"));
 575 	page->priv->station_creator_type_combo = gtk_combo_box_text_new ();
 576 	gtk_container_add (GTK_CONTAINER (combo_container), page->priv->station_creator_type_combo);
 577 	for (i = 0; i < RB_AUDIOSCROBBLER_RADIO_TYPE_LAST; i++) {
 578 		gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (page->priv->station_creator_type_combo),
 579 						rb_audioscrobbler_radio_type_get_text (i));
 580 	}
 581 	gtk_combo_box_set_active (GTK_COMBO_BOX (page->priv->station_creator_type_combo), 0);
 582 	gtk_widget_show (page->priv->station_creator_type_combo);
 583 
 584 	/* lists of data */
 585 	page->priv->recent_tracks_area = GTK_WIDGET (gtk_builder_get_object (builder, "recent_tracks_area"));
 586 	page->priv->recent_tracks_wrap_box = egg_wrap_box_new (EGG_WRAP_ALLOCATE_HOMOGENEOUS,
 587 	                                                       EGG_WRAP_BOX_SPREAD_EXPAND,
 588 	                                                       EGG_WRAP_BOX_SPREAD_START,
 589 	                                                       2, 2);
 590 	gtk_box_pack_end (GTK_BOX (page->priv->recent_tracks_area),
 591 	                  page->priv->recent_tracks_wrap_box,
 592 	                  TRUE, TRUE, 0);
 593 
 594 	page->priv->top_tracks_area = GTK_WIDGET (gtk_builder_get_object (builder, "top_tracks_area"));
 595 	page->priv->top_tracks_wrap_box = egg_wrap_box_new (EGG_WRAP_ALLOCATE_HOMOGENEOUS,
 596 	                                                    EGG_WRAP_BOX_SPREAD_EXPAND,
 597 	                                                    EGG_WRAP_BOX_SPREAD_START,
 598 	                                                    2, 2);
 599 	gtk_box_pack_end (GTK_BOX (page->priv->top_tracks_area),
 600 	                  page->priv->top_tracks_wrap_box,
 601 	                  TRUE, TRUE, 0);
 602 
 603 	page->priv->loved_tracks_area = GTK_WIDGET (gtk_builder_get_object (builder, "loved_tracks_area"));
 604 	page->priv->loved_tracks_wrap_box = egg_wrap_box_new (EGG_WRAP_ALLOCATE_HOMOGENEOUS,
 605 	                                                      EGG_WRAP_BOX_SPREAD_EXPAND,
 606 	                                                      EGG_WRAP_BOX_SPREAD_START,
 607 	                                                      2, 2);
 608 	gtk_box_pack_end (GTK_BOX (page->priv->loved_tracks_area),
 609 	                  page->priv->loved_tracks_wrap_box,
 610 	                  TRUE, TRUE, 0);
 611 	
 612 	page->priv->top_artists_area = GTK_WIDGET (gtk_builder_get_object (builder, "top_artists_area"));
 613 	page->priv->top_artists_wrap_box = egg_wrap_box_new (EGG_WRAP_ALLOCATE_HOMOGENEOUS,
 614 	                                                     EGG_WRAP_BOX_SPREAD_EXPAND,
 615 	                                                     EGG_WRAP_BOX_SPREAD_START,
 616 	                                                     2, 2);
 617 	gtk_box_pack_end (GTK_BOX (page->priv->top_artists_area),
 618 	                  page->priv->top_artists_wrap_box,
 619 	                  TRUE, TRUE, 0);
 620 
 621 	page->priv->recommended_artists_area = GTK_WIDGET (gtk_builder_get_object (builder, "recommended_artists_area"));
 622 	page->priv->recommended_artists_wrap_box = egg_wrap_box_new (EGG_WRAP_ALLOCATE_HOMOGENEOUS,
 623 	                                                             EGG_WRAP_BOX_SPREAD_EXPAND,
 624 	                                                             EGG_WRAP_BOX_SPREAD_START,
 625 	                                                             2, 2);
 626 	gtk_box_pack_end (GTK_BOX (page->priv->recommended_artists_area),
 627 	                  page->priv->recommended_artists_wrap_box,
 628 	                  TRUE, TRUE, 0);
 629 
 630 	/* pack profile into main vbox */
 631 	gtk_box_pack_start (GTK_BOX (page->priv->main_vbox), page->priv->profile_window, TRUE, TRUE, 0);
 632 
 633 
 634 	g_object_unref (plugin);
 635 	g_free (builder_file);
 636 	g_object_unref (builder);
 637 }
 638 
 639 static void
 640 init_actions (RBAudioscrobblerProfilePage *page)
 641 {
 642 	char *ui_file;
 643 	RBShell *shell;
 644 	RBShellPlayer *player;
 645 	GObject *plugin;
 646 	GtkUIManager *ui_manager;
 647 	RhythmDBEntry *entry;
 648 	char *group_name;
 649 	char *toolbar_name;
 650 
 651 	g_object_get (page, "shell", &shell, "plugin", &plugin, "ui-manager", &ui_manager, NULL);
 652 	ui_file = rb_find_plugin_data_file (plugin, "audioscrobbler-profile-ui.xml");
 653 	page->priv->ui_merge_id = gtk_ui_manager_add_ui_from_file (ui_manager, ui_file, NULL);
 654 
 655 	page->priv->profile_action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (page),
 656 										   "AudioscrobblerProfileActions",
 657 										   NULL, 0,
 658 										   page);
 659 	_rb_action_group_add_display_page_actions (page->priv->profile_action_group,
 660 						   G_OBJECT (shell),
 661 						   profile_actions,
 662 						   G_N_ELEMENTS (profile_actions));
 663 
 664 	/* Unfortunately we can't use the usual trick of declaring a static array of GtkActionEntry,
 665 	 * and simply using _rb_source_register_action_group with that array.
 666 	 * This is because each instance of this page needs its own love and ban actions
 667 	 * so tracks can be loved/banned differently for different audioscrobbler services.
 668 	 */
 669 	group_name = g_strdup_printf ("%sActions", rb_audioscrobbler_service_get_name (page->priv->service));
 670 	page->priv->love_action_name = g_strdup_printf ("%sLoveTrack", rb_audioscrobbler_service_get_name (page->priv->service));
 671 	page->priv->ban_action_name = g_strdup_printf ("%sBanTrack", rb_audioscrobbler_service_get_name (page->priv->service));
 672 	page->priv->download_action_name = g_strdup_printf ("%sDownloadTrack", rb_audioscrobbler_service_get_name (page->priv->service));
 673 
 674 	GtkActionEntry service_actions [] =
 675 	{
 676 		{ page->priv->love_action_name, "emblem-favorite", N_("Love"), NULL,
 677 		  N_("Mark this song as loved"),
 678 		  G_CALLBACK (love_track_action_cb) },
 679 		{ page->priv->ban_action_name, GTK_STOCK_CANCEL, N_("Ban"), NULL,
 680 		  N_("Ban the current track from being played again"),
 681 		  G_CALLBACK (ban_track_action_cb) },
 682 		{ page->priv->download_action_name, GTK_STOCK_SAVE, N_("Download"), NULL,
 683 		  N_("Download the currently playing track"),
 684 		  G_CALLBACK (download_track_action_cb) }
 685 	};
 686 
 687 	page->priv->service_action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (page),
 688 										   group_name,
 689 										   service_actions,
 690 										   G_N_ELEMENTS (service_actions),
 691 										   page);
 692 	g_object_get (shell, "shell-player", &player, NULL);
 693 	entry = rb_shell_player_get_playing_entry (player);
 694 	update_service_actions_sensitivity (page, entry);
 695 	if (entry != NULL) {
 696 		rhythmdb_entry_unref (entry);
 697 	}
 698 	g_object_unref (player);
 699 
 700 	/* set up toolbar */
 701 	toolbar_name = g_strdup_printf ("%sSourceToolBar", rb_audioscrobbler_service_get_name (page->priv->service));
 702 	page->priv->toolbar_path = g_strdup_printf ("/%sSourceToolBar", rb_audioscrobbler_service_get_name (page->priv->service));
 703 	gtk_ui_manager_add_ui (ui_manager, page->priv->ui_merge_id, "/", toolbar_name, NULL, GTK_UI_MANAGER_TOOLBAR, TRUE);
 704 	gtk_ui_manager_add_ui (ui_manager, page->priv->ui_merge_id, page->priv->toolbar_path, "Love", page->priv->love_action_name, GTK_UI_MANAGER_TOOLITEM, FALSE);
 705 	gtk_ui_manager_add_ui (ui_manager, page->priv->ui_merge_id, page->priv->toolbar_path, "Ban", page->priv->ban_action_name, GTK_UI_MANAGER_TOOLITEM, FALSE);
 706 	gtk_ui_manager_add_ui (ui_manager, page->priv->ui_merge_id, page->priv->toolbar_path, "Download", page->priv->download_action_name, GTK_UI_MANAGER_TOOLITEM, FALSE);
 707 
 708 	page->priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (page), ui_manager);
 709 	gtk_box_pack_start (GTK_BOX (page->priv->main_vbox), GTK_WIDGET (page->priv->toolbar), FALSE, FALSE, 0);
 710 	gtk_widget_show (GTK_WIDGET (page->priv->toolbar));
 711 
 712 	g_free (ui_file);
 713 	g_free (toolbar_name);
 714 	g_object_unref (shell);
 715 	g_object_unref (plugin);
 716 	g_object_unref (ui_manager);
 717 	g_free (group_name);
 718 }
 719 
 720 static void
 721 login_bar_response_cb (GtkInfoBar *info_bar,
 722                        gint response_id,
 723                        RBAudioscrobblerProfilePage *page)
 724 {
 725 	switch (rb_audioscrobbler_account_get_login_status (page->priv->account)) {
 726 	case RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGED_OUT:
 727 	case RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_AUTH_ERROR:
 728 	case RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_CONNECTION_ERROR:
 729 		rb_audioscrobbler_account_authenticate (page->priv->account);
 730 		break;
 731 	case RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGING_IN:
 732 	case RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGED_IN:
 733 		rb_audioscrobbler_account_logout (page->priv->account);
 734 		break;
 735 	default:
 736 		g_assert_not_reached ();
 737 		break;
 738 	}
 739 }
 740 
 741 void
 742 logout_button_clicked_cb (GtkButton *button,
 743                           RBAudioscrobblerProfilePage *page)
 744 {
 745 	rb_audioscrobbler_account_logout (page->priv->account);
 746 }
 747 
 748 static void
 749 login_status_change_cb (RBAudioscrobblerAccount *account,
 750                         RBAudioscrobblerAccountLoginStatus status,
 751                         RBAudioscrobblerProfilePage *page)
 752 {
 753 	const char *username;
 754 	const char *session_key;
 755 	char *label_text = NULL;
 756 	char *button_text = NULL;
 757 	gboolean show_login_bar;
 758 	gboolean show_profile;
 759 
 760 	username = rb_audioscrobbler_account_get_username (page->priv->account);
 761 	session_key = rb_audioscrobbler_account_get_session_key (page->priv->account);
 762 
 763 	/* delete old scrobbler */
 764 	if (page->priv->audioscrobbler != NULL) {
 765 		g_object_unref (page->priv->audioscrobbler);
 766 		page->priv->audioscrobbler = NULL;
 767 	}
 768 
 769 	/* create new scrobbler if new user has logged in and scrobbling is enabled */
 770 	if (status == RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGED_IN &&
 771 	    g_settings_get_boolean (page->priv->settings, AUDIOSCROBBLER_SCROBBLING_ENABLED_KEY)) {
 772 		RBShell *shell;
 773 		RBShellPlayer *shell_player;
 774 		g_object_get (page, "shell", &shell, NULL);
 775 		g_object_get (shell, "shell-player", &shell_player, NULL);
 776 		page->priv->audioscrobbler =
 777 			rb_audioscrobbler_new (page->priv->service,
 778 				               shell_player,
 779                                                rb_audioscrobbler_account_get_username (page->priv->account),
 780 			                       rb_audioscrobbler_account_get_session_key (page->priv->account));
 781 		g_signal_connect (page->priv->audioscrobbler,
 782 			          "authentication-error",
 783 			          G_CALLBACK (scrobbler_authentication_error_cb),
 784 			          page);
 785 		g_signal_connect (page->priv->audioscrobbler,
 786 			          "statistics-changed",
 787 			          G_CALLBACK (scrobbler_statistics_changed_cb),
 788 			          page);
 789 		rb_audioscrobbler_statistics_changed (page->priv->audioscrobbler);
 790 		g_object_unref (shell_player);
 791 		g_object_unref (shell);
 792 	}
 793 
 794 	/* set the new user details */
 795 	rb_audioscrobbler_user_set_authentication_details (page->priv->user, username, session_key);
 796 	if (username != NULL) {
 797 		rb_audioscrobbler_user_update (page->priv->user);
 798 	}
 799 
 800 	load_radio_stations (page);
 801 
 802 	/* update the login ui */
 803 	switch (status) {
 804 	case RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGED_OUT:
 805 		show_login_bar = TRUE;
 806 		show_profile = FALSE;
 807 		label_text = g_strdup (_("You are not currently logged in."));
 808 		button_text = g_strdup (_("Log in"));
 809 		gtk_info_bar_set_message_type (GTK_INFO_BAR (page->priv->login_bar), GTK_MESSAGE_INFO);
 810 		break;
 811 	case RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGING_IN:
 812 		show_login_bar = TRUE;
 813 		show_profile = FALSE;
 814 		label_text = g_strdup (_("Waiting for authentication..."));
 815 		button_text = g_strdup (_("Cancel"));
 816 		gtk_info_bar_set_message_type (GTK_INFO_BAR (page->priv->login_bar), GTK_MESSAGE_INFO);
 817 		break;
 818 	case RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGED_IN:
 819 		show_login_bar = FALSE;
 820 		show_profile = TRUE;
 821 		break;
 822 	case RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_AUTH_ERROR:
 823 		show_login_bar = TRUE;
 824 		show_profile = FALSE;
 825 		label_text = g_strdup (_("Authentication error. Please try logging in again."));
 826 		button_text = g_strdup (_("Log in"));
 827 		gtk_info_bar_set_message_type (GTK_INFO_BAR (page->priv->login_bar), GTK_MESSAGE_WARNING);
 828 		break;
 829 	case RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_CONNECTION_ERROR:
 830 		show_login_bar = TRUE;
 831 		show_profile = FALSE;
 832 		label_text = g_strdup (_("Connection error. Please try logging in again."));
 833 		button_text = g_strdup (_("Log in"));
 834 		gtk_info_bar_set_message_type (GTK_INFO_BAR (page->priv->login_bar), GTK_MESSAGE_WARNING);
 835 		break;
 836 	default:
 837 		g_assert_not_reached ();
 838 		break;
 839 	}
 840 
 841 	gtk_label_set_label (GTK_LABEL (page->priv->login_status_label), label_text);
 842 	gtk_button_set_label (GTK_BUTTON (page->priv->login_response_button), button_text);
 843 	if (show_login_bar == TRUE) {
 844 		gtk_widget_show_all (page->priv->login_bar);
 845 	} else {
 846 		gtk_widget_hide (page->priv->login_bar);
 847 	}
 848 	if (show_profile == TRUE) {
 849 		gtk_label_set_label (GTK_LABEL (page->priv->username_label),
 850 			             username);
 851 		gtk_widget_show (page->priv->username_label);
 852 		gtk_widget_show (page->priv->profile_window);
 853 	} else {
 854 		gtk_widget_hide (page->priv->profile_window);
 855 	}
 856 
 857 	g_free (label_text);
 858 	g_free (button_text);
 859 }
 860 
 861 void
 862 scrobbling_enabled_check_toggled_cb (GtkToggleButton *togglebutton,
 863                                      RBAudioscrobblerProfilePage *page)
 864 {
 865 	g_settings_set_boolean (page->priv->settings,
 866 				AUDIOSCROBBLER_SCROBBLING_ENABLED_KEY,
 867 				gtk_toggle_button_get_active (togglebutton));
 868 }
 869 
 870 static void
 871 scrobbler_settings_changed_cb (GSettings *settings,
 872 			       const char *key,
 873                                RBAudioscrobblerProfilePage *page)
 874 {
 875 	gboolean enabled;
 876 	if (g_strcmp0 (key, AUDIOSCROBBLER_SCROBBLING_ENABLED_KEY) != 0) {
 877 		return;
 878 	}
 879 
 880 	enabled = g_settings_get_boolean (settings, key);
 881 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (page->priv->scrobbling_enabled_check),
 882 	                              enabled);
 883 
 884 	if (page->priv->audioscrobbler != NULL && enabled == FALSE) {
 885 		g_object_unref (page->priv->audioscrobbler);
 886 		page->priv->audioscrobbler = NULL;
 887 		gtk_label_set_label (GTK_LABEL (page->priv->scrobbler_status_msg_label),
 888 		                     _("Disabled"));
 889 	} else if (page->priv->audioscrobbler == NULL && enabled == TRUE) {
 890 		RBShell *shell;
 891 		RBShellPlayer *shell_player;
 892 		g_object_get (page, "shell", &shell, NULL);
 893 		g_object_get (shell, "shell-player", &shell_player, NULL);
 894 		page->priv->audioscrobbler =
 895 			rb_audioscrobbler_new (page->priv->service,
 896 				               shell_player,
 897                                                rb_audioscrobbler_account_get_username (page->priv->account),
 898 			                       rb_audioscrobbler_account_get_session_key (page->priv->account));
 899 		g_signal_connect (page->priv->audioscrobbler,
 900 			          "authentication-error",
 901 			          G_CALLBACK (scrobbler_authentication_error_cb),
 902 			          page);
 903 		g_signal_connect (page->priv->audioscrobbler,
 904 			          "statistics-changed",
 905 			          G_CALLBACK (scrobbler_statistics_changed_cb),
 906 			          page);
 907 		rb_audioscrobbler_statistics_changed (page->priv->audioscrobbler);
 908 		g_object_unref (shell_player);
 909 		g_object_unref (shell);
 910 	}
 911 }
 912 
 913 static void
 914 scrobbler_authentication_error_cb (RBAudioscrobbler *audioscrobbler,
 915                                    RBAudioscrobblerProfilePage *page)
 916 {
 917 	rb_audioscrobbler_account_notify_of_auth_error (page->priv->account);
 918 }
 919 
 920 static void
 921 scrobbler_statistics_changed_cb (RBAudioscrobbler *audioscrobbler,
 922                                  const char *status_msg,
 923                                  guint queue_count,
 924                                  guint submit_count,
 925                                  const char *submit_time,
 926                                  RBAudioscrobblerProfilePage *page)
 927 {
 928 	char *queue_count_text;
 929 	char *submit_count_text;
 930 
 931 	gtk_label_set_text (GTK_LABEL (page->priv->scrobbler_status_msg_label), status_msg);
 932 
 933 	queue_count_text = g_strdup_printf ("%u", queue_count);
 934 	gtk_label_set_text (GTK_LABEL (page->priv->scrobbler_queue_count_label), queue_count_text);
 935 
 936 	submit_count_text = g_strdup_printf ("%u", submit_count);
 937 	gtk_label_set_text (GTK_LABEL (page->priv->scrobbler_submit_count_label), submit_count_text);
 938 
 939 	gtk_label_set_text (GTK_LABEL (page->priv->scrobbler_submit_time_label), submit_time);
 940 
 941 	g_free (queue_count_text);
 942 	g_free (submit_count_text);
 943 }
 944 
 945 static void
 946 playing_song_changed_cb (RBShellPlayer *player,
 947                          RhythmDBEntry *entry,
 948                          RBAudioscrobblerProfilePage *page)
 949 {
 950 	update_service_actions_sensitivity (page, entry);
 951 }
 952 
 953 static void
 954 update_service_actions_sensitivity (RBAudioscrobblerProfilePage *page, RhythmDBEntry *entry)
 955 {
 956 	GtkAction *love;
 957 	GtkAction *ban;
 958 	GtkAction *download;
 959 
 960 	/* enable love/ban if an entry is playing */
 961 	love = gtk_action_group_get_action (page->priv->service_action_group, page->priv->love_action_name);
 962 	ban = gtk_action_group_get_action (page->priv->service_action_group, page->priv->ban_action_name);
 963 	if (entry == NULL) {
 964 		gtk_action_set_sensitive (love, FALSE);
 965 		gtk_action_set_sensitive (ban, FALSE);
 966 	} else {
 967 		gtk_action_set_sensitive (love, TRUE);
 968 		gtk_action_set_sensitive (ban, TRUE);
 969 	}
 970 
 971 	/* enable download if the playing entry is a radio track from this service which provides a download url */
 972 	download = gtk_action_group_get_action (page->priv->service_action_group, page->priv->download_action_name);
 973 	if (entry != NULL &&
 974 	    rhythmdb_entry_get_entry_type (entry) == RHYTHMDB_ENTRY_TYPE_AUDIOSCROBBLER_RADIO_TRACK) {
 975 		RBAudioscrobblerRadioTrackData *data;
 976 		data = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RBAudioscrobblerRadioTrackData);
 977 
 978 		if (data->service == page->priv->service && data->download_url != NULL) {
 979 			gtk_action_set_sensitive (download, TRUE);
 980 		} else {
 981 			gtk_action_set_sensitive (download, FALSE);
 982 		}
 983 	} else {
 984 		gtk_action_set_sensitive (download, FALSE);
 985 	}
 986 }
 987 
 988 static void
 989 love_track_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page)
 990 {
 991 	RBShell *shell;
 992 	RBShellPlayer *shell_player;
 993 	RhythmDBEntry *playing;
 994 	GtkAction *ban_action;
 995 
 996 	g_object_get (page, "shell", &shell, NULL);
 997 	g_object_get (shell, "shell-player", &shell_player, NULL);
 998 	playing = rb_shell_player_get_playing_entry (shell_player);
 999 
1000 	if (playing != NULL) {
1001 		rb_audioscrobbler_user_love_track (page->priv->user,
1002 			                           rhythmdb_entry_get_string (playing, RHYTHMDB_PROP_TITLE),
1003 			                           rhythmdb_entry_get_string (playing, RHYTHMDB_PROP_ARTIST));
1004 		rhythmdb_entry_unref (playing);
1005 	}
1006 
1007 	/* disable love/ban */
1008 	gtk_action_set_sensitive (action, FALSE);
1009 	ban_action = gtk_action_group_get_action (page->priv->service_action_group, page->priv->ban_action_name);
1010 	gtk_action_set_sensitive (ban_action, FALSE);
1011 
1012 	g_object_unref (shell_player);
1013 	g_object_unref (shell);
1014 }
1015 
1016 static void
1017 ban_track_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page)
1018 {
1019 	RBShell *shell;
1020 	RBShellPlayer *shell_player;
1021 	RhythmDBEntry *playing;
1022 
1023 	g_object_get (page, "shell", &shell, NULL);
1024 	g_object_get (shell, "shell-player", &shell_player, NULL);
1025 	playing = rb_shell_player_get_playing_entry (shell_player);
1026 
1027 	if (playing != NULL) {
1028 		rb_audioscrobbler_user_ban_track (page->priv->user,
1029 			                          rhythmdb_entry_get_string (playing, RHYTHMDB_PROP_TITLE),
1030 			                          rhythmdb_entry_get_string (playing, RHYTHMDB_PROP_ARTIST));
1031 		rhythmdb_entry_unref (playing);
1032 	}
1033 
1034 	/* skip to next track */
1035 	rb_shell_player_do_next (shell_player, NULL);
1036 
1037 	g_object_unref (shell_player);
1038 	g_object_unref (shell);
1039 }
1040 
1041 static void
1042 download_track_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page)
1043 {
1044 	RBShell *shell;
1045 	RBShellPlayer *shell_player;
1046 	RhythmDBEntry *playing;
1047 
1048 	/* disable the action */
1049 	gtk_action_set_sensitive (action, FALSE);
1050 
1051 	g_object_get (page, "shell", &shell, NULL);
1052 	g_object_get (shell, "shell-player", &shell_player, NULL);
1053 	playing = rb_shell_player_get_playing_entry (shell_player);
1054 
1055 	if (playing != NULL &&
1056 	    rhythmdb_entry_get_entry_type (playing) == RHYTHMDB_ENTRY_TYPE_AUDIOSCROBBLER_RADIO_TRACK) {
1057 		RBAudioscrobblerRadioTrackData *data;
1058 		data = RHYTHMDB_ENTRY_GET_TYPE_DATA (playing, RBAudioscrobblerRadioTrackData);
1059 
1060 		if (data->download_url != NULL) {
1061 			RhythmDB *db;
1062 			RBSource *library;
1063 			RhythmDBEntry *download;
1064 			GValue val = { 0, };
1065 			RBTrackTransferBatch *batch;
1066 
1067 			/* we need the library source to paste into */
1068 			g_object_get (shell, "db", &db, "library-source", &library, NULL);
1069 
1070 			/* create a new entry to paste */
1071 			download = rhythmdb_entry_new (db,
1072 			                               RHYTHMDB_ENTRY_TYPE_AUDIOSCROBBLER_RADIO_TRACK, /* not really, but it needs a type */
1073 			                               data->download_url);
1074 
1075 			g_value_init (&val, G_TYPE_STRING);
1076 			g_value_set_string (&val, rhythmdb_entry_get_string (playing, RHYTHMDB_PROP_TITLE));
1077 			rhythmdb_entry_set (db, download, RHYTHMDB_PROP_TITLE, &val);
1078 			g_value_unset (&val);
1079 			g_value_init (&val, G_TYPE_STRING);
1080 			g_value_set_string (&val, rhythmdb_entry_get_string (playing, RHYTHMDB_PROP_ARTIST));
1081 			rhythmdb_entry_set (db, download, RHYTHMDB_PROP_ARTIST, &val);
1082 			g_value_unset (&val);
1083 			g_value_init (&val, G_TYPE_STRING);
1084 			g_value_set_string (&val, rhythmdb_entry_get_string (playing, RHYTHMDB_PROP_ALBUM));
1085 			rhythmdb_entry_set (db, download, RHYTHMDB_PROP_ALBUM, &val);
1086 			g_value_unset (&val);
1087 
1088 			rb_debug ("downloading track from %s", data->download_url);
1089 			batch = rb_source_paste (library, g_list_append (NULL, download));
1090 
1091 			if (batch == NULL) {
1092 				/* delete the entry we just created */
1093 				rhythmdb_entry_delete (db, download);
1094 				rhythmdb_entry_unref (download);
1095 			} else {
1096 				/* connect a callback to delete the entry when transfer is done */
1097 				g_signal_connect_object (batch,
1098 				                         "complete",
1099 				                         G_CALLBACK (download_track_batch_complete_cb),
1100 				                         page,
1101 				                         0);
1102 			}
1103 
1104 			g_object_unref (db);
1105 			g_object_unref (library);
1106 		} else {
1107 			rb_debug ("cannot download: no download url");
1108 		}
1109 		rhythmdb_entry_unref (playing);
1110 	} else {
1111 		rb_debug ("cannot download: playing entry is not an audioscrobbler radio track");
1112 	}
1113 
1114 	g_object_unref (shell_player);
1115 	g_object_unref (shell);
1116 }
1117 
1118 static void
1119 download_track_batch_complete_cb (RBTrackTransferBatch *batch,
1120                                   RBAudioscrobblerProfilePage *page)
1121 {
1122 	GList *entries;
1123 	RBShell *shell;
1124 	RhythmDB *db;
1125 	GList *i;
1126 
1127 	g_object_get (batch, "entry-list", &entries, NULL);
1128 	g_object_get (page, "shell", &shell, NULL);
1129 	g_object_get (shell, "db", &db, NULL);
1130 
1131 	/* delete the entries which were transfered.
1132 	 * need to call unref twice as g_object_get will have reffed them
1133 	 */
1134 	for (i = entries; i != NULL; i = i->next) {
1135 		rhythmdb_entry_delete (db, i->data);
1136 		rhythmdb_entry_unref (i->data);
1137 		rhythmdb_entry_unref (i->data);
1138 	}
1139 
1140 	g_list_free (entries);
1141 	g_object_unref (shell);
1142 	g_object_unref (db);
1143 }
1144 
1145 static void
1146 refresh_profile_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page)
1147 {
1148 	rb_audioscrobbler_user_force_update (page->priv->user);
1149 }
1150 
1151 void
1152 station_creator_button_clicked_cb (GtkButton *button,
1153                                    RBAudioscrobblerProfilePage *page)
1154 {
1155 	const char *arg;
1156 
1157 	arg = gtk_entry_get_text (GTK_ENTRY (page->priv->station_creator_arg_entry));
1158 
1159 	if (arg[0] != '\0') {
1160 		RBAudioscrobblerRadioType type;
1161 		char *url;
1162 		char *name;
1163 		RBSource *radio;
1164 		RBShell *shell;
1165 		RBDisplayPageTree *page_tree;
1166 
1167 		type = gtk_combo_box_get_active (GTK_COMBO_BOX (page->priv->station_creator_type_combo));
1168 
1169 		url = g_strdup_printf (rb_audioscrobbler_radio_type_get_url (type),
1170 		                       arg);
1171 		name = g_strdup_printf (rb_audioscrobbler_radio_type_get_default_name (type),
1172 		                        arg);
1173 
1174 		radio = add_radio_station (page, url, name);
1175 		g_object_get (page, "shell", &shell, NULL);
1176 		g_object_get (shell, "display-page-tree", &page_tree, NULL);
1177 		rb_display_page_tree_select (page_tree, RB_DISPLAY_PAGE (radio));
1178 
1179 		gtk_entry_set_text (GTK_ENTRY (page->priv->station_creator_arg_entry), "");
1180 
1181 		g_free (url);
1182 		g_free (name);
1183 		g_object_unref (shell);
1184 		g_object_unref (page_tree);
1185 	}
1186 }
1187 
1188 /* delete old user's radio sources and load ones for new user */
1189 static void
1190 load_radio_stations (RBAudioscrobblerProfilePage *page)
1191 {
1192 	/* destroy existing sources */
1193 	while (page->priv->radio_sources != NULL) {
1194 		rb_display_page_delete_thyself (RB_DISPLAY_PAGE (page->priv->radio_sources->data));
1195 		page->priv->radio_sources = g_list_remove (page->priv->radio_sources, page->priv->radio_sources->data);
1196 	}
1197 
1198 	/* load the user's saved stations */
1199 	if (rb_audioscrobbler_account_get_username (page->priv->account) != NULL) {
1200 		JsonParser *parser;
1201 		char *filename;
1202 
1203 		parser = json_parser_new ();
1204 		filename = g_build_filename (rb_user_data_dir (),
1205 		                             "audioscrobbler",
1206 		                             "stations",
1207 		                             rb_audioscrobbler_service_get_name (page->priv->service),
1208 		                             rb_audioscrobbler_account_get_username (page->priv->account),
1209 		                             NULL);
1210 
1211 		if (json_parser_load_from_file (parser, filename, NULL)) {
1212 			JsonArray *stations;
1213 			int i;
1214 
1215 			stations = json_node_get_array (json_parser_get_root (parser));
1216 
1217 			for (i = 0; i < json_array_get_length (stations); i++) {
1218 				JsonObject *station;
1219 				const char *name;
1220 				const char *url;
1221 				RBSource *radio;
1222 
1223 				station = json_array_get_object_element (stations, i);
1224 				name = json_object_get_string_member (station, "name");
1225 				url = json_object_get_string_member (station, "url");
1226 
1227 				radio = rb_audioscrobbler_radio_source_new (page,
1228 				                                            page->priv->service,
1229 				                                            rb_audioscrobbler_account_get_username (page->priv->account),
1230 				                                            rb_audioscrobbler_account_get_session_key (page->priv->account),
1231 				                                            name,
1232 				                                            url);
1233 				page->priv->radio_sources = g_list_append (page->priv->radio_sources, radio);
1234 				g_signal_connect (radio, "notify::name",
1235 						  G_CALLBACK (radio_station_name_changed_cb),
1236 						  page);
1237 			}
1238 		}
1239 
1240 		/* if the list of stations is still empty then add some defaults */
1241 		if (page->priv->radio_sources == NULL) {
1242 			char *url;
1243 			char *name;
1244 
1245 			/* user's library */
1246 			url = g_strdup_printf (rb_audioscrobbler_radio_type_get_url (RB_AUDIOSCROBBLER_RADIO_TYPE_LIBRARY),
1247 			                       rb_audioscrobbler_account_get_username (page->priv->account));
1248 			name = g_strdup (_("My Library"));
1249 			add_radio_station (page, url, name);
1250 			g_free (url);
1251 			g_free (name);
1252 
1253 			/* user's recommendations */
1254 			url = g_strdup_printf (rb_audioscrobbler_radio_type_get_url (RB_AUDIOSCROBBLER_RADIO_TYPE_RECOMMENDATION),
1255 			                       rb_audioscrobbler_account_get_username (page->priv->account));
1256 			name = g_strdup (_("My Recommendations"));
1257 			add_radio_station (page, url, name);
1258 			g_free (url);
1259 			g_free (name);
1260 
1261 			/* user's neighbourhood */
1262 			url = g_strdup_printf (rb_audioscrobbler_radio_type_get_url (RB_AUDIOSCROBBLER_RADIO_TYPE_NEIGHBOURS),
1263 			                       rb_audioscrobbler_account_get_username (page->priv->account));
1264 			name = g_strdup (_("My Neighbourhood"));
1265 			add_radio_station (page, url, name);
1266 			g_free (url);
1267 			g_free (name);
1268 
1269 			/* rhythmbox group */
1270 			url = g_strdup_printf (rb_audioscrobbler_radio_type_get_url (RB_AUDIOSCROBBLER_RADIO_TYPE_GROUP),
1271 			                       "rhythmbox");
1272 			name = g_strdup_printf (rb_audioscrobbler_radio_type_get_default_name (RB_AUDIOSCROBBLER_RADIO_TYPE_GROUP),
1273 			                       "Rhythmbox");
1274 			add_radio_station (page, url, name);
1275 			g_free (url);
1276 			g_free (name);
1277 		}
1278 
1279 		g_object_unref (parser);
1280 		g_free (filename);
1281 	}
1282 }
1283 
1284 /* save user's radio stations */
1285 static void
1286 save_radio_stations (RBAudioscrobblerProfilePage *page)
1287 {
1288 	JsonNode *root;
1289 	JsonArray *stations;
1290 	GList *i;
1291 	JsonGenerator *generator;
1292 	char *filename;
1293 	char *uri;
1294 	GError *error;
1295 
1296 	root = json_node_new (JSON_NODE_ARRAY);
1297 	stations = json_array_new ();
1298 
1299 	for (i = page->priv->radio_sources; i != NULL; i = i->next) {
1300 		JsonObject *station;
1301 		char *name;
1302 		char *url;
1303 
1304 		g_object_get (i->data, "name", &name, "station-url", &url, NULL);
1305 		station = json_object_new ();
1306 		json_object_set_string_member (station, "name", name);
1307 		json_object_set_string_member (station, "url", url);
1308 		json_array_add_object_element (stations, station);
1309 
1310 		g_free (name);
1311 		g_free (url);
1312 	}
1313 
1314 	json_node_take_array (root, stations);
1315 
1316 	generator = json_generator_new ();
1317 	json_generator_set_root (generator, root);
1318 
1319 	filename = g_build_filename (rb_user_data_dir (),
1320 	                             "audioscrobbler",
1321 	                             "stations",
1322 	                             rb_audioscrobbler_service_get_name (page->priv->service),
1323 	                             rb_audioscrobbler_account_get_username (page->priv->account),
1324 	                             NULL);
1325 
1326 	uri = g_filename_to_uri (filename, NULL, NULL);
1327 	error = NULL;
1328 	rb_uri_create_parent_dirs (uri, &error);
1329 	json_generator_to_file (generator, filename, NULL);
1330 
1331 	json_node_free (root);
1332 	g_object_unref (generator);
1333 	g_free (filename);
1334 	g_free (uri);
1335 }
1336 
1337 /* adds a new radio station for the user, if it doesn't already exist */
1338 static RBSource *
1339 add_radio_station (RBAudioscrobblerProfilePage *page,
1340                    const char *url,
1341                    const char *name)
1342 {
1343 	GList *i;
1344 	RBSource *radio = NULL;
1345 
1346 	/* check for existing station */
1347 	for (i = page->priv->radio_sources; i != NULL; i = i->next) {
1348 		char *existing_url;
1349 		g_object_get (i->data, "station-url", &existing_url, NULL);
1350 
1351 		if (strcmp (existing_url, url) == 0) {
1352 			radio = i->data;
1353 		}
1354 
1355 		g_free (existing_url);
1356 	}
1357 
1358 	if (radio == NULL) {
1359 		const char *username;
1360 		const char *session_key;
1361 		RBShell *shell;
1362 
1363 		username = rb_audioscrobbler_account_get_username (page->priv->account);
1364 		session_key = rb_audioscrobbler_account_get_session_key (page->priv->account);
1365 		g_object_get (page, "shell", &shell, NULL);
1366 
1367 		radio = rb_audioscrobbler_radio_source_new (page,
1368 		                                            page->priv->service,
1369 		                                            username,
1370 		                                            session_key,
1371 		                                            name,
1372 		                                            url);
1373 		page->priv->radio_sources = g_list_append (page->priv->radio_sources, radio);
1374 		g_signal_connect (radio, "notify::name",
1375 		                  G_CALLBACK (radio_station_name_changed_cb),
1376 		                  page);
1377 		save_radio_stations (page);
1378 
1379 		g_object_unref (shell);
1380 	}
1381 
1382 	return radio;
1383 }
1384 
1385 /* called when a radio station's name changes */
1386 static void
1387 radio_station_name_changed_cb (RBAudioscrobblerRadioSource *radio,
1388                                GParamSpec *spec,
1389                                RBAudioscrobblerProfilePage *page)
1390 {
1391 	/* save list of stations with new name */
1392 	save_radio_stations (page);
1393 }
1394 
1395 /* removes a station from user's list of radio stations, deletes the source */
1396 void
1397 rb_audioscrobbler_profile_page_remove_radio_station (RBAudioscrobblerProfilePage *page,
1398 						     RBSource *station)
1399 {
1400 	GList *i;
1401 
1402 	i = g_list_find (page->priv->radio_sources, station);
1403 
1404 	if (i != NULL) {
1405 		rb_display_page_delete_thyself (i->data);
1406 		page->priv->radio_sources = g_list_remove (page->priv->radio_sources, i->data);
1407 		save_radio_stations (page);
1408 	}
1409 }
1410 
1411 static gboolean
1412 update_timeout_cb (RBAudioscrobblerProfilePage *page)
1413 {
1414 	rb_audioscrobbler_user_update (page->priv->user);
1415 
1416 	return TRUE;
1417 }
1418 
1419 static void
1420 user_info_updated_cb (RBAudioscrobblerUser *user,
1421                       RBAudioscrobblerUserData *data,
1422                       RBAudioscrobblerProfilePage *page)
1423 {
1424 	if (data != NULL) {
1425 		char *playcount_text;
1426 
1427 		gtk_label_set_label (GTK_LABEL (page->priv->username_label),
1428 			             data->user_info.username);
1429 		gtk_widget_show (page->priv->username_label);
1430 
1431 		playcount_text = g_strdup_printf (_("%s plays"), data->user_info.playcount);
1432 		gtk_label_set_label (GTK_LABEL (page->priv->playcount_label),
1433 		                     playcount_text);
1434 		g_free (playcount_text);
1435 		gtk_widget_show (page->priv->playcount_label);
1436 
1437 		gtk_link_button_set_uri (GTK_LINK_BUTTON (page->priv->view_profile_link),
1438 		                         data->url);
1439 		gtk_widget_show (page->priv->view_profile_link);
1440 
1441 		if (data->image != NULL) {
1442 			gtk_image_set_from_pixbuf (GTK_IMAGE (page->priv->profile_image), data->image);
1443 			/* show the parent because the image is packed in a viewport so it has a shadow */
1444 			gtk_widget_show (gtk_widget_get_parent (page->priv->profile_image));
1445 		} else {
1446 			gtk_widget_hide (gtk_widget_get_parent (page->priv->profile_image));
1447 		}
1448 	} else {
1449 		gtk_widget_hide (page->priv->playcount_label);
1450 		gtk_widget_hide (page->priv->view_profile_link);
1451 		gtk_widget_hide (gtk_widget_get_parent (page->priv->profile_image));
1452 	}
1453 }
1454 
1455 static void
1456 recent_tracks_updated_cb (RBAudioscrobblerUser *user,
1457                           GPtrArray *recent_tracks,
1458                           RBAudioscrobblerProfilePage *page)
1459 {
1460 	set_user_list (page, page->priv->recent_tracks_wrap_box, recent_tracks);
1461 
1462 	if (recent_tracks != NULL && recent_tracks->len != 0) {
1463 		gtk_widget_show_all (page->priv->recent_tracks_area);
1464 	} else {
1465 		gtk_widget_hide (page->priv->recent_tracks_area);
1466 	}
1467 }
1468 
1469 static void
1470 top_tracks_updated_cb (RBAudioscrobblerUser *user,
1471                        GPtrArray *top_tracks,
1472                        RBAudioscrobblerProfilePage *page)
1473 {
1474 	set_user_list (page, page->priv->top_tracks_wrap_box, top_tracks);
1475 
1476 	if (top_tracks != NULL && top_tracks->len != 0) {
1477 		gtk_widget_show_all (page->priv->top_tracks_area);
1478 	} else {
1479 		gtk_widget_hide (page->priv->top_tracks_area);
1480 	}
1481 }
1482 
1483 static void
1484 loved_tracks_updated_cb (RBAudioscrobblerUser *user,
1485                          GPtrArray *loved_tracks,
1486                          RBAudioscrobblerProfilePage *page)
1487 {
1488 	set_user_list (page, page->priv->loved_tracks_wrap_box, loved_tracks);
1489 
1490 	if (loved_tracks != NULL && loved_tracks->len != 0) {
1491 		gtk_widget_show_all (page->priv->loved_tracks_area);
1492 	} else {
1493 		gtk_widget_hide (page->priv->loved_tracks_area);
1494 	}
1495 }
1496 
1497 static void
1498 top_artists_updated_cb (RBAudioscrobblerUser *user,
1499                         GPtrArray *top_artists,
1500                         RBAudioscrobblerProfilePage *page)
1501 {
1502 	set_user_list (page, page->priv->top_artists_wrap_box, top_artists);
1503 
1504 	if (top_artists != NULL && top_artists->len != 0) {
1505 		gtk_widget_show_all (page->priv->top_artists_area);
1506 	} else {
1507 		gtk_widget_hide (page->priv->top_artists_area);
1508 	}
1509 }
1510 
1511 static void
1512 recommended_artists_updated_cb (RBAudioscrobblerUser *user,
1513                                 GPtrArray *recommended_artists,
1514                                 RBAudioscrobblerProfilePage *page)
1515 {
1516 	set_user_list (page, page->priv->recommended_artists_wrap_box, recommended_artists);
1517 
1518 	if (recommended_artists != NULL && recommended_artists->len != 0) {
1519 		gtk_widget_show_all (page->priv->recommended_artists_area);
1520 	} else {
1521 		gtk_widget_hide (page->priv->recommended_artists_area);
1522 	}
1523 }
1524 
1525 /* Creates a list of buttons packed in a wrap box for a list of data
1526  * eg user's top tracks or recommended artists
1527  */
1528 static void
1529 set_user_list (RBAudioscrobblerProfilePage *page,
1530                GtkWidget *list_wrap_box,
1531                GPtrArray *list_data)
1532 {
1533 	GList *button_node;
1534 
1535 	/* delete all existing buttons */
1536 	for (button_node = gtk_container_get_children (GTK_CONTAINER (list_wrap_box));
1537 	     button_node != NULL;
1538 	     button_node = g_list_next (button_node)) {
1539 		GtkMenu *menu;
1540 		menu = g_hash_table_lookup (page->priv->button_to_popup_menu_map, button_node->data);
1541 		g_hash_table_remove (page->priv->button_to_popup_menu_map, button_node->data);
1542 		g_hash_table_remove (page->priv->popup_menu_to_data_map, menu);
1543 		gtk_widget_destroy (button_node->data);
1544 	}
1545 
1546 	if (list_data != NULL) {
1547 		int i;
1548 		int max_image_width;
1549 
1550 		/* get the width of the widest image */
1551 		max_image_width = 0;
1552 		for (i = 0; i < list_data->len; i++) {
1553 			RBAudioscrobblerUserData *data;
1554 
1555 			data = g_ptr_array_index (list_data, i);
1556 			if (data->image != NULL) {
1557 				int width = gdk_pixbuf_get_width (data->image);
1558 				max_image_width = MAX (max_image_width, width);
1559 			}
1560 
1561 		}
1562 
1563 		/* add a new button for each item in the list */
1564 		for (i = 0; i < list_data->len; i++) {
1565 			RBAudioscrobblerUserData *data;
1566 			GtkWidget *button;
1567 			GtkWidget *menu;
1568 
1569 			data = g_ptr_array_index (list_data, i);
1570 			button = create_list_button (page, data, max_image_width);
1571 			menu = create_popup_menu (page, data);
1572 
1573 			g_hash_table_insert (page->priv->button_to_popup_menu_map, button, g_object_ref_sink (menu));
1574 			g_hash_table_insert (page->priv->popup_menu_to_data_map, menu, data);
1575 
1576 			egg_wrap_box_insert_child (EGG_WRAP_BOX (list_wrap_box),
1577 			                           button,
1578 			                           -1,
1579 			                           EGG_WRAP_BOX_H_EXPAND);
1580 		}
1581 	}
1582 }
1583 
1584 /* creates a button for use in a list */
1585 static GtkWidget *
1586 create_list_button (RBAudioscrobblerProfilePage *page,
1587                     RBAudioscrobblerUserData *data,
1588                     int max_sibling_image_width)
1589 {
1590 	GtkWidget *button;
1591 	GtkWidget *button_contents;
1592 	char *button_markup;
1593 	int label_indent;
1594 	GtkWidget *label;
1595 	GtkWidget *label_alignment;
1596 
1597 	button = gtk_button_new ();
1598 	gtk_button_set_alignment (GTK_BUTTON (button),
1599 		                  0, 0.5);
1600 	gtk_button_set_focus_on_click (GTK_BUTTON (button),
1601 		                       FALSE);
1602 	gtk_button_set_relief (GTK_BUTTON (button),
1603 		               GTK_RELIEF_NONE);
1604 
1605 	button_contents = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
1606 	gtk_container_add (GTK_CONTAINER (button), button_contents);
1607 
1608 	if (data->image != NULL) {
1609 		GtkWidget *image;
1610 		GtkWidget *viewport;
1611 		GtkWidget *alignment;
1612 
1613 		image = gtk_image_new_from_pixbuf (data->image);
1614 
1615 		viewport = gtk_viewport_new (NULL, NULL);
1616 		gtk_container_add (GTK_CONTAINER (viewport), image);
1617 
1618 		alignment = gtk_alignment_new (0, 0.5, 0, 0);
1619 		gtk_container_add (GTK_CONTAINER (alignment), viewport);
1620 
1621 		gtk_box_pack_start (GTK_BOX (button_contents),
1622 		                    alignment,
1623 		                    FALSE, FALSE, 0);
1624 
1625 		label_indent = max_sibling_image_width - gdk_pixbuf_get_width (data->image);
1626 	} else {
1627 		label_indent = 4;
1628 	}
1629 
1630 	button_markup = NULL;
1631 	if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_TRACK) {
1632 		char *escaped_title_text;
1633 		char *escaped_artist_text;
1634 
1635 		escaped_title_text = g_markup_escape_text (data->track.title, -1);
1636 		escaped_artist_text = g_markup_escape_text (data->track.artist, -1);
1637 		button_markup = g_strdup_printf ("%s\n<small>%s</small>",
1638 			                         escaped_title_text,
1639 			                         escaped_artist_text);
1640 
1641 		g_free (escaped_title_text);
1642 		g_free (escaped_artist_text);
1643 
1644 	} else if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_ARTIST) {
1645 		button_markup = g_markup_escape_text (data->artist.name, -1);
1646 	}
1647 
1648 	label = gtk_label_new ("");
1649 	gtk_label_set_markup (GTK_LABEL (label), button_markup);
1650 	g_free (button_markup);
1651 
1652 	label_alignment = gtk_alignment_new (0, 0.5, 0, 0);
1653 	gtk_container_add (GTK_CONTAINER (label_alignment), label);
1654 
1655 	gtk_alignment_set_padding (GTK_ALIGNMENT (label_alignment),
1656 	                           0, 0,
1657 	                           label_indent, 0);
1658 
1659 	gtk_box_pack_start (GTK_BOX (button_contents),
1660 	                    label_alignment,
1661 	                    FALSE, FALSE, 0);
1662 
1663 	g_signal_connect (button,
1664 		          "clicked",
1665 		          G_CALLBACK (list_item_clicked_cb),
1666 		          page);
1667 
1668 	return button;
1669 }
1670 
1671 /* creates a menu to be popped up when a button is clicked */
1672 static GtkWidget *
1673 create_popup_menu (RBAudioscrobblerProfilePage *page,
1674                    RBAudioscrobblerUserData *data)
1675 {
1676 	GtkWidget *menu;
1677 
1678 	menu = gtk_menu_new ();
1679 
1680 	/* Visit on website */
1681 	if (data->url != NULL && data->url[0] != '\0') {
1682 		GtkWidget *view_url_item;
1683 		char *item_text;
1684 
1685 		/* Translators: %s is the name of the audioscrobbler service, for example "Last.fm".
1686 		 * This is the label for menu item which when activated will take the user to the
1687 		 * artist/track's page on the service's website. */
1688 		item_text = g_strdup_printf (_("_View on %s"),
1689 		                             rb_audioscrobbler_service_get_name (page->priv->service));
1690 		view_url_item = gtk_menu_item_new_with_mnemonic (item_text);
1691 		g_signal_connect (view_url_item,
1692 				  "activate",
1693 				  G_CALLBACK (list_item_view_url_activated_cb),
1694 				  page);
1695 
1696 		gtk_menu_shell_append (GTK_MENU_SHELL (menu), view_url_item);
1697 		g_free (item_text);
1698 	}
1699 
1700 	/* Similar artists radio */
1701 	if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_TRACK ||
1702 	    data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_ARTIST) {
1703 		GtkWidget *similar_artists_item;
1704 
1705 		similar_artists_item = gtk_menu_item_new_with_mnemonic (_("Listen to _Similar Artists Radio"));
1706 		g_signal_connect (similar_artists_item,
1707 				  "activate",
1708 				  G_CALLBACK (list_item_listen_similar_artists_activated_cb),
1709 				  page);
1710 
1711 		gtk_menu_shell_append (GTK_MENU_SHELL (menu), similar_artists_item);
1712 	}
1713 
1714 	/* Top fans radio */
1715 	if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_TRACK ||
1716 	    data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_ARTIST) {
1717 		GtkWidget *top_fans_item;
1718 
1719 		top_fans_item = gtk_menu_item_new_with_mnemonic (_("Listen to _Top Fans Radio"));
1720 		g_signal_connect (top_fans_item,
1721 				  "activate",
1722 				  G_CALLBACK (list_item_listen_top_fans_activated_cb),
1723 				  page);
1724 
1725 		gtk_menu_shell_append (GTK_MENU_SHELL (menu), top_fans_item);
1726 	}
1727 
1728 	gtk_widget_show_all (menu);
1729 
1730 	return menu;
1731 }
1732 
1733 /* popup the appropriate menu */
1734 static void
1735 list_item_clicked_cb (GtkButton *button, RBAudioscrobblerProfilePage *page)
1736 {
1737 	GtkWidget *menu;
1738 
1739 	menu = g_hash_table_lookup (page->priv->button_to_popup_menu_map, button);
1740 
1741 	/* show menu if it has any items in it */
1742 	if (g_list_length (gtk_container_get_children (GTK_CONTAINER (menu))) != 0) {
1743 		gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time ());
1744 	}
1745 }
1746 
1747 static void
1748 list_item_view_url_activated_cb (GtkMenuItem *menuitem,
1749                                  RBAudioscrobblerProfilePage *page)
1750 {
1751 	GtkWidget *menu;
1752 	RBAudioscrobblerUserData *data;
1753 
1754 	menu = gtk_widget_get_parent (GTK_WIDGET (menuitem));
1755 	data = g_hash_table_lookup (page->priv->popup_menu_to_data_map, menu);
1756 
1757 	/* some urls are given to us without the http:// prefix */
1758 	if (g_str_has_prefix (data->url, "http://") == TRUE) {
1759 		gtk_show_uri (NULL, data->url, GDK_CURRENT_TIME, NULL);
1760 	} else {
1761 		char *url;
1762 		url = g_strdup_printf ("%s%s", "http://", data->url);
1763 		gtk_show_uri (NULL, url, GDK_CURRENT_TIME, NULL);
1764 		g_free (url);
1765 	}
1766 }
1767 
1768 static void
1769 list_item_listen_similar_artists_activated_cb (GtkMenuItem *menuitem,
1770                                                RBAudioscrobblerProfilePage *page)
1771 {
1772 	GtkWidget *menu;
1773 	RBAudioscrobblerUserData *data;
1774 	const char *artist = NULL;
1775 	char *radio_url;
1776 	char *radio_name;
1777 	RBSource *radio;
1778 	RBShell *shell;
1779 	RBDisplayPageTree *page_tree;
1780 
1781 	menu = gtk_widget_get_parent (GTK_WIDGET (menuitem));
1782 	data = g_hash_table_lookup (page->priv->popup_menu_to_data_map, menu);
1783 	if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_ARTIST) {
1784 		artist = data->artist.name;
1785 	} else if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_TRACK) {
1786 		artist = data->track.artist;
1787 	}
1788 
1789 	radio_url = g_strdup_printf (rb_audioscrobbler_radio_type_get_url (RB_AUDIOSCROBBLER_RADIO_TYPE_SIMILAR_ARTISTS),
1790 	                             artist);
1791 	radio_name = g_strdup_printf (rb_audioscrobbler_radio_type_get_default_name (RB_AUDIOSCROBBLER_RADIO_TYPE_SIMILAR_ARTISTS),
1792 	                              artist);
1793 
1794 	radio = add_radio_station (page, radio_url, radio_name);
1795 	g_object_get (page, "shell", &shell, NULL);
1796 	g_object_get (shell, "display-page-tree", &page_tree, NULL);
1797 	rb_display_page_tree_select (page_tree, RB_DISPLAY_PAGE (radio));
1798 
1799 	g_free (radio_url);
1800 	g_free (radio_name);
1801 	g_object_unref (shell);
1802 	g_object_unref (page_tree);
1803 }
1804 
1805 static void
1806 list_item_listen_top_fans_activated_cb (GtkMenuItem *menuitem,
1807                                         RBAudioscrobblerProfilePage *page)
1808 {
1809 	GtkWidget *menu;
1810 	RBAudioscrobblerUserData *data;
1811 	const char *artist = NULL;
1812 	char *radio_url;
1813 	char *radio_name;
1814 	RBSource *radio;
1815 	RBShell *shell;
1816 	RBDisplayPageTree *page_tree;
1817 
1818 	menu = gtk_widget_get_parent (GTK_WIDGET (menuitem));
1819 	data = g_hash_table_lookup (page->priv->popup_menu_to_data_map, menu);
1820 	if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_ARTIST) {
1821 		artist = data->artist.name;
1822 	} else if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_TRACK) {
1823 		artist = data->track.artist;
1824 	}
1825 
1826 	radio_url = g_strdup_printf (rb_audioscrobbler_radio_type_get_url (RB_AUDIOSCROBBLER_RADIO_TYPE_TOP_FANS),
1827 	                             artist);
1828 	radio_name = g_strdup_printf (rb_audioscrobbler_radio_type_get_default_name (RB_AUDIOSCROBBLER_RADIO_TYPE_TOP_FANS),
1829 	                              artist);
1830 
1831 	radio = add_radio_station (page, radio_url, radio_name);
1832 	g_object_get (page, "shell", &shell, NULL);
1833 	g_object_get (shell, "display-page-tree", &page_tree, NULL);
1834 	rb_display_page_tree_select (page_tree, RB_DISPLAY_PAGE (radio));
1835 
1836 	g_free (radio_url);
1837 	g_free (radio_name);
1838 	g_object_unref (shell);
1839 	g_object_unref (page_tree);
1840 }
1841 
1842 static void
1843 impl_selected (RBDisplayPage *bpage)
1844 {
1845 	RBAudioscrobblerProfilePage *page = RB_AUDIOSCROBBLER_PROFILE_PAGE (bpage);
1846 
1847 	RB_DISPLAY_PAGE_CLASS (rb_audioscrobbler_profile_page_parent_class)->selected (bpage);
1848 
1849 	/* attempt to update now and again every 5 minutes */
1850 	rb_audioscrobbler_user_update (page->priv->user);
1851 	page->priv->update_timeout_id = g_timeout_add_seconds (300,
1852 							       (GSourceFunc) update_timeout_cb,
1853 							       page);
1854 }
1855 
1856 static void
1857 impl_deselected (RBDisplayPage *bpage)
1858 {
1859 	RBAudioscrobblerProfilePage *page = RB_AUDIOSCROBBLER_PROFILE_PAGE (bpage);
1860 
1861 	RB_DISPLAY_PAGE_CLASS (rb_audioscrobbler_profile_page_parent_class)->deselected (bpage);
1862 
1863 	g_source_remove (page->priv->update_timeout_id);
1864 	page->priv->update_timeout_id = 0;
1865 }
1866 
1867 static gboolean
1868 impl_show_popup (RBDisplayPage *page)
1869 {
1870 	_rb_display_page_show_popup (page, AUDIOSCROBBLER_PROFILE_PAGE_POPUP_PATH);
1871 	return TRUE;
1872 }
1873 
1874 static void
1875 impl_delete_thyself (RBDisplayPage *bpage)
1876 {
1877 	RBAudioscrobblerProfilePage *page;
1878 	GList *i;
1879 	GtkUIManager *ui_manager;
1880 
1881 	rb_debug ("deleting profile page");
1882 
1883 	page = RB_AUDIOSCROBBLER_PROFILE_PAGE (bpage);
1884 
1885 	for (i = page->priv->radio_sources; i != NULL; i = i->next) {
1886 		rb_display_page_delete_thyself (i->data);
1887 	}
1888 
1889 	g_object_get (page, "ui-manager", &ui_manager, NULL);
1890 	gtk_ui_manager_remove_ui (ui_manager, page->priv->ui_merge_id);
1891 	gtk_ui_manager_remove_action_group (ui_manager, page->priv->service_action_group);
1892 
1893 	g_object_unref (ui_manager);
1894 }
1895 
1896 void
1897 _rb_audioscrobbler_profile_page_register_type (GTypeModule *module)
1898 {
1899 	rb_audioscrobbler_profile_page_register_type (module);
1900 }