From db6ecf86f07235ae2cf4c039dd12c707b04a36db Mon Sep 17 00:00:00 2001 From: Anthony Purchase Date: Thu, 8 Feb 2018 01:51:11 +0000 Subject: [PATCH] Improve resilience and error display Attempt to handle strange Windows issues with OpenFileDialog during Welcome screen. Added app-wide exception catcher to hopefully provide more information when unexpected things happen. Reduced default size of main window to fit on smaller desktops and force correct ratio on first run. Dev only: Added dev-only buttons to trigger dummy exceptions and to wipe current app settings. --- LarkatorGUI/App.xaml.cs | 49 +- LarkatorGUI/MainWindow.xaml | 10 +- LarkatorGUI/MainWindow.xaml.cs | 1533 ++++++++++--------- LarkatorGUI/Properties/Settings.Designer.cs | 4 +- LarkatorGUI/Properties/Settings.settings | 4 +- LarkatorGUI/Welcome.xaml.cs | 35 +- LarkatorGUI/app.config | 4 +- 7 files changed, 860 insertions(+), 779 deletions(-) diff --git a/LarkatorGUI/App.xaml.cs b/LarkatorGUI/App.xaml.cs index 2af1973..ba5d57c 100644 --- a/LarkatorGUI/App.xaml.cs +++ b/LarkatorGUI/App.xaml.cs @@ -1,4 +1,7 @@ -using System.Windows; +using System; +using System.Text; +using System.Threading.Tasks; +using System.Windows; namespace LarkatorGUI { @@ -10,11 +13,55 @@ public partial class App : Application protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); + +#if !DEBUG + RegisterExceptionHandlers(); +#endif + } + + private void RegisterExceptionHandlers() + { + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; } private void Application_Exit(object sender, ExitEventArgs e) { LarkatorGUI.Properties.Settings.Default.Save(); } + + private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + HandleUnhandledException(e.ExceptionObject as Exception); + } + + private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) + { + HandleUnhandledException(e.Exception); + } + + private void HandleUnhandledException(Exception ex) + { + if (ex == null) + { + MessageBox.Show("Null Exception!"); + return; + } + + var sb = new StringBuilder(); + sb.AppendLine("Sorry, something bad happened and the application must exit."); + sb.AppendLine(); + sb.AppendLine("If you wish to report this issue please press Ctrl-C now to copy the details and"); + sb.AppendLine("paste it into a new issue along with a description of what was happening"); + sb.AppendLine("at: https://github.com/coldino/Larkator/issues"); + sb.AppendLine("Thanks!"); + sb.AppendLine(); + sb.AppendLine("Exception:"); + sb.AppendLine(ex.ToString()); + + MessageBox.Show(sb.ToString(), "Whoops...", MessageBoxButton.OK, MessageBoxImage.Error); + + Environment.Exit(1); + } } } diff --git a/LarkatorGUI/MainWindow.xaml b/LarkatorGUI/MainWindow.xaml index f4ddfb5..05f0062 100644 --- a/LarkatorGUI/MainWindow.xaml +++ b/LarkatorGUI/MainWindow.xaml @@ -13,7 +13,7 @@ d:DataContext="{d:DesignInstance Type={x:Type local:DummyMainWindow}, IsDesignTimeCreatable=True}" SourceInitialized="Window_SourceInitialized" Loaded="Window_Loaded" Title="{Binding WindowTitle}" Background="{DynamicResource WindowBackgroundBrush}" - MaxHeight="1016" MinHeight="240" + MaxHeight="1016" MinHeight="500" Left="{Binding Source={StaticResource Settings}, Path=Default.MainWindowLeft, Mode=TwoWay}" Top="{Binding Source={StaticResource Settings}, Path=Default.MainWindowTop, Mode=TwoWay}" Width="{Binding Source={StaticResource Settings}, Path=Default.MainWindowWidth, Mode=TwoWay}" @@ -357,9 +357,11 @@ - - - + + + + + diff --git a/LarkatorGUI/MainWindow.xaml.cs b/LarkatorGUI/MainWindow.xaml.cs index 258ca98..fa583f3 100644 --- a/LarkatorGUI/MainWindow.xaml.cs +++ b/LarkatorGUI/MainWindow.xaml.cs @@ -19,753 +19,788 @@ namespace LarkatorGUI { - /// - /// Interaction logic for MainWindow.xaml - /// - public partial class MainWindow : Window, IDropTarget - { - public ObservableCollection ListSearches { get; } = new ObservableCollection(); - public Collection ListResults { get; } = new Collection(); - public List AllSpecies { get { return arkReaderWild.AllSpecies; } } - - public string ApplicationVersion - { - get - { - return appVersion; - } - } - - public string WindowTitle { get { return $"{Properties.Resources.ProgramName} {ApplicationVersion}"; } } - public MapCalibration MapCalibration { get; private set; } - public ImageSource MapImage { get; private set; } - - public bool IsLoading - { - get { return (bool)GetValue(IsLoadingProperty); } - set { SetValue(IsLoadingProperty, value); } - } - - public string StatusText - { - get { return (string)GetValue(StatusTextProperty); } - set { SetValue(StatusTextProperty, value); } - } - - public string StatusDetailText - { - get { return (string)GetValue(StatusDetailTextProperty); } - set { SetValue(StatusDetailTextProperty, value); } - } - - public SearchCriteria NewSearch - { - get { return (SearchCriteria)GetValue(NewSearchProperty); } - set { SetValue(NewSearchProperty, value); } - } - - public bool CreateSearchAvailable - { - get { return (bool)GetValue(CreateSearchAvailableProperty); } - set { SetValue(CreateSearchAvailableProperty, value); } - } - - public bool NewSearchActive - { - get { return (bool)GetValue(NewSearchActiveProperty); } - set { SetValue(NewSearchActiveProperty, value); } - } - - public bool ShowHunt - { - get { return (bool)GetValue(ShowHuntProperty); } - set { SetValue(ShowHuntProperty, value); } - } - - public bool ShowTames - { - get { return (bool)GetValue(ShowTamesProperty); } - set { SetValue(ShowTamesProperty, value); } - } - - public ImageSource ImageSource - { - get { return (ImageSource)GetValue(ImageSourceProperty); } - set { SetValue(ImageSourceProperty, value); } - } - - public static readonly DependencyProperty ImageSourceProperty = - DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(MainWindow), new PropertyMetadata(null)); - - public static readonly DependencyProperty ShowTamesProperty = - DependencyProperty.Register("ShowTames", typeof(bool), typeof(MainWindow), new PropertyMetadata(false)); - - public static readonly DependencyProperty ShowHuntProperty = - DependencyProperty.Register("ShowHunt", typeof(bool), typeof(MainWindow), new PropertyMetadata(true)); - - public static readonly DependencyProperty NewSearchActiveProperty = - DependencyProperty.Register("NewSearchActive", typeof(bool), typeof(MainWindow), new PropertyMetadata(false)); - - public static readonly DependencyProperty CreateSearchAvailableProperty = - DependencyProperty.Register("CreateSearchAvailable", typeof(bool), typeof(MainWindow), new PropertyMetadata(true)); - - public static readonly DependencyProperty NewSearchProperty = - DependencyProperty.Register("NewSearch", typeof(SearchCriteria), typeof(MainWindow), new PropertyMetadata(null)); - - public static readonly DependencyProperty IsLoadingProperty = - DependencyProperty.Register("IsLoading", typeof(bool), typeof(MainWindow), new PropertyMetadata(false)); - - public static readonly DependencyProperty StatusTextProperty = - DependencyProperty.Register("StatusText", typeof(string), typeof(MainWindow), new PropertyMetadata("")); - - public static readonly DependencyProperty StatusDetailTextProperty = - DependencyProperty.Register("StatusDetailText", typeof(string), typeof(MainWindow), new PropertyMetadata("")); - - - ArkReader arkReaderWild; - ArkReader arkReaderTamed; - FileSystemWatcher fileWatcher; - DispatcherTimer reloadTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) }; - private List mapCalibrations; - private readonly string appVersion; - readonly List nullableBoolValues = new List { null, false, true }; - - public MainWindow() - { - ValidateWindowPositionAndSize(); - - arkReaderWild = new ArkReader(true); - arkReaderTamed = new ArkReader(false); - - appVersion = CalculateApplicationVersion(); - - LoadCalibrations(); - DiscoverCalibration(); - - DataContext = this; - - InitializeComponent(); - - devButtons.Visibility = (ApplicationVersion == "DEVELOPMENT") ? Visibility.Visible : Visibility.Collapsed; - - LoadSavedSearches(); - EnsureOutputDirectory(); - SetupFileWatcher(); - CheckIfArkChanged(false); - } - - private static string CalculateApplicationVersion() - { - try - { - return ApplicationDeployment.CurrentDeployment.CurrentVersion.ToString(); - } - catch (InvalidDeploymentException) - { - return "DEVELOPMENT"; - } - } - - private void SetupFileWatcher() - { - if (fileWatcher != null) fileWatcher.EnableRaisingEvents = false; - fileWatcher = new FileSystemWatcher(Path.GetDirectoryName(Properties.Settings.Default.SaveFile)); - fileWatcher.Renamed += FileWatcher_Changed; - fileWatcher.EnableRaisingEvents = true; - reloadTimer.Interval = TimeSpan.FromMilliseconds(Properties.Settings.Default.ConvertDelay); - reloadTimer.Tick += ReloadTimer_Tick; - } - - private void LoadCalibrations() - { - mapCalibrations = JsonConvert.DeserializeObject>(Properties.Resources.calibrationsJson); - } - - private void DiscoverCalibration() - { - var filename = Properties.Settings.Default.SaveFile; - filename = Path.GetFileNameWithoutExtension(filename); - var best = mapCalibrations.FirstOrDefault(cal => filename.StartsWith(cal.Filename)); - if (best == null) - { - StatusText = "Warning: Unable to determine map from filename - defaulting to The Island"; - MapCalibration = mapCalibrations.Single(cal => cal.Filename == "TheIsland"); - } - else - { - MapCalibration = best; - } - - var imgFilename = $"pack://application:,,,/imgs/map_{MapCalibration.Filename}.jpg"; - MapImage = (new ImageSourceConverter()).ConvertFromString(imgFilename) as ImageSource; - if (image != null) image.Source = MapImage; - } - - private void ValidateWindowPositionAndSize() - { - var settings = Properties.Settings.Default; - - if (settings.MainWindowLeft <= -10000 || settings.MainWindowTop <= -10000) - { - WindowStartupLocation = WindowStartupLocation.CenterScreen; - } - - if (settings.MainWindowWidth < 0 || settings.MainWindowHeight < 0) - { - settings.MainWindowWidth = (double)settings.Properties["MainWindowWidth"].DefaultValue; - settings.MainWindowHeight = (double)settings.Properties["MainWindowHeight"].DefaultValue; - settings.Save(); - } - } - - private void EnsureOutputDirectory() - { - if (String.IsNullOrWhiteSpace(Properties.Settings.Default.OutputDir)) - { - Properties.Settings.Default.OutputDir = Path.Combine(Path.GetTempPath(), Properties.Resources.ProgramName); - if (!Directory.Exists(Properties.Settings.Default.OutputDir)) - { - Directory.CreateDirectory(Properties.Settings.Default.OutputDir); - Properties.Settings.Default.Save(); - } - } - } - - private void CheckIfArkChanged(bool reconvert = true) - { - var arkChanged = false; - try - { - var lastArk = File.ReadAllText(Path.Combine(Properties.Settings.Default.OutputDir, Properties.Resources.LastArkFile)); - if (lastArk != Properties.Settings.Default.SaveFile) arkChanged = true; - } - catch - { - arkChanged = true; - } - - if (arkChanged) - { - arkReaderTamed.ForceNextConversion = true; - arkReaderWild.ForceNextConversion = true; - - if (reconvert) - { - NotifyArkChanged(); - } - } - } - - private void NotifyArkChanged() - { - // Cause a fresh conversion of the new ark - Dispatcher.Invoke(() => ReReadArk(force: true), DispatcherPriority.Background); - - // Ensure the file watcher is watching the right directory - fileWatcher.Path = Path.GetDirectoryName(Properties.Settings.Default.SaveFile); - } - - private void FileWatcher_Changed(object sender, FileSystemEventArgs e) - { - if (!String.Equals(e.FullPath, Properties.Settings.Default.SaveFile)) return; - - Dispatcher.Invoke(() => - { - StatusText = "Detected change to saved ARK..."; - StatusDetailText = "...waiting"; - }); - - // Cancel any existing timer to ensure we're not called multiple times - if (reloadTimer.IsEnabled) reloadTimer.Stop(); - - reloadTimer.Start(); - } - - private async void ReloadTimer_Tick(object sender, EventArgs e) - { - reloadTimer.Stop(); - await Dispatcher.InvokeAsync(() => ReReadArk(), DispatcherPriority.Background); - } - - private async void Window_Loaded(object sender, RoutedEventArgs e) - { - await UpdateArkToolsData(); - await ReReadArk(); - } - - private void Searches_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - UpdateCurrentSearch(); - } - - private void RemoveSearch_Click(object sender, RoutedEventArgs e) - { - if (ShowTames) return; - - var button = sender as Button; - if (button?.DataContext is SearchCriteria search) ListSearches.Remove(search); - UpdateCurrentSearch(); - - MarkSearchesChanged(); - } - - private void MapPin_Click(object sender, MouseButtonEventArgs ev) - { - if (sender is FrameworkElement e && e.DataContext is DinoViewModel dvm) - { - dvm.Highlight = !dvm.Highlight; - if (dvm.Highlight) resultsList.ScrollIntoView(dvm); - } - } - - private void Window_SourceInitialized(object sender, EventArgs e) - { - // Handler to maintain window aspect ratio - WindowAspectRatio.Register((Window)sender, height => (int)Math.Round( - height - statusPanel.ActualHeight - 2 * SystemParameters.ResizeFrameHorizontalBorderHeight - SystemParameters.WindowCaptionHeight - + leftPanel.ActualWidth + rightPanel.ActualWidth + 2 * SystemParameters.ResizeFrameVerticalBorderWidth)); - } - - private void CreateSearch_Click(object sender, RoutedEventArgs e) - { - NewSearch = new SearchCriteria(); - NewSearchActive = true; - CreateSearchAvailable = false; - - speciesCombo.ItemsSource = arkReaderWild.AllSpecies; - groupsCombo.ItemsSource = ListSearches.Select(sc => sc.Group).Distinct().OrderBy(g => g).ToArray(); - } - - private void Calibration_Click(object sender, MouseButtonEventArgs e) - { - var win = new CalibrationWindow(new Calibration { Bounds = new Bounds() }); - win.ShowDialog(); - } - - private void DummyData_Click(object sender, MouseButtonEventArgs e) - { - ListResults.Clear(); - - var dummyData = new Dino[] { - new Dino { Location=new Position{ Lat=10,Lon=10 }, Type="Testificate", Name="10,10" }, - new Dino { Location=new Position{ Lat=90,Lon=10 }, Type="Testificate", Name="90,10" }, - new Dino { Location=new Position{ Lat=10,Lon=90 }, Type="Testificate", Name="10,90" }, - new Dino { Location=new Position{ Lat=90,Lon=90 }, Type="Testificate", Name="90,90" }, - new Dino { Location=new Position{ Lat=50,Lon=50 }, Type="Testificate", Name="50,50" }, - }; - - var rnd = new Random(); - foreach (var result in dummyData) - { - result.Id = (ulong)rnd.Next(); - DinoViewModel vm = new DinoViewModel(result) { Color = Colors.Green }; - ListResults.Add(vm); - } - - var cv = (CollectionView)CollectionViewSource.GetDefaultView(ListResults); - cv.Refresh(); - } - - private async void SaveSearch_Click(object sender, RoutedEventArgs e) - { - if (String.IsNullOrWhiteSpace(NewSearch.Species)) return; - - try - { - NewSearch.Order = ListSearches.Where(sc => sc.Group == NewSearch.Group).Max(sc => sc.Order) + 100; - } - catch (InvalidOperationException) // no entries for .Max - ignore - { } - - IsLoading = true; - try - { - StatusText = $"Loading {NewSearch.Species}..."; - await arkReaderWild.EnsureSpeciesIsLoaded(NewSearch.Species); - StatusText = $"Ready"; - } - finally - { - IsLoading = false; - } - - ListSearches.Add(NewSearch); - NewSearch = null; - NewSearchActive = false; - CreateSearchAvailable = true; - - MarkSearchesChanged(); - } - - private void CloseNewSearch_Click(object sender, MouseButtonEventArgs e) - { - NewSearchActive = false; - CreateSearchAvailable = true; - } - - private void ShowTames_Click(object sender, MouseButtonEventArgs e) - { - ShowTames = true; - ShowHunt = false; - NewSearchActive = false; - CreateSearchAvailable = false; - - ShowTameSearches(); - } - - private void ShowTheHunt_Click(object sender, MouseButtonEventArgs e) - { - ShowTames = false; - ShowHunt = true; - NewSearchActive = false; - CreateSearchAvailable = true; - - ShowWildSearches(); - } - - private void Settings_Click(object sender, MouseButtonEventArgs e) - { - var settings = new SettingsWindow(); - settings.ShowDialog(); - - OnSettingsChanged(); - } - - private void AdjustableInteger_MouseWheel(object sender, MouseWheelEventArgs e) - { - var tb = (TextBlock)sender; - var diff = Math.Sign(e.Delta) * Properties.Settings.Default.LevelStep; - var bexpr = tb.GetBindingExpression(TextBlock.TextProperty); - var accessor = TypeAccessor.Create(typeof(SearchCriteria)); - var value = (int?)accessor[bexpr.ResolvedSource, bexpr.ResolvedSourcePropertyName]; - if (value.HasValue) - { - value = value + diff; - if (value < 0 || value > Properties.Settings.Default.MaxLevel) value = null; - } - else - { - value = (diff > 0) ? 0 : Properties.Settings.Default.MaxLevel; - } - - accessor[bexpr.ResolvedSource, bexpr.ResolvedSourcePropertyName] = value; - bexpr.UpdateTarget(); - - if (null != searchesList.SelectedItem) - UpdateCurrentSearch(); - - MarkSearchesChanged(); - } - - private void AdjustableGender_MouseWheel(object sender, MouseWheelEventArgs e) - { - var im = (Image)sender; - var diff = Math.Sign(e.Delta); - var nOptions = nullableBoolValues.Count; - var bexpr = im.GetBindingExpression(Image.SourceProperty); - var accessor = TypeAccessor.Create(typeof(SearchCriteria)); - var value = (bool?)accessor[bexpr.ResolvedSource, bexpr.ResolvedSourcePropertyName]; - var index = nullableBoolValues.IndexOf(value); - index = (index + diff + nOptions) % nOptions; - value = nullableBoolValues[index]; - accessor[bexpr.ResolvedSource, bexpr.ResolvedSourcePropertyName] = value; - bexpr.UpdateTarget(); - - if (null != searchesList.SelectedItem) - UpdateCurrentSearch(); - - MarkSearchesChanged(); - } - - private void Result_MouseEnter(object sender, MouseEventArgs e) - { - var fe = sender as FrameworkElement; - if (fe == null) return; - var dino = fe.DataContext as DinoViewModel; - if (dino == null) return; - dino.Highlight = true; - } - - private void Result_MouseLeave(object sender, MouseEventArgs e) - { - var fe = sender as FrameworkElement; - if (fe == null) return; - var dino = fe.DataContext as DinoViewModel; - if (dino == null) return; - dino.ClearValue(DinoViewModel.HighlightProperty); - } - - private void ResultList_Sorting(object sender, DataGridSortingEventArgs e) - { - if (e.Column.SortDirection == null) - e.Column.SortDirection = ListSortDirection.Ascending; - - e.Handled = false; - } - - private void MarkSearchesChanged() - { - SaveSearches(); - } - - private void SaveSearches() - { - Properties.Settings.Default.SavedSearches = JsonConvert.SerializeObject(ListSearches); - Properties.Settings.Default.Save(); - } - - private void LoadSavedSearches() - { - if (!String.IsNullOrWhiteSpace(Properties.Settings.Default.SavedSearches)) - { - Collection searches; - try - { - searches = JsonConvert.DeserializeObject>(Properties.Settings.Default.SavedSearches); - } - catch (Exception e) - { - Console.WriteLine("Exception reading saved searches: " + e.ToString()); - return; - } - - ListSearches.Clear(); - foreach (var search in searches) - ListSearches.Add(search); - } - } - - private async Task UpdateArkToolsData() - { - StatusText = "Updating ark-tools database"; - try - { - await ArkReader.ExecuteArkTools("update-data"); - StatusText = "Updated ark-tools database"; - } - catch (Exception e) - { - StatusText = "Failed to update ark-tools database: " + e.Message; - } - } - - private async Task ReReadArk(bool force = false) - { - if (IsLoading) return; - - await PerformConversion(force); - await LoadSearchSpecies(); - - var currentSearch = searchesList.SelectedItems.Cast().ToList(); - UpdateSearchResults(currentSearch); - } - - private async Task LoadSearchSpecies() - { - IsLoading = true; - try - { - var species = arkReaderWild.AllSpecies.Distinct(); - foreach (var speciesName in species) - { - StatusText = $"Loading {speciesName}..."; - await arkReaderWild.EnsureSpeciesIsLoaded(speciesName); - } - - species = arkReaderTamed.AllSpecies.Distinct(); - foreach (var speciesName in species) - { - StatusText = $"Loading {speciesName}..."; - await arkReaderTamed.EnsureSpeciesIsLoaded(speciesName); - } - - StatusText = "Ready"; - } - finally - { - IsLoading = false; - } - } - - private void UpdateSearchResults(IList searches) - { - if (searches == null || searches.Count == 0) - { - ListResults.Clear(); - } - else - { - // Find dinos that match the given searches - var found = new List(); - var reader = ShowTames ? arkReaderTamed : arkReaderWild; - foreach (var search in searches) - { - if (String.IsNullOrWhiteSpace(search.Species)) - { - foreach (var speciesDinos in reader.FoundDinos.Values) - found.AddRange(speciesDinos); - } - else - { - if (reader.FoundDinos.ContainsKey(search.Species)) - { - var dinoList = reader.FoundDinos[search.Species]; - found.AddRange(dinoList.Where(d => search.Matches(d))); - } - } - } - - ListResults.Clear(); - foreach (var result in found) - ListResults.Add(result); - } - - ((CollectionViewSource)Resources["OrderedResults"]).View.Refresh(); - } - - private async Task PerformConversion(bool force) - { - string arkDirName = Path.GetFileNameWithoutExtension(Properties.Settings.Default.SaveFile); - - IsLoading = true; - try - { - StatusDetailText = "...converting"; - StatusText = "Processing saved ARK : Wild"; - await arkReaderWild.PerformConversion(force, arkDirName); - StatusText = "Processing saved ARK : Tamed"; - await arkReaderTamed.PerformConversion(force, arkDirName); - StatusText = "ARK processing completed"; - StatusDetailText = $"{arkReaderWild.NumberOfSpecies} wild and {arkReaderTamed.NumberOfSpecies} tame species located"; - - // Write path to last ark into the output folder so we can check when we change ARKs - File.WriteAllText(Path.Combine(Properties.Settings.Default.OutputDir, Properties.Resources.LastArkFile), Properties.Settings.Default.SaveFile); - } - catch (Exception ex) - { - StatusText = "ARK processing failed"; - StatusDetailText = ""; - MessageBox.Show(ex.Message, "ARK Tools Error", MessageBoxButton.OK, MessageBoxImage.Exclamation); - } - finally - { - IsLoading = false; - } - } - - private void UpdateCurrentSearch() - { - var search = (SearchCriteria)searchesList.SelectedItem; - var searches = new List(); - if (search != null) searches.Add(search); - UpdateSearchResults(searches); - } - - void IDropTarget.DragOver(IDropInfo dropInfo) - { - if (dropInfo.TargetItem is SearchCriteria targetItem && dropInfo.Data is SearchCriteria sourceItem) - { - dropInfo.DropTargetAdorner = DropTargetAdorners.Insert; - dropInfo.Effects = DragDropEffects.Move; - } - } - - void IDropTarget.Drop(IDropInfo dropInfo) - { - var sourceItem = (SearchCriteria)dropInfo.Data; - var targetItem = (SearchCriteria)dropInfo.TargetItem; - - var ii = dropInfo.InsertIndex; - var ip = dropInfo.InsertPosition; - - // Change source item's group - sourceItem.Group = targetItem.Group; - - // Try to figure out the other item to insert between, or pick a boundary - var options = ListSearches - .Where(sc => sc != sourceItem) - .Where(sc => sc.Group == targetItem.Group) - .OrderBy(sc => sc.Order) - .ToArray(); - - var above = options.Where(sc => sc.Order < targetItem.Order).OrderByDescending(sc => sc.Order).FirstOrDefault(); - var below = options.Where(sc => sc.Order > targetItem.Order).OrderBy(sc => sc.Order).FirstOrDefault(); - - var aboveOrder = (above == null) ? options.Min(sc => sc.Order) - 1 : above.Order; - var belowOrder = (below == null) ? options.Max(sc => sc.Order) + 1 : below.Order; - - // Update the order to be mid-way between either above or below, based on drag insert position - sourceItem.Order = (targetItem.Order + (ip.HasFlag(RelativeInsertPosition.AfterTargetItem) ? belowOrder : aboveOrder)) / 2; - - // Renumber the results - var orderedSearches = ListSearches - .OrderBy(sc => sc.Group) - .ThenBy(sc => sc.Order) - .ToArray(); - - for (var i = 0; i < orderedSearches.Length; i++) - { - orderedSearches[i].Order = i; - } - - // Force binding update - CollectionViewSource.GetDefaultView(searchesList.ItemsSource).Refresh(); - - // Save list - MarkSearchesChanged(); - } - - private void ShowTameSearches() - { - SetupTamedSearches(); - } - - private void SetupTamedSearches() - { - var wildcard = new string[] { null }; - var speciesList = wildcard.Concat(arkReaderTamed.AllSpecies).ToList(); - var orderList = Enumerable.Range(0, speciesList.Count); - var searches = speciesList.Zip(orderList, (species, order) => new SearchCriteria { Species = species, Order = order }); - - ListSearches.Clear(); - foreach (var search in searches) - ListSearches.Add(search); - } - - private void ShowWildSearches() - { - LoadSavedSearches(); - } - - private void OnSettingsChanged() - { - DiscoverCalibration(); - EnsureOutputDirectory(); - CheckIfArkChanged(); - UpdateCurrentSearch(); - - ForceFontSizeUpdate(); - - reloadTimer.Interval = TimeSpan.FromMilliseconds(Properties.Settings.Default.ConvertDelay); - } - - private void ForceFontSizeUpdate() - { - Dispatcher.Invoke(() => RefreshDataGridColumnWidths("GroupedSearchCriteria", searchesList), DispatcherPriority.ContextIdle); - Dispatcher.Invoke(() => RefreshDataGridColumnWidths("OrderedResults", resultsList), DispatcherPriority.ContextIdle); - } - - private void RefreshDataGridColumnWidths(string resourceName, DataGrid dataGrid) - { - var widths = dataGrid.Columns.Select(col => col.Width).ToArray(); - foreach (var col in dataGrid.Columns) - col.Width = 0; - - ((CollectionViewSource)Resources[resourceName]).View.Refresh(); - dataGrid.UpdateLayout(); - - foreach (var o in dataGrid.Columns.Zip(widths, (col, width) => new { col, width })) - o.col.Width = o.width; - } - } + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window, IDropTarget + { + private const string DEV_STRING = "DEVELOPMENT"; + + public ObservableCollection ListSearches { get; } = new ObservableCollection(); + public Collection ListResults { get; } = new Collection(); + public List AllSpecies { get { return arkReaderWild.AllSpecies; } } + + public string ApplicationVersion + { + get + { + return appVersion; + } + } + + public string WindowTitle { get { return $"{Properties.Resources.ProgramName} {ApplicationVersion}"; } } + public MapCalibration MapCalibration { get; private set; } + public ImageSource MapImage { get; private set; } + + public bool IsLoading + { + get { return (bool)GetValue(IsLoadingProperty); } + set { SetValue(IsLoadingProperty, value); } + } + + public string StatusText + { + get { return (string)GetValue(StatusTextProperty); } + set { SetValue(StatusTextProperty, value); } + } + + public string StatusDetailText + { + get { return (string)GetValue(StatusDetailTextProperty); } + set { SetValue(StatusDetailTextProperty, value); } + } + + public SearchCriteria NewSearch + { + get { return (SearchCriteria)GetValue(NewSearchProperty); } + set { SetValue(NewSearchProperty, value); } + } + + public bool CreateSearchAvailable + { + get { return (bool)GetValue(CreateSearchAvailableProperty); } + set { SetValue(CreateSearchAvailableProperty, value); } + } + + public bool NewSearchActive + { + get { return (bool)GetValue(NewSearchActiveProperty); } + set { SetValue(NewSearchActiveProperty, value); } + } + + public bool ShowHunt + { + get { return (bool)GetValue(ShowHuntProperty); } + set { SetValue(ShowHuntProperty, value); } + } + + public bool ShowTames + { + get { return (bool)GetValue(ShowTamesProperty); } + set { SetValue(ShowTamesProperty, value); } + } + + public ImageSource ImageSource + { + get { return (ImageSource)GetValue(ImageSourceProperty); } + set { SetValue(ImageSourceProperty, value); } + } + + public static readonly DependencyProperty ImageSourceProperty = + DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(MainWindow), new PropertyMetadata(null)); + + public static readonly DependencyProperty ShowTamesProperty = + DependencyProperty.Register("ShowTames", typeof(bool), typeof(MainWindow), new PropertyMetadata(false)); + + public static readonly DependencyProperty ShowHuntProperty = + DependencyProperty.Register("ShowHunt", typeof(bool), typeof(MainWindow), new PropertyMetadata(true)); + + public static readonly DependencyProperty NewSearchActiveProperty = + DependencyProperty.Register("NewSearchActive", typeof(bool), typeof(MainWindow), new PropertyMetadata(false)); + + public static readonly DependencyProperty CreateSearchAvailableProperty = + DependencyProperty.Register("CreateSearchAvailable", typeof(bool), typeof(MainWindow), new PropertyMetadata(true)); + + public static readonly DependencyProperty NewSearchProperty = + DependencyProperty.Register("NewSearch", typeof(SearchCriteria), typeof(MainWindow), new PropertyMetadata(null)); + + public static readonly DependencyProperty IsLoadingProperty = + DependencyProperty.Register("IsLoading", typeof(bool), typeof(MainWindow), new PropertyMetadata(false)); + + public static readonly DependencyProperty StatusTextProperty = + DependencyProperty.Register("StatusText", typeof(string), typeof(MainWindow), new PropertyMetadata("")); + + public static readonly DependencyProperty StatusDetailTextProperty = + DependencyProperty.Register("StatusDetailText", typeof(string), typeof(MainWindow), new PropertyMetadata("")); + + + ArkReader arkReaderWild; + ArkReader arkReaderTamed; + FileSystemWatcher fileWatcher; + DispatcherTimer reloadTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) }; + private List mapCalibrations; + private readonly string appVersion; + readonly List nullableBoolValues = new List { null, false, true }; + + public MainWindow() + { + ValidateWindowPositionAndSize(); + + arkReaderWild = new ArkReader(true); + arkReaderTamed = new ArkReader(false); + + appVersion = CalculateApplicationVersion(); + + LoadCalibrations(); + DiscoverCalibration(); + + DataContext = this; + + InitializeComponent(); + + devButtons.Visibility = (ApplicationVersion == DEV_STRING) ? Visibility.Visible : Visibility.Collapsed; + Dispatcher.Invoke(async () => + { + await Task.Yield(); + Properties.Settings.Default.MainWindowWidth = CalculateWidthFromHeight((int)Math.Round(Properties.Settings.Default.MainWindowHeight)); + }, DispatcherPriority.Loaded); + + LoadSavedSearches(); + EnsureOutputDirectory(); + SetupFileWatcher(); + CheckIfArkChanged(false); + + var cmdThrowExceptionAndExit = new RoutedCommand(); + cmdThrowExceptionAndExit.InputGestures.Add(new KeyGesture(Key.F2, ModifierKeys.Control)); + CommandBindings.Add(new CommandBinding(cmdThrowExceptionAndExit, (o, e) => Dev_GenerateException_Click(null, null))); + } + + private static string CalculateApplicationVersion() + { + try + { + return ApplicationDeployment.CurrentDeployment.CurrentVersion.ToString(); + } + catch (InvalidDeploymentException) + { + return DEV_STRING; + } + } + + private void SetupFileWatcher() + { + if (fileWatcher != null) fileWatcher.EnableRaisingEvents = false; + fileWatcher = new FileSystemWatcher(Path.GetDirectoryName(Properties.Settings.Default.SaveFile)); + fileWatcher.Renamed += FileWatcher_Changed; + fileWatcher.EnableRaisingEvents = true; + reloadTimer.Interval = TimeSpan.FromMilliseconds(Properties.Settings.Default.ConvertDelay); + reloadTimer.Tick += ReloadTimer_Tick; + } + + private void LoadCalibrations() + { + mapCalibrations = JsonConvert.DeserializeObject>(Properties.Resources.calibrationsJson); + } + + private void DiscoverCalibration() + { + var filename = Properties.Settings.Default.SaveFile; + filename = Path.GetFileNameWithoutExtension(filename); + var best = mapCalibrations.FirstOrDefault(cal => filename.StartsWith(cal.Filename)); + if (best == null) + { + StatusText = "Warning: Unable to determine map from filename - defaulting to The Island"; + MapCalibration = mapCalibrations.Single(cal => cal.Filename == "TheIsland"); + } + else + { + MapCalibration = best; + } + + var imgFilename = $"pack://application:,,,/imgs/map_{MapCalibration.Filename}.jpg"; + MapImage = (new ImageSourceConverter()).ConvertFromString(imgFilename) as ImageSource; + if (image != null) image.Source = MapImage; + } + + private void ValidateWindowPositionAndSize() + { + var settings = Properties.Settings.Default; + + if (settings.MainWindowLeft <= -10000 || settings.MainWindowTop <= -10000) + { + WindowStartupLocation = WindowStartupLocation.CenterScreen; + } + + if (settings.MainWindowWidth < 0 || settings.MainWindowHeight < 0) + { + settings.MainWindowWidth = (double)settings.Properties["MainWindowWidth"].DefaultValue; + settings.MainWindowHeight = (double)settings.Properties["MainWindowHeight"].DefaultValue; + settings.Save(); + } + } + + private void EnsureOutputDirectory() + { + if (String.IsNullOrWhiteSpace(Properties.Settings.Default.OutputDir)) + { + Properties.Settings.Default.OutputDir = Path.Combine(Path.GetTempPath(), Properties.Resources.ProgramName); + if (!Directory.Exists(Properties.Settings.Default.OutputDir)) + { + Directory.CreateDirectory(Properties.Settings.Default.OutputDir); + Properties.Settings.Default.Save(); + } + } + } + + private void CheckIfArkChanged(bool reconvert = true) + { + var arkChanged = false; + try + { + var lastArk = File.ReadAllText(Path.Combine(Properties.Settings.Default.OutputDir, Properties.Resources.LastArkFile)); + if (lastArk != Properties.Settings.Default.SaveFile) arkChanged = true; + } + catch + { + arkChanged = true; + } + + if (arkChanged) + { + arkReaderTamed.ForceNextConversion = true; + arkReaderWild.ForceNextConversion = true; + + if (reconvert) + { + NotifyArkChanged(); + } + } + } + + private void NotifyArkChanged() + { + // Cause a fresh conversion of the new ark + Dispatcher.Invoke(() => ReReadArk(force: true), DispatcherPriority.Background); + + // Ensure the file watcher is watching the right directory + fileWatcher.Path = Path.GetDirectoryName(Properties.Settings.Default.SaveFile); + } + + private void FileWatcher_Changed(object sender, FileSystemEventArgs e) + { + if (!String.Equals(e.FullPath, Properties.Settings.Default.SaveFile)) return; + + Dispatcher.Invoke(() => + { + StatusText = "Detected change to saved ARK..."; + StatusDetailText = "...waiting"; + }); + + // Cancel any existing timer to ensure we're not called multiple times + if (reloadTimer.IsEnabled) reloadTimer.Stop(); + + reloadTimer.Start(); + } + + private async void ReloadTimer_Tick(object sender, EventArgs e) + { + reloadTimer.Stop(); + await Dispatcher.InvokeAsync(() => ReReadArk(), DispatcherPriority.Background); + } + + private async void Window_Loaded(object sender, RoutedEventArgs e) + { + await UpdateArkToolsData(); + await ReReadArk(); + } + + private void Searches_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + UpdateCurrentSearch(); + } + + private void RemoveSearch_Click(object sender, RoutedEventArgs e) + { + if (ShowTames) return; + + var button = sender as Button; + if (button?.DataContext is SearchCriteria search) ListSearches.Remove(search); + UpdateCurrentSearch(); + + MarkSearchesChanged(); + } + + private void MapPin_Click(object sender, MouseButtonEventArgs ev) + { + if (sender is FrameworkElement e && e.DataContext is DinoViewModel dvm) + { + dvm.Highlight = !dvm.Highlight; + if (dvm.Highlight) resultsList.ScrollIntoView(dvm); + } + } + + private void Window_SourceInitialized(object sender, EventArgs e) + { + // Handler to maintain window aspect ratio + WindowAspectRatio.Register((Window)sender, CalculateWidthFromHeight); + } + + private int CalculateWidthFromHeight(int height) + { + return (int)Math.Round(height + - statusPanel.ActualHeight + - 2 * SystemParameters.ResizeFrameHorizontalBorderHeight + - SystemParameters.WindowCaptionHeight + + leftPanel.ActualWidth + rightPanel.ActualWidth + + 2 * SystemParameters.ResizeFrameVerticalBorderWidth); + } + + private void CreateSearch_Click(object sender, RoutedEventArgs e) + { + NewSearch = new SearchCriteria(); + NewSearchActive = true; + CreateSearchAvailable = false; + + speciesCombo.ItemsSource = arkReaderWild.AllSpecies; + groupsCombo.ItemsSource = ListSearches.Select(sc => sc.Group).Distinct().OrderBy(g => g).ToArray(); + } + + private void Dev_Calibration_Click(object sender, MouseButtonEventArgs e) + { + var win = new CalibrationWindow(new Calibration { Bounds = new Bounds() }); + win.ShowDialog(); + } + + private void Dev_GenerateException_Click(object sender, MouseButtonEventArgs e) + { + throw new ApplicationException("Dummy unhandled exception"); + } + + private void Dev_RemoveSettings_Click(object sender, MouseButtonEventArgs e) + { + var result = MessageBox.Show(this, "Are you sure you wish to reset your options and force the application to exit?", "Unrecoverable action", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel); + if (result != MessageBoxResult.OK) + return; + + Properties.Settings.Default.Reset(); + Properties.Settings.Default.Save(); + Environment.Exit(0); + } + + private void Dev_DummyData_Click(object sender, MouseButtonEventArgs e) + { + ListResults.Clear(); + + var dummyData = new Dino[] { + new Dino { Location=new Position{ Lat=10,Lon=10 }, Type="Testificate", Name="10,10" }, + new Dino { Location=new Position{ Lat=90,Lon=10 }, Type="Testificate", Name="90,10" }, + new Dino { Location=new Position{ Lat=10,Lon=90 }, Type="Testificate", Name="10,90" }, + new Dino { Location=new Position{ Lat=90,Lon=90 }, Type="Testificate", Name="90,90" }, + new Dino { Location=new Position{ Lat=50,Lon=50 }, Type="Testificate", Name="50,50" }, + }; + + var rnd = new Random(); + foreach (var result in dummyData) + { + result.Id = (ulong)rnd.Next(); + DinoViewModel vm = new DinoViewModel(result) { Color = Colors.Green }; + ListResults.Add(vm); + } + + var cv = (CollectionView)CollectionViewSource.GetDefaultView(ListResults); + cv.Refresh(); + } + + private async void SaveSearch_Click(object sender, RoutedEventArgs e) + { + if (String.IsNullOrWhiteSpace(NewSearch.Species)) return; + + try + { + NewSearch.Order = ListSearches.Where(sc => sc.Group == NewSearch.Group).Max(sc => sc.Order) + 100; + } + catch (InvalidOperationException) // no entries for .Max - ignore + { } + + IsLoading = true; + try + { + StatusText = $"Loading {NewSearch.Species}..."; + await arkReaderWild.EnsureSpeciesIsLoaded(NewSearch.Species); + StatusText = $"Ready"; + } + finally + { + IsLoading = false; + } + + ListSearches.Add(NewSearch); + NewSearch = null; + NewSearchActive = false; + CreateSearchAvailable = true; + + MarkSearchesChanged(); + } + + private void CloseNewSearch_Click(object sender, MouseButtonEventArgs e) + { + NewSearchActive = false; + CreateSearchAvailable = true; + } + + private void ShowTames_Click(object sender, MouseButtonEventArgs e) + { + ShowTames = true; + ShowHunt = false; + NewSearchActive = false; + CreateSearchAvailable = false; + + ShowTameSearches(); + } + + private void ShowTheHunt_Click(object sender, MouseButtonEventArgs e) + { + ShowTames = false; + ShowHunt = true; + NewSearchActive = false; + CreateSearchAvailable = true; + + ShowWildSearches(); + } + + private void Settings_Click(object sender, MouseButtonEventArgs e) + { + var settings = new SettingsWindow(); + settings.ShowDialog(); + + OnSettingsChanged(); + } + + private void AdjustableInteger_MouseWheel(object sender, MouseWheelEventArgs e) + { + var tb = (TextBlock)sender; + var diff = Math.Sign(e.Delta) * Properties.Settings.Default.LevelStep; + var bexpr = tb.GetBindingExpression(TextBlock.TextProperty); + var accessor = TypeAccessor.Create(typeof(SearchCriteria)); + var value = (int?)accessor[bexpr.ResolvedSource, bexpr.ResolvedSourcePropertyName]; + if (value.HasValue) + { + value = value + diff; + if (value < 0 || value > Properties.Settings.Default.MaxLevel) value = null; + } + else + { + value = (diff > 0) ? 0 : Properties.Settings.Default.MaxLevel; + } + + accessor[bexpr.ResolvedSource, bexpr.ResolvedSourcePropertyName] = value; + bexpr.UpdateTarget(); + + if (null != searchesList.SelectedItem) + UpdateCurrentSearch(); + + MarkSearchesChanged(); + } + + private void AdjustableGender_MouseWheel(object sender, MouseWheelEventArgs e) + { + var im = (Image)sender; + var diff = Math.Sign(e.Delta); + var nOptions = nullableBoolValues.Count; + var bexpr = im.GetBindingExpression(Image.SourceProperty); + var accessor = TypeAccessor.Create(typeof(SearchCriteria)); + var value = (bool?)accessor[bexpr.ResolvedSource, bexpr.ResolvedSourcePropertyName]; + var index = nullableBoolValues.IndexOf(value); + index = (index + diff + nOptions) % nOptions; + value = nullableBoolValues[index]; + accessor[bexpr.ResolvedSource, bexpr.ResolvedSourcePropertyName] = value; + bexpr.UpdateTarget(); + + if (null != searchesList.SelectedItem) + UpdateCurrentSearch(); + + MarkSearchesChanged(); + } + + private void Result_MouseEnter(object sender, MouseEventArgs e) + { + var fe = sender as FrameworkElement; + if (fe == null) return; + var dino = fe.DataContext as DinoViewModel; + if (dino == null) return; + dino.Highlight = true; + } + + private void Result_MouseLeave(object sender, MouseEventArgs e) + { + var fe = sender as FrameworkElement; + if (fe == null) return; + var dino = fe.DataContext as DinoViewModel; + if (dino == null) return; + dino.ClearValue(DinoViewModel.HighlightProperty); + } + + private void ResultList_Sorting(object sender, DataGridSortingEventArgs e) + { + if (e.Column.SortDirection == null) + e.Column.SortDirection = ListSortDirection.Ascending; + + e.Handled = false; + } + + private void MarkSearchesChanged() + { + SaveSearches(); + } + + private void SaveSearches() + { + Properties.Settings.Default.SavedSearches = JsonConvert.SerializeObject(ListSearches); + Properties.Settings.Default.Save(); + } + + private void LoadSavedSearches() + { + if (!String.IsNullOrWhiteSpace(Properties.Settings.Default.SavedSearches)) + { + Collection searches; + try + { + searches = JsonConvert.DeserializeObject>(Properties.Settings.Default.SavedSearches); + } + catch (Exception e) + { + Console.WriteLine("Exception reading saved searches: " + e.ToString()); + return; + } + + ListSearches.Clear(); + foreach (var search in searches) + ListSearches.Add(search); + } + } + + private async Task UpdateArkToolsData() + { + StatusText = "Updating ark-tools database"; + try + { + await ArkReader.ExecuteArkTools("update-data"); + StatusText = "Updated ark-tools database"; + } + catch (Exception e) + { + StatusText = "Failed to update ark-tools database: " + e.Message; + } + } + + private async Task ReReadArk(bool force = false) + { + if (IsLoading) return; + + await PerformConversion(force); + await LoadSearchSpecies(); + + var currentSearch = searchesList.SelectedItems.Cast().ToList(); + UpdateSearchResults(currentSearch); + } + + private async Task LoadSearchSpecies() + { + IsLoading = true; + try + { + var species = arkReaderWild.AllSpecies.Distinct(); + foreach (var speciesName in species) + { + StatusText = $"Loading {speciesName}..."; + await arkReaderWild.EnsureSpeciesIsLoaded(speciesName); + } + + species = arkReaderTamed.AllSpecies.Distinct(); + foreach (var speciesName in species) + { + StatusText = $"Loading {speciesName}..."; + await arkReaderTamed.EnsureSpeciesIsLoaded(speciesName); + } + + StatusText = "Ready"; + } + finally + { + IsLoading = false; + } + } + + private void UpdateSearchResults(IList searches) + { + if (searches == null || searches.Count == 0) + { + ListResults.Clear(); + } + else + { + // Find dinos that match the given searches + var found = new List(); + var reader = ShowTames ? arkReaderTamed : arkReaderWild; + foreach (var search in searches) + { + if (String.IsNullOrWhiteSpace(search.Species)) + { + foreach (var speciesDinos in reader.FoundDinos.Values) + found.AddRange(speciesDinos); + } + else + { + if (reader.FoundDinos.ContainsKey(search.Species)) + { + var dinoList = reader.FoundDinos[search.Species]; + found.AddRange(dinoList.Where(d => search.Matches(d))); + } + } + } + + ListResults.Clear(); + foreach (var result in found) + ListResults.Add(result); + } + + ((CollectionViewSource)Resources["OrderedResults"]).View.Refresh(); + } + + private async Task PerformConversion(bool force) + { + string arkDirName = Path.GetFileNameWithoutExtension(Properties.Settings.Default.SaveFile); + + IsLoading = true; + try + { + StatusDetailText = "...converting"; + StatusText = "Processing saved ARK : Wild"; + await arkReaderWild.PerformConversion(force, arkDirName); + StatusText = "Processing saved ARK : Tamed"; + await arkReaderTamed.PerformConversion(force, arkDirName); + StatusText = "ARK processing completed"; + StatusDetailText = $"{arkReaderWild.NumberOfSpecies} wild and {arkReaderTamed.NumberOfSpecies} tame species located"; + + // Write path to last ark into the output folder so we can check when we change ARKs + File.WriteAllText(Path.Combine(Properties.Settings.Default.OutputDir, Properties.Resources.LastArkFile), Properties.Settings.Default.SaveFile); + } + catch (Exception ex) + { + StatusText = "ARK processing failed"; + StatusDetailText = ""; + MessageBox.Show(ex.Message, "ARK Tools Error", MessageBoxButton.OK, MessageBoxImage.Exclamation); + } + finally + { + IsLoading = false; + } + } + + private void UpdateCurrentSearch() + { + var search = (SearchCriteria)searchesList.SelectedItem; + var searches = new List(); + if (search != null) searches.Add(search); + UpdateSearchResults(searches); + } + + void IDropTarget.DragOver(IDropInfo dropInfo) + { + if (dropInfo.TargetItem is SearchCriteria targetItem && dropInfo.Data is SearchCriteria sourceItem) + { + dropInfo.DropTargetAdorner = DropTargetAdorners.Insert; + dropInfo.Effects = DragDropEffects.Move; + } + } + + void IDropTarget.Drop(IDropInfo dropInfo) + { + var sourceItem = (SearchCriteria)dropInfo.Data; + var targetItem = (SearchCriteria)dropInfo.TargetItem; + + var ii = dropInfo.InsertIndex; + var ip = dropInfo.InsertPosition; + + // Change source item's group + sourceItem.Group = targetItem.Group; + + // Try to figure out the other item to insert between, or pick a boundary + var options = ListSearches + .Where(sc => sc != sourceItem) + .Where(sc => sc.Group == targetItem.Group) + .OrderBy(sc => sc.Order) + .ToArray(); + + var above = options.Where(sc => sc.Order < targetItem.Order).OrderByDescending(sc => sc.Order).FirstOrDefault(); + var below = options.Where(sc => sc.Order > targetItem.Order).OrderBy(sc => sc.Order).FirstOrDefault(); + + var aboveOrder = (above == null) ? options.Min(sc => sc.Order) - 1 : above.Order; + var belowOrder = (below == null) ? options.Max(sc => sc.Order) + 1 : below.Order; + + // Update the order to be mid-way between either above or below, based on drag insert position + sourceItem.Order = (targetItem.Order + (ip.HasFlag(RelativeInsertPosition.AfterTargetItem) ? belowOrder : aboveOrder)) / 2; + + // Renumber the results + var orderedSearches = ListSearches + .OrderBy(sc => sc.Group) + .ThenBy(sc => sc.Order) + .ToArray(); + + for (var i = 0; i < orderedSearches.Length; i++) + { + orderedSearches[i].Order = i; + } + + // Force binding update + CollectionViewSource.GetDefaultView(searchesList.ItemsSource).Refresh(); + + // Save list + MarkSearchesChanged(); + } + + private void ShowTameSearches() + { + SetupTamedSearches(); + } + + private void SetupTamedSearches() + { + var wildcard = new string[] { null }; + var speciesList = wildcard.Concat(arkReaderTamed.AllSpecies).ToList(); + var orderList = Enumerable.Range(0, speciesList.Count); + var searches = speciesList.Zip(orderList, (species, order) => new SearchCriteria { Species = species, Order = order }); + + ListSearches.Clear(); + foreach (var search in searches) + ListSearches.Add(search); + } + + private void ShowWildSearches() + { + LoadSavedSearches(); + } + + private void OnSettingsChanged() + { + DiscoverCalibration(); + EnsureOutputDirectory(); + CheckIfArkChanged(); + UpdateCurrentSearch(); + + ForceFontSizeUpdate(); + + reloadTimer.Interval = TimeSpan.FromMilliseconds(Properties.Settings.Default.ConvertDelay); + } + + private void ForceFontSizeUpdate() + { + Dispatcher.Invoke(() => RefreshDataGridColumnWidths("GroupedSearchCriteria", searchesList), DispatcherPriority.ContextIdle); + Dispatcher.Invoke(() => RefreshDataGridColumnWidths("OrderedResults", resultsList), DispatcherPriority.ContextIdle); + } + + private void RefreshDataGridColumnWidths(string resourceName, DataGrid dataGrid) + { + var widths = dataGrid.Columns.Select(col => col.Width).ToArray(); + foreach (var col in dataGrid.Columns) + col.Width = 0; + + ((CollectionViewSource)Resources[resourceName]).View.Refresh(); + dataGrid.UpdateLayout(); + + foreach (var o in dataGrid.Columns.Zip(widths, (col, width) => new { col, width })) + o.col.Width = o.width; + } + } } diff --git a/LarkatorGUI/Properties/Settings.Designer.cs b/LarkatorGUI/Properties/Settings.Designer.cs index fb3d4d3..fc8c1f8 100644 --- a/LarkatorGUI/Properties/Settings.Designer.cs +++ b/LarkatorGUI/Properties/Settings.Designer.cs @@ -143,7 +143,7 @@ public double MainWindowTop { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("1352")] + [global::System.Configuration.DefaultSettingValueAttribute("1252")] public double MainWindowWidth { get { return ((double)(this["MainWindowWidth"])); @@ -155,7 +155,7 @@ public double MainWindowWidth { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("800")] + [global::System.Configuration.DefaultSettingValueAttribute("700")] public double MainWindowHeight { get { return ((double)(this["MainWindowHeight"])); diff --git a/LarkatorGUI/Properties/Settings.settings b/LarkatorGUI/Properties/Settings.settings index 7b2bcc5..0607085 100644 --- a/LarkatorGUI/Properties/Settings.settings +++ b/LarkatorGUI/Properties/Settings.settings @@ -27,10 +27,10 @@ -10000 - 1352 + 1252 - 800 + 700 2000 diff --git a/LarkatorGUI/Welcome.xaml.cs b/LarkatorGUI/Welcome.xaml.cs index 321d183..7a84108 100644 --- a/LarkatorGUI/Welcome.xaml.cs +++ b/LarkatorGUI/Welcome.xaml.cs @@ -1,21 +1,10 @@ using Microsoft.Win32; using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Configuration; using System.Diagnostics; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Shapes; namespace LarkatorGUI { @@ -67,6 +56,14 @@ private void UpdateValidation() LetsGoButton.IsEnabled = toolsGood && saveGood; } + private string CheckFile(string fullpath) + { + if (String.IsNullOrWhiteSpace(fullpath) || !File.Exists(fullpath)) + return Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); + else + return Path.GetDirectoryName(fullpath); + } + private void BrowseArkTools_Click(object sender, RoutedEventArgs e) { var dialog = new OpenFileDialog() @@ -74,15 +71,16 @@ private void BrowseArkTools_Click(object sender, RoutedEventArgs e) AddExtension = true, CheckFileExists = true, CheckPathExists = true, + DereferenceLinks = false, + Multiselect = false, DefaultExt = "ark-tools.exe", - DereferenceLinks = true, Filter = "ARK Tools Executable|ark-tools.exe", - Multiselect = false, Title = "Locate ark-tools.exe...", FileName = ArkToolsPath, + InitialDirectory = CheckFile(ArkToolsPath), }; - var result = dialog.ShowDialog(); + var result = dialog.ShowDialog(this); if (result == true) { ArkToolsPath = dialog.FileName; @@ -96,15 +94,16 @@ private void BrowseSaveFile_Click(object sender, RoutedEventArgs e) AddExtension = true, CheckFileExists = true, CheckPathExists = true, + DereferenceLinks = false, + Multiselect = false, DefaultExt = ".ark", - DereferenceLinks = true, Filter = "ARK Save File|*.ark", - Multiselect = false, Title = "Locate saved ARK...", FileName = SaveFilePath, + InitialDirectory = CheckFile(SaveFilePath), }; - var result = dialog.ShowDialog(); + var result = dialog.ShowDialog(this); if (result == true) { SaveFilePath = dialog.FileName; @@ -113,8 +112,6 @@ private void BrowseSaveFile_Click(object sender, RoutedEventArgs e) private void LetsGoButton_Click(object sender, RoutedEventArgs e) { - Console.WriteLine("Let's go!"); - Properties.Settings.Default.ArkTools = ArkToolsPath; Properties.Settings.Default.SaveFile = SaveFilePath; diff --git a/LarkatorGUI/app.config b/LarkatorGUI/app.config index 3f29b91..7f30ec8 100644 --- a/LarkatorGUI/app.config +++ b/LarkatorGUI/app.config @@ -32,10 +32,10 @@ -10000 - 1352 + 1252 - 800 + 700 2000