diff --git a/EVEData/Anom.cs b/EVEData/Anom.cs
index 3b4ff693..90d1a09b 100644
--- a/EVEData/Anom.cs
+++ b/EVEData/Anom.cs
@@ -81,8 +81,8 @@ public enum SignatureType
public string Type { get; set; }
///
- /// To String
+ /// Convert to string, keeping the in-game format so that the string can be imported back into SMT
///
- public override string ToString() => Signature + " " + Type + " " + Name;
+ public override string ToString() => Signature + "\t" + "Cosmic Signature" + "\t" + Type + "\t" + Name + "\t\t";
}
}
\ No newline at end of file
diff --git a/EVEData/AnomData.cs b/EVEData/AnomData.cs
index c6a86193..6ed65f85 100644
--- a/EVEData/AnomData.cs
+++ b/EVEData/AnomData.cs
@@ -40,82 +40,64 @@ public AnomData()
/// Update the AnomData from the string (usually the clipboard)
///
/// raw Anom strings
- public void UpdateFromPaste(string pastedText)
+ /// HashSet of anom IDs present in the dictionary but missing from the pasted text
+ public HashSet UpdateFromPaste(string pastedText)
{
- bool validPaste = false;
- List itemsToKeep = new List();
+ HashSet signaturesPresent = new HashSet();
+
string[] pastelines = pastedText.Split('\n');
foreach (string line in pastelines)
{
// split on tabs
string[] words = line.Split('\t');
- if (words.Length == 6)
- {
- // only care about "Cosmic Signature"
- if (CosmicSignatureTags.Contains(words[1]))
- {
- validPaste = true;
-
- string sigID = words[0];
- string sigType = words[2];
- string sigName = words[3];
-
- if (string.IsNullOrEmpty(sigType))
- {
- sigType = "Unknown";
- }
-
- itemsToKeep.Add(sigID);
-
- // valid sig
- if (Anoms.ContainsKey(sigID))
- {
- // updating an existing one
- Anom an = Anoms[sigID];
- if (an.Type == "Unknown")
- {
- an.Type = sigType;
- }
-
- if (!string.IsNullOrEmpty(sigName))
- {
- an.Name = sigName;
- }
- }
- else
- {
- Anom an = new Anom();
- an.Signature = sigID;
- an.Type = sigType;
-
- if (!string.IsNullOrEmpty(sigName))
- {
- an.Name = sigName;
- }
-
- Anoms.Add(sigID, an);
- }
- }
- }
- }
- // if we had a valid paste dump any items we didnt reference, brute force scan and remove.. come back to this later..
- if (validPaste)
- {
- List toRemove = new List();
- foreach (string an in Anoms.Keys.ToList())
+ if (words.Length != 6)
+ continue;
+
+ // only care about "Cosmic Signature"
+ if (!CosmicSignatureTags.Contains(words[1]))
+ continue;
+
+ string sigID = words[0];
+ string sigType = words[2];
+ string sigName = words[3];
+
+ if (string.IsNullOrEmpty(sigType))
+ sigType = "Unknown";
+
+ signaturesPresent.Add(sigID);
+
+ if (Anoms.ContainsKey(sigID))
{
- if (!itemsToKeep.Contains(an))
- {
- toRemove.Add(an);
- }
- }
+ // update an existing signature
+ Anom an = Anoms[sigID];
- foreach (string s in toRemove)
+ if (an.Type == "Unknown")
+ an.Type = sigType;
+
+ if (!string.IsNullOrEmpty(sigName))
+ an.Name = sigName;
+ }
+ else
{
- Anoms.Remove(s);
+ // add a new signature
+ Anom an = new Anom();
+ an.Signature = sigID;
+ an.Type = sigType;
+
+ if (!string.IsNullOrEmpty(sigName))
+ an.Name = sigName;
+
+ Anoms.Add(sigID, an);
}
}
+
+ // find existing signatures that are missing from the paste
+ var signaturesMissing = Anoms.Where(kvp => !signaturesPresent.Contains(kvp.Value.Signature))
+ .Select(kvp => kvp.Key)
+ .ToHashSet();
+
+ return signaturesMissing;
}
}
}
\ No newline at end of file
diff --git a/SMT/MainWindow.xaml b/SMT/MainWindow.xaml
index fa140a8e..162209b7 100644
--- a/SMT/MainWindow.xaml
+++ b/SMT/MainWindow.xaml
@@ -20,6 +20,7 @@
+
@@ -114,43 +115,68 @@
-
+
-
+
-
+
+
-
-
-
-
+
+
+
+
+
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
+
+
diff --git a/SMT/MainWindow.xaml.cs b/SMT/MainWindow.xaml.cs
index 1ccc15c0..f77251ef 100644
--- a/SMT/MainWindow.xaml.cs
+++ b/SMT/MainWindow.xaml.cs
@@ -36,6 +36,7 @@ public partial class MainWindow : Window
private PreferencesWindow preferencesWindow;
private int uiRefreshCounter = 0;
+ private int anomRefreshCounter = 0;
private System.Windows.Threading.DispatcherTimer uiRefreshTimer;
private List InfoLayer;
@@ -702,6 +703,14 @@ private void UiRefreshTimer_Tick(object sender, EventArgs e)
{
UpdateCharacterSelectionBasedOnActiveWindow();
}
+
+ // refresh the anomalies datagrid every 60 seconds to update the "since" column
+ anomRefreshCounter++;
+ if (anomRefreshCounter == 60)
+ {
+ anomRefreshCounter = 0;
+ AnomSigList.Items.Refresh();
+ }
}
#region RegionsView Control
@@ -1974,6 +1983,40 @@ private void ZKBFeedFilterViewChk_Checked(object sender, RoutedEventArgs e)
#region Anoms
+ ///
+ /// The anomalies grid is clicked
+ ///
+ private void AnomSigList_PreviewMouseDown(object sender, MouseButtonEventArgs e)
+ {
+ // set focus to enable the grid to receive the PreviewKeyDown events
+ AnomSigList.Focus();
+ }
+
+ ///
+ /// Keys pressed while the anomalies grid is in focus
+ ///
+ private void AnomSigList_PreviewKeyDown(object sender, KeyEventArgs e)
+ {
+ // handle paste operation
+ if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.V)
+ {
+ updateAnomListFromClipboard();
+ e.Handled = true;
+ }
+ // handle copy operation
+ else if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.C)
+ {
+ copyAnomListToClipboard();
+ e.Handled = true;
+ }
+ // handle delete
+ else if (Keyboard.Modifiers == ModifierKeys.None && e.Key == Key.Delete)
+ {
+ deleteSelectedAnoms();
+ e.Handled = true;
+ }
+ }
+
///
/// Clear system Anoms button clicked
///
@@ -1989,24 +2032,100 @@ private void btnClearAnomList_Click(object sender, RoutedEventArgs e)
}
}
+ ///
+ /// Delete selected Anoms button clicked
+ ///
+ private void btnDeleteSelectedAnoms_Click(object sender, RoutedEventArgs e)
+ {
+ deleteSelectedAnoms();
+ }
+
///
/// Update Anoms clicked
///
private void btnUpdateAnomList_Click(object sender, RoutedEventArgs e)
{
+ updateAnomListFromClipboard();
+ }
+
+ private void updateAnomListFromClipboard()
+ {
+ EVEData.AnomData ad = ANOMManager.ActiveSystem;
string pasteData = Clipboard.GetText();
- if (!string.IsNullOrEmpty(pasteData))
+
+ if (ad == null || string.IsNullOrEmpty(pasteData))
+ return;
+
+ HashSet missingSignatures = ad.UpdateFromPaste(pasteData);
+
+ // Clear current selection
+ AnomSigList.SelectedItems.Clear();
+
+ foreach (var item in AnomSigList.Items)
{
- EVEData.AnomData ad = ANOMManager.ActiveSystem;
+ var anom = item as Anom;
+ if (anom != null && missingSignatures.Contains(anom.Signature))
+ AnomSigList.SelectedItems.Add(item);
+ }
+
+ if (AnomSigList.SelectedItems.Count > 0)
+ AnomSigList.ScrollIntoView(AnomSigList.SelectedItems[0]);
+
+ AnomSigList.Items.Refresh();
+ AnomSigList.UpdateLayout();
+ CollectionViewSource.GetDefaultView(AnomSigList.ItemsSource).Refresh();
+ }
- if (ad != null)
+ private void copyAnomListToClipboard()
+ {
+ EVEData.AnomData ad = ANOMManager.ActiveSystem;
+ if (ad == null)
+ return;
+
+ var str = string.Empty;
+ if (AnomSigList.SelectedItems.Count > 0)
+ {
+ // copy the selected items to clipboard
+ foreach (var entry in AnomSigList.SelectedItems)
+ {
+ var anom = entry as Anom;
+ str += anom.ToString() + "\n";
+ }
+ }
+ else
+ {
+ // copy the entire list
+ foreach (var entry in ad.Anoms)
{
- ad.UpdateFromPaste(pasteData);
- AnomSigList.Items.Refresh();
- AnomSigList.UpdateLayout();
- CollectionViewSource.GetDefaultView(AnomSigList.ItemsSource).Refresh();
+ var anom = entry.Value;
+ str += anom.ToString() + "\n";
}
}
+
+ Clipboard.SetText(str);
+ }
+
+ private void deleteSelectedAnoms()
+ {
+ EVEData.AnomData ad = ANOMManager.ActiveSystem;
+ if (ad == null || AnomSigList.SelectedItems.Count == 0)
+ return;
+
+ var selectedSignatures = AnomSigList.SelectedItems.Cast()
+ .Select(anom => anom.Signature)
+ .ToHashSet();
+
+ var keysToRemove = ad.Anoms
+ .Where(kvp => selectedSignatures.Contains(kvp.Value.Signature))
+ .Select(kvp => kvp.Key)
+ .ToList();
+
+ foreach (var key in keysToRemove)
+ ad.Anoms.Remove(key);
+
+ AnomSigList.Items.Refresh();
+ AnomSigList.UpdateLayout();
+ CollectionViewSource.GetDefaultView(AnomSigList.ItemsSource).Refresh();
}
#endregion Anoms
@@ -2521,6 +2640,43 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu
}
}
+ ///
+ /// Convert time elapsed to simple human format
+ ///
+ public class TimeSinceConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is not DateTime timeFound)
+ return "Unknown";
+
+ var now = DateTime.Now;
+ var elapsed = now - timeFound;
+
+ if (elapsed.TotalDays >= 1.0 && elapsed.TotalDays < 2.0)
+ return $"{(int)elapsed.TotalDays} day";
+ else if (elapsed.TotalDays >= 2.0)
+ return $"{(int)elapsed.TotalDays} days";
+
+ if (elapsed.TotalHours >= 1.0 && elapsed.TotalHours < 2.0)
+ return $"{(int)elapsed.TotalHours} hour";
+ else if (elapsed.TotalHours >= 2.0)
+ return $"{(int)elapsed.TotalHours} hours";
+
+ if (elapsed.TotalMinutes < 1.0)
+ return "Now";
+ else if (elapsed.TotalMinutes < 2.0)
+ return $"{(int)elapsed.TotalMinutes} minute";
+ else
+ return $"{(int)elapsed.TotalMinutes} minutes";
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
///
/// Window Flashing helper
///