/// <summary>
        /// Occurs when we downloaded a loadouts feed from BattleClinic
        /// </summary>
        /// <param name="feed"></param>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        private void OnLoadoutFeedDownloaded(SerializableLoadoutFeed feed, string errorMessage)
        {
            if (this.IsDisposed)
                return;

            // Restore the default cursor instead of the waiting one
            Cursor.Current = Cursors.Default;
            m_selectedLoadout = null;
            btnPlan.Enabled = false;

            // Was there an error ?
            if (!String.IsNullOrEmpty(errorMessage))
            {
                lblLoadouts.Text = String.Format(CultureConstants.DefaultCulture, "There was a problem connecting to BattleClinic, it may be down for maintainance.\r\n{0}", errorMessage);
                return;
            }

            // Are there no feeds ?
            if (feed.Race == null || feed.Race.Loadouts.Length == 0)
            {
                lblLoadouts.Text = String.Format(CultureConstants.DefaultCulture, "There are no loadouts for {0}, why not submit one to BattleClinic?", m_ship.Name);
                return;
            }

            // Add the listview items for every loadout
            lvLoadouts.Items.Clear();
            foreach (SerializableLoadout loadout in feed.Race.Loadouts)
            {
                ListViewItem lvi = new ListViewItem(loadout.LoadoutName);
                lvi.Text = loadout.LoadoutName;
                lvi.SubItems.Add(loadout.Author);
                lvi.SubItems.Add(loadout.rating.ToString());
                lvi.SubItems.Add(loadout.SubmissionDate.ToString());
                lvi.Tag = loadout;
                lvLoadouts.Items.Add(lvi);
            }

            // Update the header
            lblLoadouts.Text = String.Format(CultureConstants.DefaultCulture, "Found {0} loadouts", lvLoadouts.Items.Count);

            // Update the listview's comparer and sort
            lvLoadouts.Sort();
        }
        /// <summary>
        /// Occurs when we downloaded a loadout from BattleClinic
        /// </summary>
        /// <param name="feed"></param>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        private void OnLoadoutDownloaded(SerializableLoadoutFeed loadoutFeed, string errorMessage)
        {
            if (this.IsDisposed)
                return;

            // Reset the controls
            btnPlan.Enabled = false;
            m_prerequisites.Clear();
            tvLoadout.Nodes.Clear();
            Cursor.Current = Cursors.Default;

            // Was there an error ?
            if (!String.IsNullOrEmpty(errorMessage) || loadoutFeed.Race.Loadouts.Length == 0)
            {
                lblTrainTime.Text = String.Format(CultureConstants.DefaultCulture, "Couldn't download that loadout.\r\n{0}", errorMessage);
                lblTrainTime.Visible = true;
                return;
            }

            var loadout = loadoutFeed.Race.Loadouts[0];

            // Fill the items tree
            var slotTypes = loadout.Slots.GroupBy(x => x.SlotType);
            foreach (var slotType in slotTypes)
            {
                TreeNode typeNode = new TreeNode(s_typeMap[slotType.Key]);

                foreach (var slot in slotType)
                {
                    Item item = StaticItems.GetItemByID(slot.ItemID);
                    if (item == null)
                        continue;

                    TreeNode slotNode = new TreeNode();
                    slotNode.Text = item.Name;
                    slotNode.Tag = item;
                    typeNode.Nodes.Add(slotNode);

                    m_prerequisites.AddRange(item.Prerequisites);
                }

                tvLoadout.Nodes.Add(typeNode);
            }

            // Compute the training time
            UpdatePlanningControls();
            tvLoadout.ExpandAll();
        }