/// <summary> /// Updates the training skill info. /// </summary> private void UpdateTrainingSkillInfo() { QueuedSkill training = m_character.CurrentlyTrainingSkill; DateTime completionTime = training.EndTime.ToLocalTime(); lblTrainingSkill.Text = training.ToString(); lblSPPerHour.Text = training.Skill == null ? "???" : $"{training.SkillPointsPerHour} SP/Hour"; lblTrainingEst.Text = $"{completionTime:ddd} {completionTime:G}"; // Dipslay a warning if anything scheduled is blocking us string conflictMessage; bool isAutoBlocking; bool isBlocking = Scheduler.SkillIsBlockedAt(training.EndTime.ToLocalTime(), out conflictMessage, out isAutoBlocking); CCPCharacter ccpCharacter = m_character as CCPCharacter; // Do not show the "DOWNTIME" warning if character's skill queue has more than one skills if (ccpCharacter != null && ccpCharacter.SkillQueue.Count > 1 && isAutoBlocking && string.Equals(conflictMessage, EveMonConstants.DowntimeText, StringComparison.OrdinalIgnoreCase)) { lblScheduleWarning.Visible = false; lblScheduleWarning.Text = string.Empty; return; } lblScheduleWarning.Visible = isBlocking; lblScheduleWarning.Text = conflictMessage; }
/// <summary> /// Updates the skill queue info if queue is paused. /// </summary> /// <returns></returns> private bool SkillQueueIsPaused() { CCPCharacter ccpCharacter = m_character as CCPCharacter; if (ccpCharacter == null || !ccpCharacter.SkillQueue.IsPaused) { return(false); } QueuedSkill training = ccpCharacter.SkillQueue.CurrentlyTraining; lblTrainingSkill.Text = training.ToString(); lblSPPerHour.Text = training.Skill == null ? "???" : $"{training.Skill.SkillPointsPerHour} SP/Hour"; lblTrainingRemain.Text = "Paused"; lblTrainingEst.Text = string.Empty; lblScheduleWarning.Visible = false; skillQueueTimePanel.Visible = false; skillQueuePanel.Visible = true; pnlTraining.Visible = true; lblPaused.Visible = true; return(true); }
/// <summary> /// Updates the skill queue info. /// </summary> private void UpdateSkillQueueInfo() { CCPCharacter ccpCharacter = m_character as CCPCharacter; if (ccpCharacter == null) { return; } DateTime queueCompletionTime = ccpCharacter.SkillQueue.EndTime.ToLocalTime(); lblQueueCompletionTime.Text = $"{queueCompletionTime:ddd} {queueCompletionTime:G}"; // Skill queue time panel skillQueueTimePanel.Visible = ccpCharacter.SkillQueue.Count > 1 || Settings.UI.MainWindow.AlwaysShowSkillQueueTime || (ccpCharacter.SkillQueue.Count == 1 && Settings.UI.MainWindow.AlwaysShowSkillQueueTime); // Update the remaining training time label QueuedSkill training = m_character.CurrentlyTrainingSkill; lblTrainingRemain.Text = training.EndTime.ToRemainingTimeDescription(DateTimeKind.Utc); // Update the remaining queue time label DateTime queueEndTime = ccpCharacter.SkillQueue.EndTime; lblQueueRemaining.Text = queueEndTime.ToRemainingTimeDescription(DateTimeKind.Utc); }
/// <summary> /// Gets the rectangle a skill rendes in within a specified rectange. /// </summary> /// <param name="skill">Skill that exists within the queue</param> /// <param name="width">Width of the canvas</param> /// <param name="height">Height of the canvas</param> /// <returns> /// Rectangle representing the area within the visual /// queue the skill occupies. /// </returns> internal static Rectangle GetSkillRect(QueuedSkill skill, int width, int height) { TimeSpan relativeStart; TimeSpan relativeFinish; // Character is training ? we update the timespan if (skill.Owner.IsTraining) { relativeStart = skill.StartTime.Subtract(DateTime.UtcNow); relativeFinish = skill.EndTime.Subtract(DateTime.UtcNow); } // Timespan is stable else { relativeStart = skill.StartTime.Subtract(m_paintTime); relativeFinish = skill.EndTime.Subtract(m_paintTime); } int TotalSeconds = (int)TimeSpan.FromHours(24).TotalSeconds; double Start = Math.Floor((relativeStart.TotalSeconds / TotalSeconds) * width); double Finish = Math.Floor((relativeFinish.TotalSeconds / TotalSeconds) * width); // If the start time is before now set it to zero if (Start < 0) { Start = 0; } return(new Rectangle((int)Start, 0, (int)(Finish - Start), height)); }
/// <summary> /// Adds the skill boiler plate. /// </summary> /// <param name="toolTip">The tool tip.</param> /// <param name="skill">The skill.</param> private static void AddSkillBoilerPlate(StringBuilder toolTip, QueuedSkill skill) { toolTip.AppendLine().AppendLine(skill.Skill.Description.WordWrap(100)); toolTip.Append("Primary: ").Append(skill.Skill.PrimaryAttribute).Append(", "); toolTip.Append("Secondary: ").Append(skill.Skill.SecondaryAttribute).Append(" ("); toolTip.AppendFormat("{0:F0}", skill.SkillPointsPerHour).Append(" SP/Hour)"); }
/// <summary> /// Draws the queue color bar. /// </summary> /// <param name="skill">The skill.</param> /// <param name="e">The <see cref="System.Windows.Forms.DrawItemEventArgs"/> instance containing the event data.</param> private void DrawQueueColorBar(QueuedSkill skill, DrawItemEventArgs e) { Graphics g = e.Graphics; // Draw the background Rectangle qBarRect = new Rectangle(e.Bounds.Left, GetItemHeight - LowerBoxHeight, e.Bounds.Width, LowerBoxHeight); g.FillRectangle(Brushes.DimGray, qBarRect); double oneDaySkillQueueWidth = Character.SkillQueue.GetOneDaySkillQueueWidth(qBarRect.Width); IList <RectangleF> skillRects = Character.SkillQueue.GetSkillRects(skill, qBarRect.Width, LowerBoxHeight - 1).ToList(); Brush lessThanDayBrush = Settings.UI.SafeForWork ? Brushes.LightGray : Brushes.Khaki; Brush moreThanDayBrush = Settings.UI.SafeForWork ? Brushes.DarkGray : Brushes.CornflowerBlue; RectangleF skillRectFirst = skillRects.First(); // Skill starts before the 24h marker if (skillRectFirst.X < oneDaySkillQueueWidth) { // Iterate only through rectangles with width foreach (RectangleF skillRect in skillRects.Skip(1).Where(rect => rect.Width > 0)) { Brush brush = oneDaySkillQueueWidth - skillRect.X <= 0 ? moreThanDayBrush : lessThanDayBrush; g.FillRectangle(brush, new RectangleF(skillRect.X, GetItemHeight - LowerBoxHeight, skillRect.Width, skillRect.Height)); } return; } g.FillRectangle(moreThanDayBrush, new RectangleF(skillRectFirst.X, GetItemHeight - LowerBoxHeight, skillRectFirst.Width, skillRectFirst.Height)); }
/// <summary> /// On timer tick, we invalidate the training skill display /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void EveClient_TimerTick(object sender, EventArgs e) { if (Character.IsTraining && Visible) { // Retrieves the trained skill for update but quit if the skill is null (was not in our datafiles) QueuedSkill trainingSkill = Character.CurrentlyTrainingSkill; if (trainingSkill == null) { return; } // Invalidate the currently training skill level row int index = lbSkillsQueue.Items.IndexOf(trainingSkill); if (index == 0) { lbSkillsQueue.Invalidate(lbSkillsQueue.GetItemRectangle(index)); } // When there are more than one skill level rows in queue, we invalidate them on a timer if (lbSkillsQueue.Items.Count > 1 && DateTime.Now > m_nextRepainting) { lbSkillsQueue.Invalidate(); } UpdateContent(); } }
/// <summary> /// Process the queue into Google calendar. /// </summary> /// <param name="queuedSkill">The queued skill.</param> /// <param name="queuePosition">The queue position.</param> /// <param name="lastSkillInQueue">if set to <c>true</c> skill is the last in queue.</param> private static async Task DoGoogleAppointmentAsync(QueuedSkill queuedSkill, int queuePosition, bool lastSkillInQueue) { try { // Set the subject to the character name and the skill and level in queue for uniqueness sakes GoogleCalendarEvent googleAppointmentFilter = new GoogleCalendarEvent { StartDate = DateTime.Now.AddDays(-40), EndDate = DateTime.Now.AddDays(100), Subject = string.Format( CultureConstants.DefaultCulture, "{0} - {1} {2}", queuedSkill.Owner.Name, queuedSkill.SkillName, Skill.GetRomanFromInt(queuedSkill.Level)) }; // Pull the list of appointments, hopefully we should either get 1 or none back await googleAppointmentFilter.ReadEventsAsync(); // If there is are appointments, see if any match the subject bool foundAppointment = false; if (googleAppointmentFilter.ItemCount > 0) { foundAppointment = googleAppointmentFilter.GetEvent(); } // Update the appointment we may have pulled or the new one // Set the appointment length to 5 minutes, starting at the estimated completion date and time // Reminder interval was already validated // Use the values from the screen as these may differ what the user has set for defaults googleAppointmentFilter.StartDate = queuedSkill.EndTime.ToLocalTime(); googleAppointmentFilter.EndDate = queuedSkill.EndTime.ToLocalTime().AddMinutes(5); googleAppointmentFilter.ItemReminder = Settings.Calendar.UseReminding; googleAppointmentFilter.AlternateReminder = Settings.Calendar.UseAlternateReminding; googleAppointmentFilter.EarlyReminder = Settings.Calendar.EarlyReminding; googleAppointmentFilter.LateReminder = Settings.Calendar.LateReminding; googleAppointmentFilter.Minutes = Settings.Calendar.RemindingInterval; googleAppointmentFilter.ReminderMethod = Settings.Calendar.GoogleEventReminder; await googleAppointmentFilter.AddOrUpdateEventAsync(foundAppointment, queuePosition, lastSkillInQueue); } catch (TokenResponseException ex) { MessageBox.Show(ex.Error.ErrorDescription, @"Google Calendar"); } catch (GoogleApiException ex) { MessageBox.Show(ex.Error.Message, @"Google Calendar"); } catch (APIException ex) { MessageBox.Show(ex.Message, ex.ErrorCode ?? @"Google Calendar"); } catch (Exception ex) { MessageBox.Show(ex.Message, @"Google Calendar"); } }
/// <summary> /// Handles the DrawItem event of the lbSkillsQueue control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.Windows.Forms.DrawItemEventArgs"/> instance containing the event data.</param> private void lbSkillsQueue_DrawItem(object sender, DrawItemEventArgs e) { if (e.Index < 0) { return; } item = lbSkillsQueue.Items[e.Index] as QueuedSkill; DrawItem(item, e); }
/// <summary> /// Adds the skill boiler plate. /// </summary> /// <param name="toolTip">The tool tip.</param> /// <param name="skill">The skill.</param> private static void AddSkillBoilerPlate(StringBuilder toolTip, QueuedSkill skill) { toolTip .AppendLine() .AppendLine(skill.Skill.Description.WordWrap(100)) .Append($"Primary: {skill.Skill.PrimaryAttribute}, ") .Append($"Secondary: {skill.Skill.SecondaryAttribute} ") .Append($"({skill.SkillPointsPerHour} SP/Hour)"); }
/// <summary> /// On a mouse down event /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void lbSkills_MouseDown(object sender, MouseEventArgs e) { // Retrieve the item at the given point and quit if none int index = lbSkillsQueue.IndexFromPoint(e.X, e.Y); if (index < 0 || index >= lbSkillsQueue.Items.Count) { return; } // Beware, this last index may actually means a click in the whitespace at the bottom // Let's deal with this special case if (index == lbSkillsQueue.Items.Count - 1) { Rectangle itemRect = lbSkillsQueue.GetItemRectangle(index); if (!itemRect.Contains(e.Location)) { return; } } // Right click for skills below lv5 : we display a context menu to plan higher levels. item = lbSkillsQueue.Items[index] as QueuedSkill; m_skill = item.Skill; if (e.Button == MouseButtons.Right && m_skill.Level < 5) { // Reset the menu. ToolStripMenuItem tm = new ToolStripMenuItem(String.Format(CultureInfo.CurrentCulture, "Add {0}", m_skill.Name)); // Build the level options. int nextLevel = Math.Min(5, m_skill.Level + 1); for (int level = nextLevel; level < 6; level++) { ToolStripMenuItem menuLevel = new ToolStripMenuItem(String.Format(CultureInfo.CurrentCulture, "Level {0} to", Skill.GetRomanForInt(level))); tm.DropDownItems.Add(menuLevel); foreach (var plan in m_character.Plans) { ToolStripMenuItem menuPlanItem = new ToolStripMenuItem(plan.Name); menuPlanItem.Click += new EventHandler(menuPlanItem_Click); menuPlanItem.Tag = new Pair <Plan, SkillLevel>(plan, new SkillLevel(m_skill, level)); menuLevel.DropDownItems.Add(menuPlanItem); } } // Add to the context menu and display contextMenuStripPlanPopup.Items.Clear(); contextMenuStripPlanPopup.Items.Add(tm); contextMenuStripPlanPopup.Show((Control)sender, new Point(e.X, e.Y)); return; } // Non-right click or already lv5, display the tooltip DisplayTooltip(item); }
/// <summary> /// Displays the tooltip for the given skill. /// </summary> /// <param name="item"></param> private void DisplayTooltip(QueuedSkill item) { if (ttToolTip.Active && m_lastTooltipItem == item) { return; } m_lastTooltipItem = item; ttToolTip.Active = false; ttToolTip.SetToolTip(lbSkillsQueue, GetTooltip(item)); ttToolTip.Active = true; }
/// <summary> /// Draws the boxes. /// </summary> /// <param name="fractionCompleted">The fraction completed.</param> /// <param name="skill">The skill.</param> /// <param name="e">The <see cref="System.Windows.Forms.DrawItemEventArgs"/> instance containing the event data.</param> private void DrawBoxes(double fractionCompleted, QueuedSkill skill, DrawItemEventArgs e) { Graphics g = e.Graphics; g.DrawRectangle(Pens.Black, new Rectangle(e.Bounds.Right - BoxWidth - PadRight, e.Bounds.Top + PadTop, BoxWidth, BoxHeight)); const int LevelBoxWidth = (BoxWidth - 4 - 3) / 5; for (int level = 1; level <= 5; level++) { Rectangle brect = new Rectangle(e.Bounds.Right - BoxWidth - PadRight + 2 + LevelBoxWidth * (level - 1) + (level - 1), e.Bounds.Top + PadTop + 2, LevelBoxWidth, BoxHeight - 3); // Box color g.FillRectangle(skill.Skill != null && level < skill.Level ? Brushes.Black : Brushes.DarkGray, brect); if (skill.Skill == null) { continue; } foreach (QueuedSkill qskill in Character.SkillQueue) { if ((!qskill.IsTraining && skill == qskill && level == qskill.Level) || (skill == qskill && level <= qskill.Level && level > skill.Skill.Level && Math.Abs(fractionCompleted) < double.Epsilon)) { g.FillRectangle(Brushes.RoyalBlue, brect); } // Blinking indicator of skill level in training if (!qskill.IsTraining || skill != qskill || level != skill.Level || Math.Abs(fractionCompleted) < double.Epsilon) { continue; } if (m_blinkAction == BlinkAction.Blink) { g.FillRectangle(Brushes.RoyalBlue, brect); } m_blinkAction = m_blinkAction == BlinkAction.Reset ? BlinkAction.Blink : BlinkAction.Stop; } } }
/// <summary> /// Process the queue into MS Outlook. /// </summary> /// <param name="queuedSkill">The queued skill.</param> /// <param name="queuePosition">The queue position.</param> /// <param name="lastSkillInQueue">if set to <c>true</c> skill is the last in queue.</param> private static async Task DoOutlookAppointmentAsync(QueuedSkill queuedSkill, int queuePosition, bool lastSkillInQueue) { // Get the calendar if (!OutlookCalendarEvent.OutlookCalendarExist(Settings.Calendar.UseOutlookDefaultCalendar)) { MessageBox.Show(@"Outlook calendar does not exist. Please check your settings.", @"Outlook Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } // Set the subject to the character name and the skill and level in queue for uniqueness sake OutlookCalendarEvent outlookAppointmentFilter = new OutlookCalendarEvent { StartDate = DateTime.Now.AddDays(-40), EndDate = DateTime.Now.AddDays(100), Subject = string.Format( CultureConstants.DefaultCulture, "{0} - {1} {2}", queuedSkill.Owner.Name, queuedSkill.SkillName, Skill.GetRomanFromInt(queuedSkill.Level)) }; // Pull the list of appointments, hopefully we should either get 1 or none back await outlookAppointmentFilter.ReadEventsAsync(); // If there is an appointment, get the first one bool foundAppointment = false; if (outlookAppointmentFilter.ItemCount > 0) { foundAppointment = outlookAppointmentFilter.GetEvent(); } // Update the appointment we may have pulled or the new one // Set the appointment length to 5 minutes, starting at the estimated completion date and time // Reminder value was already validated // Use the values from the screen as these may differ what the user has set for defaults outlookAppointmentFilter.StartDate = queuedSkill.EndTime.ToLocalTime(); outlookAppointmentFilter.EndDate = queuedSkill.EndTime.ToLocalTime().AddMinutes(5); outlookAppointmentFilter.ItemReminder = Settings.Calendar.UseReminding; outlookAppointmentFilter.AlternateReminder = Settings.Calendar.UseAlternateReminding; outlookAppointmentFilter.EarlyReminder = Settings.Calendar.EarlyReminding; outlookAppointmentFilter.LateReminder = Settings.Calendar.LateReminding; outlookAppointmentFilter.Minutes = Settings.Calendar.RemindingInterval; await outlookAppointmentFilter.AddOrUpdateEventAsync(foundAppointment, queuePosition, lastSkillInQueue); }
/// <summary> /// Handles the DrawItem event of the lbSkillsQueue control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.Windows.Forms.DrawItemEventArgs"/> instance containing the event data.</param> private void lbSkillsQueue_DrawItem(object sender, DrawItemEventArgs e) { if (e.Index < 0 || e.Index >= lbSkillsQueue.Items.Count) { return; } QueuedSkill item = lbSkillsQueue.Items[e.Index] as QueuedSkill; if (item == null) { return; } DrawItem(item, e); }
/// <summary> /// Displays the tooltip for the given skill. /// </summary> /// <param name="skill"></param> private void DisplayTooltip(QueuedSkill skill) { if (skill == null) { return; } if (ttToolTip.Active && m_lastTooltipItem != null && m_lastTooltipItem == skill) { return; } m_lastTooltipItem = skill; ttToolTip.Active = false; ttToolTip.SetToolTip(lbSkillsQueue, GetTooltip(skill)); ttToolTip.Active = true; }
/// <summary> /// Gets the rectangle a skill rendes in within a specified /// rectange. /// </summary> /// <param name="skill">Skill that exists within the queue</param> /// <param name="width">Width of the canvas</param> /// <param name="height">Height of the canvas</param> /// <returns> /// Rectangle representing the area within the visual /// queue the skill occupies. /// </returns> private Rectangle GetSkillRect(QueuedSkill skill, int width, int height) { TimeSpan relativeStart = skill.StartTime - DateTime.UtcNow; TimeSpan relativeFinish = skill.EndTime - DateTime.UtcNow; int TotalSeconds = (int)TimeSpan.FromHours(24).TotalSeconds; double Start = Math.Floor((relativeStart.TotalSeconds / TotalSeconds) * width); double Finish = Math.Floor((relativeFinish.TotalSeconds / TotalSeconds) * width); // if the start time is before now set it to zero if (Start < 0) { Start = 0; } return(new Rectangle((int)Start, 0, (int)(Finish - Start), height)); }
/// <summary> /// On mouse move, we hide the tooltip. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void lbSkills_MouseMove(object sender, MouseEventArgs e) { for (int i = 0; i < lbSkillsQueue.Items.Count; i++) { // Skip until we found the mouse location Rectangle rect = lbSkillsQueue.GetItemRectangle(i); if (!rect.Contains(e.Location)) { continue; } // Updates the tooltip m_item = lbSkillsQueue.Items[i] as QueuedSkill; DisplayTooltip(m_item); return; } // If we went so far, we're not over anything. m_lastTooltipItem = null; ttToolTip.Active = false; }
/// <summary> /// Updates the tooltip's content. /// </summary> protected override void UpdateContent() { if (!Visible) { UpdatePending = true; return; } UpdatePending = false; UpdateCharactersList(); // Replaces the fragments like "%10546464r" (the number being the character ID) by the remaining time string tooltip = m_tooltipFormat ?? String.Empty; foreach (Character character in m_characters) { if (character.IsTraining) { QueuedSkill trainingSkill = character.CurrentlyTrainingSkill; TimeSpan remainingTime = trainingSkill.EndTime.Subtract(DateTime.UtcNow); tooltip = Regex.Replace(tooltip, $"%{character.CharacterID}r", remainingTime.ToDescriptiveText(DescriptiveTextOptions.IncludeCommas), RegexOptions.Compiled); } CCPCharacter ccpCharacter = character as CCPCharacter; if (ccpCharacter != null && ccpCharacter.SkillQueue.IsPaused) { tooltip = Regex.Replace(tooltip, $"%{character.CharacterID}r", "(Paused)", RegexOptions.Compiled); } } // Updates the tooltip and its location ToolTipLabel.Text = tooltip; TrayIcon.SetToolTipLocation(this); }
/// <summary> /// Compares the two characters by their training completion time or, when not in training their names /// </summary> /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns></returns> /// <exception cref="System.ArgumentNullException"> /// x /// or /// y /// </exception> public static int CompareByCompletionTime(Character x, Character y) { x.ThrowIfNull(nameof(x)); y.ThrowIfNull(nameof(y)); // Get their training skills QueuedSkill skillX = x.CurrentlyTrainingSkill; QueuedSkill skillY = y.CurrentlyTrainingSkill; if (skillX == null && skillY == null) { return(String.Compare(x.Name, y.Name, StringComparison.CurrentCulture)); } if (skillX == null || skillY == null) { return(-1); } // Compare end time return(DateTime.Compare(skillX.EndTime, skillY.EndTime)); }
/// <summary> /// Displays the skill tool tip. /// </summary> /// <param name="skillRect">The skill rect.</param> /// <param name="skill">The skill.</param> private void DisplaySkillToolTip(RectangleF skillRect, QueuedSkill skill) { const string Format = "{0} {1}\n Start{2}\t{3}\n Ends\t{4}"; string skillName = skill.SkillName; string skillLevel = Skill.GetRomanFromInt(skill.Level); string skillStart = skill.Owner.IsTraining ? skill.StartTime.ToLocalTime().ToAbsoluteDateTimeDescription(DateTimeKind.Local) : "Paused"; string skillEnd = skill.Owner.IsTraining ? skill.EndTime.ToLocalTime().ToAbsoluteDateTimeDescription(DateTimeKind.Local) : "Paused"; string startText = skill.StartTime < DateTime.UtcNow ? "ed" : "s"; string text = string.Format(CultureConstants.DefaultCulture, Format, skillName, skillLevel, startText, skillStart, skillEnd); Size textSize = TextRenderer.MeasureText(text, Font); Size toolTipSize = new Size(textSize.Width + 13, textSize.Height + 11); Point tipPoint = new Point((int)(Math.Min(skillRect.Right, Width) + skillRect.Left) / 2 - toolTipSize.Width / 2, -toolTipSize.Height); tipPoint.Offset(0, -21); m_toolTip.Show(text, tipPoint); }
/// <summary> /// Builds the context menu for the selected queued skill. /// </summary> /// <param name="queuedSkill">The queued skill.</param> private void BuildContextMenu(QueuedSkill queuedSkill) { if (m_selectedSkill == null) { return; } tsmiAddSkill.DropDownItems.Clear(); // Reset the menu tsmiAddSkill.Text = $"Add {queuedSkill.Skill.Name}"; // Build the level options for (long level = queuedSkill.Level; level <= 5; level++) { ToolStripMenuItem tempMenuLevel = null; try { tempMenuLevel = new ToolStripMenuItem($"Level {Skill.GetRomanFromInt(level)} to"); Character.Plans.AddTo(tempMenuLevel.DropDownItems, (menuPlanItem, plan) => { menuPlanItem.Click += menuPlanItem_Click; menuPlanItem.Tag = new KeyValuePair <Plan, SkillLevel>(plan, new SkillLevel(queuedSkill.Skill, level)); }); ToolStripMenuItem menuLevel = tempMenuLevel; tempMenuLevel = null; tsmiAddSkill.DropDownItems.Add(menuLevel); } finally { tempMenuLevel?.Dispose(); } } }
public static void SkillQueueTest() { var url = new Uri("https://api.eveonline.com/char/SkillQueue.xml.aspx"); const int characterId = 123456; Dictionary <string, string> data = ApiTestHelpers.GetBaseTestParams(); data.Add(ApiConstants.CharacterId, characterId.ToString(CultureInfo.InvariantCulture)); IHttpRequestProvider mockProvider = MockRequests.GetMockedProvider(url, data, ApiTestHelpers.GetXmlData("TestData\\Api\\SkillQueue.xml")); using ( var client = new EveAPI(ApiTestHelpers.EveServiceApiHost, ApiTestHelpers.GetNullCacheProvider(), mockProvider)) { Task <EveServiceResponse <IEnumerable <QueuedSkill> > > task = client.Character.SkillQueueAsync(ApiTestHelpers.KeyIdValue, ApiTestHelpers.VCodeValue, characterId); task.Wait(); ApiTestHelpers.BasicSuccessResultValidations(task); Assert.IsNotNull(task.Result); Assert.IsNotEmpty(task.Result.ResultData); List <QueuedSkill> skillQueue = task.Result.ResultData.ToList(); Assert.AreEqual(2, skillQueue.Count); QueuedSkill skill = skillQueue[1]; Assert.AreEqual(2, skill.QueuePosition); Assert.AreEqual(20533, skill.TypeId); Assert.AreEqual(4, skill.Level); Assert.AreEqual(112000, skill.StartSP); Assert.AreEqual(633542, skill.EndSP); Assert.AreEqual(new DateTimeOffset(2009, 03, 18, 15, 19, 21, TimeSpan.Zero), skill.StartTime); Assert.AreEqual(new DateTimeOffset(2009, 03, 30, 03, 16, 14, TimeSpan.Zero), skill.EndTime); } }
/// <summary> /// Handles the MouseDown event of the lbSkills control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param> private void lbSkillsQueue_MouseDown(object sender, MouseEventArgs e) { // Retrieve the item at the given point and quit if none int index = lbSkillsQueue.IndexFromPoint(e.Location); if (index < 0 || index >= lbSkillsQueue.Items.Count) { return; } QueuedSkill item = lbSkillsQueue.Items[index] as QueuedSkill; // Beware, this last index may actually means a click in the whitespace at the bottom // Let's deal with this special case if (index == lbSkillsQueue.Items.Count - 1) { Rectangle itemRect = lbSkillsQueue.GetItemRectangle(index); if (!itemRect.Contains(e.Location)) { item = null; } } if (e.Button != MouseButtons.Right) { return; } // Right click for skills below lv5 : we display a context menu to plan higher levels lbSkillsQueue.Cursor = Cursors.Default; // Set the selected item m_selectedSkill = item; // Display the context menu contextMenuStrip.Show(lbSkillsQueue, e.Location); }
/// <summary> /// Draws the list item for the given skill /// </summary> /// <param name="skill"></param> /// <param name="e"></param> public void DrawItem(QueuedSkill skill, DrawItemEventArgs e) { Graphics g = e.Graphics; // Draw background if (e.Index % 2 == 0) { // Not in training - odd g.FillRectangle(Brushes.LightGray, e.Bounds); } else { // Not in training - even g.FillRectangle(Brushes.White, e.Bounds); } // Measure texts var format = TextFormatFlags.NoPadding | TextFormatFlags.NoClipping; double percentCompleted = 0; int skillPoints = (skill.Skill == null ? 0 : skill.Skill.SkillPoints); int skillPointsToNextLevel = (skill.Skill == null ? 0 : skill.Skill.StaticData.GetPointsRequiredForLevel( Math.Min(skill.Level, 5))); if (skill.Skill != null && skill.Level == skill.Skill.Level + 1) { percentCompleted = skill.Skill.PercentCompleted; } if (skill.Skill != null && skill.Level > skill.Skill.Level + 1) { skillPoints = skill.CurrentSP; } string rankText = String.Format(CultureConstants.DefaultCulture, " (Rank {0})", (skill.Skill == null ? 0 : skill.Skill.Rank)); string spText = String.Format(CultureConstants.DefaultCulture, "SP: {0:#,##0}/{1:#,##0}", skillPoints, skillPointsToNextLevel); string levelText = String.Format(CultureConstants.DefaultCulture, "Level {0}", skill.Level); string pctText = String.Format(CultureConstants.DefaultCulture, "{0}% Done", Math.Floor(percentCompleted)); Size skillNameSize = TextRenderer.MeasureText(g, skill.SkillName, m_boldSkillsQueueFont, Size.Empty, format); Size rankTextSize = TextRenderer.MeasureText(g, rankText, m_skillsQueueFont, Size.Empty, format); Size levelTextSize = TextRenderer.MeasureText(g, levelText, m_skillsQueueFont, Size.Empty, format); Size spTextSize = TextRenderer.MeasureText(g, spText, m_skillsQueueFont, Size.Empty, format); Size pctTextSize = TextRenderer.MeasureText(g, pctText, m_skillsQueueFont, Size.Empty, format); // Draw texts Color highlightColor = Color.Black; TextRenderer.DrawText(g, skill.SkillName, m_boldSkillsQueueFont, new Rectangle(e.Bounds.Left + PadLeft, e.Bounds.Top + PadTop, skillNameSize.Width + PadLeft, skillNameSize.Height), highlightColor); TextRenderer.DrawText(g, rankText, m_skillsQueueFont, new Rectangle(e.Bounds.Left + PadLeft + skillNameSize.Width, e.Bounds.Top + PadTop, rankTextSize.Width + PadLeft, rankTextSize.Height), highlightColor); TextRenderer.DrawText(g, spText, m_skillsQueueFont, new Rectangle(e.Bounds.Left + PadLeft, e.Bounds.Top + PadTop + skillNameSize.Height + LineVPad, spTextSize.Width + PadLeft, spTextSize.Height), highlightColor); // Boxes g.DrawRectangle(Pens.Black, new Rectangle(e.Bounds.Right - BoxWidth - PadRight, e.Bounds.Top + PadTop, BoxWidth, BoxHeight)); int levelBoxWidth = (BoxWidth - 4 - 3) / 5; for (int level = 1; level <= 5; level++) { var brect = new Rectangle(e.Bounds.Right - BoxWidth - PadRight + 2 + (levelBoxWidth * (level - 1)) + (level - 1), e.Bounds.Top + PadTop + 2, levelBoxWidth, BoxHeight - 3); if (skill.Skill != null && level <= skill.Skill.Level) { g.FillRectangle(Brushes.Black, brect); } else { g.FillRectangle(Brushes.DarkGray, brect); } // Color indicator for a queued level SkillQueue skillQueue = m_ccpCharacter.SkillQueue; if (skill.Skill != null) { Brush brush = (Settings.UI.SafeForWork ? Brushes.Gray : Brushes.RoyalBlue); foreach (QueuedSkill qskill in skillQueue) { if ((!skill.Skill.IsTraining && skill == qskill && level == qskill.Level) || (skill == qskill && level <= qskill.Level && level > skill.Skill.Level && percentCompleted == 0)) { g.FillRectangle(brush, brect); } // Blinking indicator of skill level in training if (skill.Skill.IsTraining && skill == qskill && level == skill.Level && percentCompleted > 0) { if (m_count == 0) { g.FillRectangle(Brushes.White, brect); } if (m_count == 1) { m_count = -1; } m_count++; } } } } // Draw progression bar g.DrawRectangle(Pens.Black, new Rectangle(e.Bounds.Right - BoxWidth - PadRight, e.Bounds.Top + PadTop + BoxHeight + BoxVPad, BoxWidth, LowerBoxHeight)); var pctBarRect = new Rectangle(e.Bounds.Right - BoxWidth - PadRight + 2, e.Bounds.Top + PadTop + BoxHeight + BoxVPad + 2, BoxWidth - 3, LowerBoxHeight - 3); g.FillRectangle(Brushes.DarkGray, pctBarRect); var fillWidth = (int)(pctBarRect.Width * (percentCompleted / 100)); if (fillWidth > 0) { var fillRect = new Rectangle(pctBarRect.X, pctBarRect.Y, fillWidth, pctBarRect.Height); g.FillRectangle(Brushes.Black, fillRect); } // Draw level and percent texts TextRenderer.DrawText(g, levelText, m_skillsQueueFont, new Rectangle( e.Bounds.Right - BoxWidth - PadRight - BoxHPad - levelTextSize.Width, e.Bounds.Top + PadTop, levelTextSize.Width + PadRight, levelTextSize.Height), Color.Black); TextRenderer.DrawText(g, pctText, m_skillsQueueFont, new Rectangle( e.Bounds.Right - BoxWidth - PadRight - BoxHPad - pctTextSize.Width, e.Bounds.Top + PadTop + levelTextSize.Height + LineVPad, pctTextSize.Width + PadRight, pctTextSize.Height), Color.Black); // Draw the queue color bar DrawQueueColorBar(g, e); }
/// <summary> /// Paints the current character's training informations, this is the regular painting operation. /// </summary> private void PaintsCharacter() { m_lcdLines.Clear(); if (!MonitoredCharacters.Any()) { m_lcdLines.Add(new LcdLine("No CCP Characters To Display", m_defaultFont)); RenderLines(); UpdateLcdDisplay(); return; } if (CycleSkillQueueTime) { if (TimeSpan.FromTicks(DateTime.Now.Ticks - m_cycleQueueInfoTime.Ticks).TotalSeconds > CycleCompletionInterval) { m_cycleQueueInfoTime = DateTime.Now; m_showingCycledQueueInfo = !m_showingCycledQueueInfo; } } if (CurrentCharacter == null) { return; } m_lcdLines.Add(new LcdLine(CurrentCharacter.AdornedName, m_defaultFont)); QueuedSkill queuedSkill = CurrentCharacter.SkillQueue.CurrentlyTraining; if (CurrentCharacter.SkillQueue.IsTraining) { if (m_showingCycledQueueInfo) { if (CurrentCharacter.SkillQueue.LessThanWarningThreshold) { // Place holder for skill queue training time rendering m_lcdLines.Add(new LcdLine(" ", m_defaultFont)); } else if (CurrentCharacter.SkillQueue.Count > 1) { // If more then one skill is in queue, show queue finish time string endTimeText = CurrentCharacter.SkillQueue.EndTime .Subtract(DateTime.UtcNow).ToDescriptiveText(DescriptiveTextOptions.SpaceBetween); m_lcdLines.Add(new LcdLine($"Queue ends in {endTimeText}", m_defaultFont)); } else { // Show the skill in training m_lcdLines.Add(new LcdLine($"{queuedSkill}", m_defaultFont)); } } else { // Show the skill in training m_lcdLines.Add(new LcdLine($"{queuedSkill}", m_defaultFont)); } m_lcdLines.Add(new LcdLine(queuedSkill.EndTime.Subtract(DateTime.UtcNow).ToDescriptiveText( DescriptiveTextOptions.SpaceBetween), m_defaultFont)); } else { if (CurrentCharacter.SkillQueue.IsPaused) { m_lcdLines.Add(new LcdLine($"{queuedSkill}", m_defaultFont)); m_lcdLines.Add(new LcdLine("Skill Training Is Paused", m_defaultFont)); } else { m_lcdLines.Add(new LcdLine("No Skill In Training", m_defaultFont)); m_lcdLines.Add(new LcdLine("Skill Queue Is Empty", m_defaultFont)); } } m_lcdLines.Add(new LcdLine($"{queuedSkill?.FractionCompleted ?? 0}", m_defaultFont)); RenderLines(); RenderWalletBalance(); RenderSkillQueueInfo(); RenderCompletionTime(); UpdateLcdDisplay(); }
/// <summary> /// Update the controls. /// </summary> private void UpdateContent() { if (!Visible) { return; } // Update character's 'Adorned Name' and 'Portrait' in case they have changed lblCharName.Text = Character.LabelPrefix + Character.AdornedName; pbCharacterPortrait.Character = Character; lblTotalSkillPoints.Text = string.Format("{0:N0} SP", Character.SkillPoints); FormatBalance(); CCPCharacter ccpCharacter = Character as CCPCharacter; QueuedSkill trainingSkill = Character.CurrentlyTrainingSkill; // Character in training ? We have labels to fill if (Character.IsTraining || (ccpCharacter != null && trainingSkill != null && ccpCharacter.SkillQueue.IsPaused)) { // Update the skill in training label lblSkillInTraining.Text = trainingSkill.ToString(); DateTime endTime = trainingSkill.EndTime.ToLocalTime(); // Updates the time remaining label lblRemainingTime.Text = (ccpCharacter != null && ccpCharacter.SkillQueue. IsPaused) ? "Paused" : trainingSkill.RemainingTime.ToDescriptiveText( DescriptiveTextOptions.IncludeCommas); // Update the completion time lblCompletionTime.Text = (ccpCharacter != null && ccpCharacter.SkillQueue. IsPaused) ? string.Empty : $"{endTime:ddd} {endTime:G}"; // Changes the completion time color on scheduling block string blockingEntry; bool isAutoBlocking; bool isBlocking = Scheduler.SkillIsBlockedAt(endTime, out blockingEntry, out isAutoBlocking); lblCompletionTime.ForeColor = (m_showConflicts && isBlocking && (ccpCharacter == null || ccpCharacter.SkillQueue.Count == 1 || !isAutoBlocking)) ? Color.Red : m_settingsForeColor; // Update the skill queue training time label UpdateSkillQueueTrainingTime(); // Show the training labels m_hasSkillInTraining = true; m_hasCompletionTime = true; m_hasRemainingTime = true; m_hasSkillQueueTrainingTime = true; } else { // Hide the training labels m_hasSkillInTraining = false; m_hasCompletionTime = false; m_hasRemainingTime = false; m_hasSkillQueueTrainingTime = false; } // Determine the character's system location int locID = Character?.LastKnownLocation?.SolarSystemID ?? 0; string locText; if (locID == 0) { locText = EveMonConstants.UnknownText + " Location"; } else { locText = StaticGeography.GetSolarSystemName(locID); } lblLocation.Text = locText; // Adjusts all the controls layout PerformCustomLayout(m_isTooltip); }
/// <summary> /// Draws the queue color bar. /// </summary> /// <param name="skill">The skill.</param> /// <param name="e">The <see cref="System.Windows.Forms.DrawItemEventArgs"/> instance containing the event data.</param> private void DrawQueueColorBar(QueuedSkill skill, DrawItemEventArgs e) { Graphics g = e.Graphics; // Draw the background Rectangle qBarRect = new Rectangle(e.Bounds.Left, GetItemHeight - LowerBoxHeight, e.Bounds.Width, LowerBoxHeight); g.FillRectangle(Brushes.DimGray, qBarRect); double oneDaySkillQueueWidth = Character.SkillQueue.GetOneDaySkillQueueWidth(qBarRect.Width); IList<RectangleF> skillRects = Character.SkillQueue.GetSkillRects(skill, qBarRect.Width, LowerBoxHeight - 1).ToList(); Brush lessThanDayBrush = Settings.UI.SafeForWork ? Brushes.LightGray : Brushes.Khaki; Brush moreThanDayBrush = Settings.UI.SafeForWork ? Brushes.DarkGray : Brushes.CornflowerBlue; RectangleF skillRectFirst = skillRects.First(); // Skill starts before the 24h marker if (skillRectFirst.X < oneDaySkillQueueWidth) { // Iterate only through rectangles with width foreach (RectangleF skillRect in skillRects.Skip(1).Where(rect => rect.Width > 0)) { Brush brush = oneDaySkillQueueWidth - skillRect.X <= 0 ? moreThanDayBrush : lessThanDayBrush; g.FillRectangle(brush, new RectangleF(skillRect.X, GetItemHeight - LowerBoxHeight, skillRect.Width, skillRect.Height)); } return; } g.FillRectangle(moreThanDayBrush, new RectangleF(skillRectFirst.X, GetItemHeight - LowerBoxHeight, skillRectFirst.Width, skillRectFirst.Height)); }
/// <summary> /// Gets the tooltip text for the given skill /// </summary> /// <param name="skill"></param> private string GetTooltip(QueuedSkill skill) { if (skill.Skill == null) { return(String.Empty); } int sp = skill.Skill.SkillPoints; int nextLevel = Math.Min(5, skill.Level); double percentCompleted = 0; if (skill.Level == skill.Skill.Level + 1) { percentCompleted = skill.Skill.PercentCompleted; } if (skill.Level > skill.Skill.Level + 1) { sp = skill.CurrentSP; } int nextLevelSP = skill.Skill.StaticData.GetPointsRequiredForLevel(nextLevel); int pointsLeft = nextLevelSP - sp; TimeSpan timeSpanFromPoints = skill.Skill.GetTimeSpanForPoints(pointsLeft); string remainingTimeText = timeSpanFromPoints.ToDescriptiveText( DescriptiveTextOptions.IncludeCommas | DescriptiveTextOptions.UppercaseText); if (sp < skill.Skill.StaticData.GetPointsRequiredForLevel(1)) { // Training hasn't got past level 1 yet var untrainedToolTip = new StringBuilder(); untrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Not yet trained to Level I ({0}%)\n", Math.Floor(percentCompleted)); untrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Next level I: {0:#,##0} skill points remaining\n", pointsLeft); untrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Training time remaining: {0}", remainingTimeText); AddSkillBoilerPlate(untrainedToolTip, skill.Skill); return(untrainedToolTip.ToString()); } // So, it's a left click on a skill, we display the tool tip // Currently training skill? if (skill.Skill.IsTraining && percentCompleted != 0) { var partiallyTrainedToolTip = new StringBuilder(); partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Partially Completed ({0}%)\n", Math.Floor(percentCompleted)); partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Training to level {0}: {1:#,##0} skill points remaining\n", Skill.GetRomanForInt(nextLevel), pointsLeft); partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Training time remaining: {0}", remainingTimeText); AddSkillBoilerPlate(partiallyTrainedToolTip, skill.Skill); return(partiallyTrainedToolTip.ToString()); } // Currently training skill but next queued level? if (skill.Skill.IsTraining && percentCompleted == 0) { var partiallyTrainedToolTip = new StringBuilder(); partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Previous level not yet completed\n"); partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Queued to level {0}: {1:#,##0} skill points remaining\n", Skill.GetRomanForInt(nextLevel), pointsLeft); partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Training time to next level: {0}", remainingTimeText); AddSkillBoilerPlate(partiallyTrainedToolTip, skill.Skill); return(partiallyTrainedToolTip.ToString()); } // Partially trained skill and not in training? if (skill.Skill.IsPartiallyTrained && !skill.Skill.IsTraining) { var partiallyTrainedToolTip = new StringBuilder(); partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Partially Completed ({0}%)\n", Math.Floor(percentCompleted)); partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Queued to level {0}: {1:#,##0} skill points remaining\n", Skill.GetRomanForInt(nextLevel), pointsLeft); partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Training time remaining: {0}", remainingTimeText); AddSkillBoilerPlate(partiallyTrainedToolTip, skill.Skill); return(partiallyTrainedToolTip.ToString()); } // We've completed all the skill points for the current level if (!skill.Skill.IsPartiallyTrained && skill.Level != 5) { var levelCompleteToolTip = new StringBuilder(); levelCompleteToolTip.AppendFormat(CultureConstants.DefaultCulture, "Completed Level {0}: {1:#,##0}/{2:#,##0}\n", Skill.GetRomanForInt(skill.Level - 1), sp, nextLevelSP); levelCompleteToolTip.AppendFormat(CultureConstants.DefaultCulture, "Queued level {0}: {1:#,##0} skill points required\n", Skill.GetRomanForInt(nextLevel), pointsLeft); levelCompleteToolTip.AppendFormat(CultureConstants.DefaultCulture, "Training time to next level: {0}", remainingTimeText); AddSkillBoilerPlate(levelCompleteToolTip, skill.Skill); return(levelCompleteToolTip.ToString()); } // Error in calculating SkillPoints var calculationErrorToolTip = new StringBuilder(); calculationErrorToolTip.AppendLine("Partially Trained (Could not calculate all skill details)"); calculationErrorToolTip.AppendFormat(CultureConstants.DefaultCulture, "Next level {0}: {1:#,##0} skill points remaining\n", nextLevel, pointsLeft); calculationErrorToolTip.AppendFormat(CultureConstants.DefaultCulture, "Training time remaining: {0}", remainingTimeText); AddSkillBoilerPlate(calculationErrorToolTip, skill.Skill); return(calculationErrorToolTip.ToString()); }
/// <summary> /// Builds the context menu for the selected queued skill. /// </summary> /// <param name="queuedSkill">The queued skill.</param> private void BuildContextMenu(QueuedSkill queuedSkill) { if (m_selectedSkill == null) return; tsmiAddSkill.DropDownItems.Clear(); // Reset the menu tsmiAddSkill.Text = $"Add {queuedSkill.Skill.Name}"; // Build the level options for (Int64 level = queuedSkill.Level; level <= 5; level++) { ToolStripMenuItem tempMenuLevel = null; try { tempMenuLevel = new ToolStripMenuItem($"Level {Skill.GetRomanFromInt(level)} to"); Character.Plans.AddTo(tempMenuLevel.DropDownItems, (menuPlanItem, plan) => { menuPlanItem.Click += menuPlanItem_Click; menuPlanItem.Tag = new KeyValuePair<Plan, SkillLevel>(plan, new SkillLevel(queuedSkill.Skill, level)); }); ToolStripMenuItem menuLevel = tempMenuLevel; tempMenuLevel = null; tsmiAddSkill.DropDownItems.Add(menuLevel); } finally { tempMenuLevel?.Dispose(); } } }
/// <summary> /// Draws the boxes. /// </summary> /// <param name="fractionCompleted">The fraction completed.</param> /// <param name="skill">The skill.</param> /// <param name="e">The <see cref="System.Windows.Forms.DrawItemEventArgs"/> instance containing the event data.</param> private void DrawBoxes(double fractionCompleted, QueuedSkill skill, DrawItemEventArgs e) { Graphics g = e.Graphics; g.DrawRectangle(Pens.Black, new Rectangle(e.Bounds.Right - BoxWidth - PadRight, e.Bounds.Top + PadTop, BoxWidth, BoxHeight)); const int LevelBoxWidth = (BoxWidth - 4 - 3) / 5; for (int level = 1; level <= 5; level++) { Rectangle brect = new Rectangle(e.Bounds.Right - BoxWidth - PadRight + 2 + LevelBoxWidth * (level - 1) + (level - 1), e.Bounds.Top + PadTop + 2, LevelBoxWidth, BoxHeight - 3); // Box color g.FillRectangle(skill.Skill != null && level < skill.Level ? Brushes.Black : Brushes.DarkGray, brect); if (skill.Skill == null) continue; foreach (QueuedSkill qskill in Character.SkillQueue) { if ((!qskill.IsTraining && skill == qskill && level == qskill.Level) || (skill == qskill && level <= qskill.Level && level > skill.Skill.Level && Math.Abs(fractionCompleted) < double.Epsilon)) { g.FillRectangle(Brushes.RoyalBlue, brect); } // Blinking indicator of skill level in training if (!qskill.IsTraining || skill != qskill || level != skill.Level || Math.Abs(fractionCompleted) < double.Epsilon) { continue; } if (m_blinkAction == BlinkAction.Blink) g.FillRectangle(Brushes.RoyalBlue, brect); m_blinkAction = m_blinkAction == BlinkAction.Reset ? BlinkAction.Blink : BlinkAction.Stop; } } }
/// <summary> /// Gets the tooltip text for the given skill /// </summary> /// <param name="skill"></param> private static string GetTooltip(QueuedSkill skill) { if (skill.Skill == null) return String.Empty; Int64 sp = skill.Level > skill.Skill.Level + 1 ? skill.CurrentSP : skill.Skill.SkillPoints; Int32 nextLevel = Math.Min(5, skill.Level); Double fractionCompleted = skill.FractionCompleted; Int64 nextLevelSP = skill.Skill == Skill.UnknownSkill ? skill.EndSP : skill.Skill.StaticData.GetPointsRequiredForLevel(nextLevel); Int64 pointsLeft = nextLevelSP - sp; TimeSpan timeSpanFromPoints = skill.Skill == Skill.UnknownSkill ? skill.EndTime.Subtract(DateTime.UtcNow) : skill.Skill.GetTimeSpanForPoints(pointsLeft); string remainingTimeText = timeSpanFromPoints.ToDescriptiveText( DescriptiveTextOptions.IncludeCommas | DescriptiveTextOptions.UppercaseText); if (sp < skill.Skill.StaticData.GetPointsRequiredForLevel(1)) { // Training hasn't got past level 1 yet StringBuilder untrainedToolTip = new StringBuilder(); untrainedToolTip .Append($"Not yet trained to Level I ({Math.Floor(fractionCompleted * 100) / 100:P0})") .AppendLine() .Append($"Next level I: {pointsLeft:N0} skill points remaining") .AppendLine() .Append($"Training time remaining: {remainingTimeText}") .AppendLine(); AddSkillBoilerPlate(untrainedToolTip, skill); return untrainedToolTip.ToString(); } // So, it's a left click on a skill, we display the tool tip // Currently training skill? if (skill.IsTraining && fractionCompleted > 0) { StringBuilder partiallyTrainedToolTip = new StringBuilder(); partiallyTrainedToolTip .Append($"Partially Completed ({Math.Floor(fractionCompleted * 100) / 100:P0})") .AppendLine() .Append($"Training to level {Skill.GetRomanFromInt(nextLevel)}: {pointsLeft:N0} skill points remaining") .AppendLine() .Append($"Training time remaining: {remainingTimeText}") .AppendLine(); AddSkillBoilerPlate(partiallyTrainedToolTip, skill); return partiallyTrainedToolTip.ToString(); } // Currently training skill but next queued level? if (skill.IsTraining && Math.Abs(fractionCompleted) < double.Epsilon) { StringBuilder partiallyTrainedToolTip = new StringBuilder(); partiallyTrainedToolTip .Append($"Previous level not yet completed") .AppendLine() .Append($"Queued to level {Skill.GetRomanFromInt(nextLevel)}: {pointsLeft:N0} skill points remaining") .AppendLine() .Append($"Training time to next level: {remainingTimeText}") .AppendLine(); AddSkillBoilerPlate(partiallyTrainedToolTip, skill); return partiallyTrainedToolTip.ToString(); } // Partially trained skill and not in training? if (skill.Skill.IsPartiallyTrained && !skill.IsTraining) { StringBuilder partiallyTrainedToolTip = new StringBuilder(); partiallyTrainedToolTip .Append($"Partially Completed ({Math.Floor(fractionCompleted * 100) / 100:P0})") .AppendLine() .Append($"Queued to level {Skill.GetRomanFromInt(nextLevel)}: {pointsLeft:N0} skill points remaining") .AppendLine() .Append($"Training time remaining: {remainingTimeText}") .AppendLine(); AddSkillBoilerPlate(partiallyTrainedToolTip, skill); return partiallyTrainedToolTip.ToString(); } // We've completed all the skill points for the current level if (!skill.Skill.IsPartiallyTrained && skill.Level <= 5) { StringBuilder levelCompleteToolTip = new StringBuilder(); levelCompleteToolTip .Append($"Completed Level {Skill.GetRomanFromInt(skill.Level - 1)}: {sp:N0}/{nextLevelSP:N0}") .AppendLine() .Append($"Queued level {Skill.GetRomanFromInt(nextLevel)}: {pointsLeft:N0} skill points required") .AppendLine() .Append($"Training time to next level: {remainingTimeText}") .AppendLine(); AddSkillBoilerPlate(levelCompleteToolTip, skill); return levelCompleteToolTip.ToString(); } // Error in calculating SkillPoints StringBuilder calculationErrorToolTip = new StringBuilder(); calculationErrorToolTip .AppendLine("Partially Trained (Could not calculate all skill details)") .Append($"Next level {nextLevel}: {pointsLeft:N0} skill points remaining") .AppendLine() .Append($"Training time remaining: {remainingTimeText}") .AppendLine(); AddSkillBoilerPlate(calculationErrorToolTip, skill); return calculationErrorToolTip.ToString(); }
/// <summary> /// On a mouse down event /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void lbSkills_MouseDown(object sender, MouseEventArgs e) { // Retrieve the item at the given point and quit if none int index = lbSkillsQueue.IndexFromPoint(e.X, e.Y); if (index < 0 || index >= lbSkillsQueue.Items.Count) { return; } // Beware, this last index may actually means a click in the whitespace at the bottom // Let's deal with this special case if (index == lbSkillsQueue.Items.Count - 1) { Rectangle itemRect = lbSkillsQueue.GetItemRectangle(index); if (!itemRect.Contains(e.Location)) { return; } } // Right click for skills below lv5 : we display a context menu to plan higher levels. m_item = lbSkillsQueue.Items[index] as QueuedSkill; var skill = m_item.Skill; if (skill != null) { if (e.Button == MouseButtons.Right) { // "Show in Skill Explorer" menu item var tmSkillExplorer = new ToolStripMenuItem("Show In Skill Explorer", CommonProperties.Resources.LeadsTo); tmSkillExplorer.Click += tmSkillExplorer_Click; tmSkillExplorer.Tag = skill; // Add to the context menu contextMenuStripPlanPopup.Items.Clear(); contextMenuStripPlanPopup.Items.Add(tmSkillExplorer); if (skill.Level < 5) { // Reset the menu. var tm = new ToolStripMenuItem(String.Format(CultureConstants.DefaultCulture, "Add {0}", skill.Name)); // Build the level options. int nextLevel = Math.Min(5, skill.Level + 1); for (var level = nextLevel; level < 6; level++) { var menuLevel = new ToolStripMenuItem(String.Format(CultureConstants.DefaultCulture, "Level {0} to", Skill.GetRomanForInt(level))); tm.DropDownItems.Add(menuLevel); int level1 = level; Character.Plans.AddTo(menuLevel.DropDownItems, (menuPlanItem, plan) => { menuPlanItem.Click += menuPlanItem_Click; menuPlanItem.Tag = new Pair <Plan, SkillLevel>( plan, new SkillLevel(skill, level1)); }); } // Add to the context menu contextMenuStripPlanPopup.Items.Add(new ToolStripSeparator()); contextMenuStripPlanPopup.Items.Add(tm); } // Display the context menu contextMenuStripPlanPopup.Show((Control)sender, new Point(e.X, e.Y)); return; } } // Non-right click or already lv5, display the tooltip DisplayTooltip(m_item); }
/// <summary> /// Draws the list item for the given skill /// </summary> /// <param name="skill"></param> /// <param name="e"></param> private void DrawItem(QueuedSkill skill, DrawItemEventArgs e) { Graphics g = e.Graphics; // Draw background g.FillRectangle(e.Index % 2 == 0 ? Brushes.LightGray : Brushes.White, e.Bounds); // Measure texts const TextFormatFlags Format = TextFormatFlags.NoPadding | TextFormatFlags.NoClipping; bool hasSkill = (skill.Skill != null) && (skill.Skill != Skill.UnknownSkill); Int64 skillPoints = (skill.Skill != null) && (skill.Level > skill.Skill.Level + 1) ? skill.CurrentSP : !hasSkill ? skill.StartSP : skill.Skill.SkillPoints; Int64 skillPointsToNextLevel = !hasSkill ? skill.EndSP : skill.Skill.StaticData.GetPointsRequiredForLevel(Math.Min(skill.Level, 5)); Int64 pointsLeft = skillPointsToNextLevel - skillPoints; TimeSpan timeSpanFromPoints = !hasSkill ? skill.EndTime.Subtract(DateTime.UtcNow) : skill.Skill.GetTimeSpanForPoints(pointsLeft); string remainingTimeText = timeSpanFromPoints.ToDescriptiveText(DescriptiveTextOptions.SpaceBetween); double fractionCompleted = e.Index == 0 ? skill.FractionCompleted : 0d; string indexText = $"{e.Index + 1}. "; string rankText = $" (Rank {(skill.Skill == null ? 0 : skill.Rank)})"; string spPerHourText = $" SP/Hour: {skill.SkillPointsPerHour}"; string spText = $"SP: {skillPoints:N0}/{skillPointsToNextLevel:N0}"; string trainingTimeText = $" Training Time: {remainingTimeText}"; string levelText = $"Level {skill.Level}"; string percentText = $"{Math.Floor(fractionCompleted * 100) / 100:P0} Done"; Size indexTextSize = TextRenderer.MeasureText(g, indexText, m_boldSkillsQueueFont, Size.Empty, Format); Size skillNameSize = TextRenderer.MeasureText(g, skill.SkillName, m_boldSkillsQueueFont, Size.Empty, Format); Size rankTextSize = TextRenderer.MeasureText(g, rankText, m_skillsQueueFont, Size.Empty, Format); Size levelTextSize = TextRenderer.MeasureText(g, levelText, m_skillsQueueFont, Size.Empty, Format); Size spPerHourTextSize = TextRenderer.MeasureText(g, spPerHourText, m_skillsQueueFont, Size.Empty, Format); Size spTextSize = TextRenderer.MeasureText(g, spText, m_skillsQueueFont, Size.Empty, Format); Size ttTextSize = TextRenderer.MeasureText(g, trainingTimeText, m_skillsQueueFont, Size.Empty, Format); Size pctTextSize = TextRenderer.MeasureText(g, percentText, m_skillsQueueFont, Size.Empty, Format); // Draw texts Color highlightColor = Color.Black; // First line int left = e.Bounds.Left + PadLeft; int top = e.Bounds.Top + PadTop; TextRenderer.DrawText(g, indexText, m_boldSkillsQueueFont, new Rectangle(left, top, indexTextSize.Width + PadLeft, indexTextSize.Height), highlightColor); left += indexTextSize.Width; TextRenderer.DrawText(g, skill.SkillName, m_boldSkillsQueueFont, new Rectangle(left, top, skillNameSize.Width + PadLeft, skillNameSize.Height), highlightColor); left += skillNameSize.Width; TextRenderer.DrawText(g, rankText, m_skillsQueueFont, new Rectangle(left, top, rankTextSize.Width + PadLeft, rankTextSize.Height), highlightColor); // Second line left = e.Bounds.Left + PadLeft + indexTextSize.Width; top += skillNameSize.Height; TextRenderer.DrawText(g, spText, m_skillsQueueFont, new Rectangle(left, top, spTextSize.Width + PadLeft, spTextSize.Height), highlightColor); left += spTextSize.Width + PadLeft; TextRenderer.DrawText(g, spPerHourText, m_skillsQueueFont, new Rectangle(left, top, spPerHourTextSize.Width + PadLeft, spPerHourTextSize.Height), highlightColor); left += spPerHourTextSize.Width + PadLeft; TextRenderer.DrawText(g, trainingTimeText, m_skillsQueueFont, new Rectangle(left, top, ttTextSize.Width + PadLeft, ttTextSize.Height), highlightColor); // Boxes DrawBoxes(fractionCompleted, skill, e); // Draw progression bar DrawProgressionBar(fractionCompleted, e); // Draw level and percent texts TextRenderer.DrawText(g, levelText, m_skillsQueueFont, new Rectangle(e.Bounds.Right - BoxWidth - PadRight - BoxHPad - levelTextSize.Width, e.Bounds.Top + PadTop, levelTextSize.Width + PadRight, levelTextSize.Height), highlightColor); TextRenderer.DrawText(g, percentText, m_skillsQueueFont, new Rectangle(e.Bounds.Right - BoxWidth - PadRight - BoxHPad - pctTextSize.Width, e.Bounds.Top + PadTop + levelTextSize.Height, pctTextSize.Width + PadRight, pctTextSize.Height), highlightColor); // Draw the queue color bar DrawQueueColorBar(skill, e); }
/// <summary> /// Handles the MouseDown event of the lbSkills control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param> private void lbSkillsQueue_MouseDown(object sender, MouseEventArgs e) { // Retrieve the item at the given point and quit if none int index = lbSkillsQueue.IndexFromPoint(e.Location); if (index < 0 || index >= lbSkillsQueue.Items.Count) return; QueuedSkill item = lbSkillsQueue.Items[index] as QueuedSkill; // Beware, this last index may actually means a click in the whitespace at the bottom // Let's deal with this special case if (index == lbSkillsQueue.Items.Count - 1) { Rectangle itemRect = lbSkillsQueue.GetItemRectangle(index); if (!itemRect.Contains(e.Location)) item = null; } if (e.Button != MouseButtons.Right) return; // Right click for skills below lv5 : we display a context menu to plan higher levels lbSkillsQueue.Cursor = Cursors.Default; // Set the selected item m_selectedSkill = item; // Display the context menu contextMenuStrip.Show(lbSkillsQueue, e.Location); }
/// <summary> /// Displays the tooltip for the given skill. /// </summary> /// <param name="skill"></param> private void DisplayTooltip(QueuedSkill skill) { if (skill == null) return; if (ttToolTip.Active && m_lastTooltipItem != null && m_lastTooltipItem == skill) return; m_lastTooltipItem = skill; ttToolTip.Active = false; ttToolTip.SetToolTip(lbSkillsQueue, GetTooltip(skill)); ttToolTip.Active = true; }
/// <summary> /// Gets the tooltip text for the given skill /// </summary> /// <param name="skill"></param> private static string GetTooltip(QueuedSkill skill) { if (skill.Skill == null) { return(string.Empty); } long sp = skill.Level > skill.Skill.Level + 1 ? skill.CurrentSP : skill.Skill.SkillPoints; Int32 nextLevel = Math.Min(5, skill.Level); Double fractionCompleted = skill.FractionCompleted; long nextLevelSP = skill.Skill == Skill.UnknownSkill ? skill.EndSP : skill.Skill.StaticData.GetPointsRequiredForLevel(nextLevel); long pointsLeft = nextLevelSP - sp; TimeSpan timeSpanFromPoints = skill.Skill == Skill.UnknownSkill ? skill.EndTime.Subtract(DateTime.UtcNow) : skill.Skill.GetTimeSpanForPoints(pointsLeft); string remainingTimeText = timeSpanFromPoints.ToDescriptiveText( DescriptiveTextOptions.IncludeCommas | DescriptiveTextOptions.UppercaseText); if (sp < skill.Skill.StaticData.GetPointsRequiredForLevel(1)) { // Training hasn't got past level 1 yet StringBuilder untrainedToolTip = new StringBuilder(); untrainedToolTip .Append($"Not yet trained to Level I ({Math.Floor(fractionCompleted * 100) / 100:P0})") .AppendLine() .Append($"Next level I: {pointsLeft:N0} skill points remaining") .AppendLine() .Append($"Training time remaining: {remainingTimeText}") .AppendLine(); AddSkillBoilerPlate(untrainedToolTip, skill); return(untrainedToolTip.ToString()); } // So, it's a left click on a skill, we display the tool tip // Currently training skill? if (skill.IsTraining && fractionCompleted > 0) { StringBuilder partiallyTrainedToolTip = new StringBuilder(); partiallyTrainedToolTip .Append($"Partially Completed ({Math.Floor(fractionCompleted * 100) / 100:P0})") .AppendLine() .Append($"Training to level {Skill.GetRomanFromInt(nextLevel)}: {pointsLeft:N0} skill points remaining") .AppendLine() .Append($"Training time remaining: {remainingTimeText}") .AppendLine(); AddSkillBoilerPlate(partiallyTrainedToolTip, skill); return(partiallyTrainedToolTip.ToString()); } // Currently training skill but next queued level? if (skill.IsTraining && Math.Abs(fractionCompleted) < double.Epsilon) { StringBuilder partiallyTrainedToolTip = new StringBuilder(); partiallyTrainedToolTip .Append($"Previous level not yet completed") .AppendLine() .Append($"Queued to level {Skill.GetRomanFromInt(nextLevel)}: {pointsLeft:N0} skill points remaining") .AppendLine() .Append($"Training time to next level: {remainingTimeText}") .AppendLine(); AddSkillBoilerPlate(partiallyTrainedToolTip, skill); return(partiallyTrainedToolTip.ToString()); } // Partially trained skill and not in training? if (skill.Skill.IsPartiallyTrained && !skill.IsTraining) { StringBuilder partiallyTrainedToolTip = new StringBuilder(); partiallyTrainedToolTip .Append($"Partially Completed ({Math.Floor(fractionCompleted * 100) / 100:P0})") .AppendLine() .Append($"Queued to level {Skill.GetRomanFromInt(nextLevel)}: {pointsLeft:N0} skill points remaining") .AppendLine() .Append($"Training time remaining: {remainingTimeText}") .AppendLine(); AddSkillBoilerPlate(partiallyTrainedToolTip, skill); return(partiallyTrainedToolTip.ToString()); } // We've completed all the skill points for the current level if (!skill.Skill.IsPartiallyTrained && skill.Level <= 5) { StringBuilder levelCompleteToolTip = new StringBuilder(); levelCompleteToolTip .Append($"Completed Level {Skill.GetRomanFromInt(skill.Level - 1)}: {sp:N0}/{nextLevelSP:N0}") .AppendLine() .Append($"Queued level {Skill.GetRomanFromInt(nextLevel)}: {pointsLeft:N0} skill points required") .AppendLine() .Append($"Training time to next level: {remainingTimeText}") .AppendLine(); AddSkillBoilerPlate(levelCompleteToolTip, skill); return(levelCompleteToolTip.ToString()); } // Error in calculating SkillPoints StringBuilder calculationErrorToolTip = new StringBuilder(); calculationErrorToolTip .AppendLine("Partially Trained (Could not calculate all skill details)") .Append($"Next level {nextLevel}: {pointsLeft:N0} skill points remaining") .AppendLine() .Append($"Training time remaining: {remainingTimeText}") .AppendLine(); AddSkillBoilerPlate(calculationErrorToolTip, skill); return(calculationErrorToolTip.ToString()); }