public SplitRowEdit(SplitManagementViewModel collectionClass) { Name = "Unnamed Split"; PersonalBestTimeAtSplit = TimeSpan.MaxValue; GoldSplitLength = TimeSpan.MaxValue; IsPbTimeUnknown = true; IsGoldTimeUnknown = true; Divide = Command.Create(() => true, () => collectionClass.Divide(this)); Delete = Command.Create(() => true, () => collectionClass.Delete(this)); }
//private KeyboardListener keyListener = new KeyboardListener(); public MainViewModel() { MainWindow = (View.MainWindow)Application.Current.MainWindow; if (Settings.Default.IsNewVersion) { Settings.Default.Upgrade(); var targetConfigPath = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath; //Attempt to upgrade display templates if (!string.IsNullOrWhiteSpace(Settings.Default.ConfigPath)) { //Forcefully upgrade old DisplayTemplates by copying the old settings file xml node over //I know that the Application Settings can be upgraded using the Upgrade method but I //can't seem to find how to do the same with configuration sections var previousVersion = XDocument.Load(Settings.Default.ConfigPath); var newVersion = XDocument.Load(targetConfigPath); var previousDisplayTemplates = previousVersion.Descendants("displayTemplates").FirstOrDefault(); if (previousDisplayTemplates != null) { newVersion.Descendants("configuration").First().AddFirst(new XElement(previousDisplayTemplates)); newVersion.Save(targetConfigPath); } } Settings.Default.ConfigPath = targetConfigPath; Settings.Default.IsNewVersion = false; Settings.Default.Save(); } LiveTimer = new Timer(30); SettingsViewModel = new SettingsViewModel(this); DisplaySettingsViewModel = new DisplayTemplatesViewModel(this); SplitManagementViewModel = new SplitManagementViewModel(); ResizeEnabled = false; ShowGoldSplits = true; MainWindow.Loaded += (sender, e) => { //HotKeyManager must be initialized after the window has loaded HotKeyManager = new HotKeyManager(MainWindow); HotKeyManager.KeyBoardHook(); var keyPressedObs = Observable.FromEventPattern<KeyboardHookEventHandler, KeyboardHookEventArgs>( h => HotKeyManager.KeyBoardKeyEvent += h, h => HotKeyManager.KeyBoardKeyEvent -= h) .Where(_ => !SettingsWindowOpen && CurrentFile != null && CurrentRun != null) .Publish().RefCount(); TimeSpan cooldown = TimeSpan.FromMilliseconds(Settings.Default.HotkeyCooldownTime); keyPressedObs.Where(ep => ep.EventArgs.Key == Settings.Default.SplitKey).Cooldown(cooldown).SubscribeSafeLog(_ => CurrentRun.Split()); keyPressedObs.Where(ep => ep.EventArgs.Key == Settings.Default.UnsplitKey).Cooldown(cooldown).SubscribeSafeLog(_ => CurrentRun.Unsplit()); keyPressedObs.Where(ep => ep.EventArgs.Key == Settings.Default.SkipKey).Cooldown(cooldown).SubscribeSafeLog(_ => CurrentRun.SkipSplit()); keyPressedObs.Where(ep => ep.EventArgs.Key == Settings.Default.PauseKey).Cooldown(cooldown).SubscribeSafeLog(_ => CurrentRun.Pause()); keyPressedObs.Where(ep => ep.EventArgs.Key == Settings.Default.ResetKey).Cooldown(cooldown).SubscribeSafeLog(_ => { //This call to CheckMergeSuggested is done like this in order to free up the key pressed event immediately. //Blocking the event with a popup causes application responsiveness issues. Observable.Return(System.Reactive.Unit.Default).ObserveOnDispatcher().SubscribeSafeLog(_2 => { CheckMergeSuggested(); CurrentRun = new Run(CurrentFile.RunDefinition.Length); }); }); }; CreateNewFileCommand = Command.Create(() => true, CreateNewFile); OpenFileCommand = Command.Create(() => true, OpenFile); ImportFromWsplitCommand = Command.Create(() => true, ImportFromWsplit); SaveSplits = Command.Create(() => true, SaveCurrentFile); SaveSplitsAs = Command.Create(() => true, SaveCurrentFileAs); AcceptResizeCommand = Command.Create(() => true, AcceptResize); CancelResizeCommand = Command.Create(() => true, CancelResize); //Set up observable which monitors changes in the current run var runChangedObs = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>( h => PropertyChanged += h, h => PropertyChanged -= h).Where(a => a.EventArgs.PropertyName == "CurrentRun") .Publish().RefCount(); //Set up observable which monitors changes in the current file var fileChangedObs = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>( h => PropertyChanged += h, h => PropertyChanged -= h).Where(a => a.EventArgs.PropertyName == "CurrentFile") .Publish().RefCount(); //Set up observable which monitors changes in the display template var displayTemplateObs = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>( h => DisplaySettingsViewModel.PropertyChanged += h, h => DisplaySettingsViewModel.PropertyChanged -= h) .Where(a => a.EventArgs.PropertyName == "SelectedDisplayTemplate").Publish().RefCount(); //Set up an observable of an observable which will be used to monitor changes in run status var runStatusObs = runChangedObs.Select(_ => { if (CurrentRun == null) return Observable.Never<Unit>(); var startObs = Observable.FromEventPattern(h => CurrentRun.RunStatusChanged += h, h => CurrentRun.RunStatusChanged -= h).Select(_2 => Unit.Default); //Propagate the start and complete events but also fire an event immediately which will be used //to reset the timer as soon as the run changes return startObs.StartWith(Unit.Default); }); //Set up an observable of an observable which will be used to monitor changes in splits var splitObs = runChangedObs.Select(_ => { if (CurrentRun == null) return Observable.Never<EventPattern<SplitChange>>(); var splitChangedObs = Observable.FromEventPattern<SplitChange>(h => CurrentRun.SplitChanged += h, h => CurrentRun.SplitChanged -= h); return splitChangedObs.StartWith(new EventPattern<SplitChange>(this, new SplitChange(SplitChange.ActionEnum.Reset, null, -1))); }); //Set up an observable of an observable which will be used to monitor changes in display template var fileDisplayTemplateObs = fileChangedObs.Select(_ => { if (CurrentFile == null) return Observable.Never<Unit>(); var templateChangedObs = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>( h => CurrentFile.PropertyChanged += h, h => CurrentFile.PropertyChanged -= h).Where(a => a.EventArgs.PropertyName == "DisplayTemplate"); return templateChangedObs.Select(_2 => Unit.Default).StartWith(Unit.Default); }); //Set up an observable of an observable which will be used to monitor changes in window size var windowSizeObs = displayTemplateObs.Select(_ => Unit.Default).StartWith(Unit.Default).Select(_ => { if (DisplaySettingsViewModel == null || DisplaySettingsViewModel.SelectedDisplayTemplate == null) return Observable.Never<Unit>(); var selectedDisplay = DisplaySettingsViewModel.SelectedDisplayTemplate; var sizeChangedObs = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>( h => selectedDisplay.PropertyChanged += h, h => selectedDisplay.PropertyChanged -= h) .Where(a => a.EventArgs.PropertyName == "WindowWidth" || a.EventArgs.PropertyName == "WindowHeight"); return sizeChangedObs.Select(_2 => Unit.Default).StartWith(Unit.Default); }); //Monitor when run changes and changes state in order to control live timer runStatusObs.Switch().SubscribeSafeLog(_ => { if (CurrentRun == null || !CurrentRun.IsStarted) { IsCurrentRunStarted = false; LiveTimer.Clear(); } else if (CurrentRun.IsCompleted) { LiveTimer.Stop(CurrentRun.CompletedRunTime); } else if (CurrentRun.IsStarted) { if (CurrentRun.CurrentSplit == 0) { IsCurrentRunStarted = true; CurrentSplitRow = SplitRows[0]; } LiveTimer.Start(CurrentRun); } }); //Monitor when run changes and a split is detected. This is used to update the split view splitObs.Switch().SubscribeSafeLog(args => { switch(args.EventArgs.Action) { case SplitChange.ActionEnum.Removed: SplitRows[args.EventArgs.Index].CurrentRunSplit = null; PreviousSplitRow = args.EventArgs.Index > 0 ? SplitRows[args.EventArgs.Index - 1] : null; CurrentSplitRow = SplitRows[args.EventArgs.Index]; break; case SplitChange.ActionEnum.Added: SplitRows[args.EventArgs.Index].CurrentRunSplit = args.EventArgs.Item; PreviousSplitRow = SplitRows[args.EventArgs.Index]; CurrentSplitRow = SplitRows.Length > args.EventArgs.Index + 1 ? SplitRows[args.EventArgs.Index + 1] : null; break; case SplitChange.ActionEnum.Reset: var pbSplits = CurrentFile.PersonalBest.Splits; var goldSplits = CurrentFile.SumOfBest.Splits; var splitInfo = CurrentFile.RunDefinition; var merged = pbSplits.Zip(splitInfo, (pbs, si) => new { PbSplit = pbs, Info = si }).Zip(goldSplits, (a, gs) => new { PbSplit = a.PbSplit, GoldSplit = gs, Info = a.Info }); SplitRows = merged.Select(a => new SplitRowDisplay(a.Info.Name, a.PbSplit, a.GoldSplit)).ToArray(); PreviousSplitRow = null; CurrentSplitRow = null; break; } }); //Change current run when a new file is loaded fileChangedObs.SubscribeSafeLog(_ => { CurrentRun = new Run(CurrentFile.RunDefinition.Length); }); //Force window to resize correctly when a new template is loaded windowSizeObs.Switch().Throttle(TimeSpan.FromMilliseconds(50)).ObserveOnDispatcher().SubscribeSafeLog(_ => { if (DisplaySettingsViewModel == null || DisplaySettingsViewModel.SelectedDisplayTemplate == null) return; MainWindow.ForceChangeWindowSize(DisplaySettingsViewModel.SelectedDisplayTemplate.WindowHeight, DisplaySettingsViewModel.SelectedDisplayTemplate.WindowWidth); }); //Keep DisplayTemplateViewModel synchronized with these changes. fileDisplayTemplateObs.Switch().SubscribeSafeLog(_ => { if (CurrentFile == null || CurrentFile.DisplayTemplate == null) return; DisplaySettingsViewModel.SelectedDisplayTemplate = CurrentFile.DisplayTemplate; }); }