예제 #1
0
        public Split(SplitTimeSpan splitTimeSpan, Run owner)
        {
            if (owner == null) throw new ArgumentNullException();

            Owner = owner;
            SplitInfo = splitTimeSpan;
        }
예제 #2
0
 public Split(TimeSpan splitTime, Run owner, bool isPreciseSplit)
     : this(new SplitTimeSpan(splitTime, isPreciseSplit), owner)
 {
 }
예제 #3
0
        public void SaveCurrentFile()
        {
            if (!CurrentFile.IsPathSet)
            {
                SaveCurrentFileAs();
                return;
            }

            try
            {
                CurrentFile.MergeAndSave(CurrentRun);
                CurrentRun = new Run(CurrentFile.RunDefinition.Length);
            }
            catch (Exception)
            {
                MessageBox.Show(MainWindow, "Error saving file. Perhaps you don't have access to that folder.", "Error Saving", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
예제 #4
0
 public Split(TimeSpan splitTime, Run owner)
     : this(new SplitTimeSpan(splitTime, true), owner)
 {
 }
예제 #5
0
 private void generateRunsFromDefinition()
 {
     PersonalBest = new Run(RunDefinition.Select(si => si.PersonalBestSplit).ToArray());
     SumOfBest = new Run(RunDefinition.Select(si => si.SumOfBestSplit).ToArray());
 }
예제 #6
0
        //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;
            });
        }
예제 #7
0
 public bool IsPersonalBest(Run latestRun)
 {
     //Check if this run was better than personal best
     return latestRun.CompletedRunTime < PersonalBest.CompletedRunTime;
 }
예제 #8
0
 public bool ContainsGoldSplit(Run latestRun)
 {
     //Check if any splits from the current run beat any gold split
     return latestRun.Splits.Zip(SumOfBest.Splits, (lr, sb) => new { Latest = lr, Best = sb })
         .Any(a => Split.IsGoldSplit(a.Latest, a.Best));
 }
예제 #9
0
 public bool CheckMergeSuggested(Run latestRun)
 {
     return ContainsGoldSplit(latestRun) || IsPersonalBest(latestRun);
 }
예제 #10
0
        public void MergeAndSave(Run latestRun)
        {
            var zippedSplits = latestRun.Splits.Zip(PersonalBest.Splits, (lr, pb) => new { Lr = lr, Pb = pb }).Zip(SumOfBest.Splits,
                (a, sb) => new { Lr = a.Lr, Pb = a.Pb, Sb = sb }).Zip(RunDefinition, (a, d) =>
                new { Latest = a.Lr, PersonalBest = a.Pb, SumOfBest = a.Sb, Definition = d }).ToArray();

            var isPersonalBest = IsPersonalBest(latestRun);
            var isGoldSplit = ContainsGoldSplit(latestRun);
            if (isPersonalBest || isGoldSplit)
            {
                if (isPersonalBest) PersonalBestDate = latestRun.StartTime;

                RunDefinition = zippedSplits.Select(a =>
                {
                    return new SplitInfo()
                    {
                        Name = a.Definition.Name,
                        PersonalBestSplit = isPersonalBest ? a.Latest.SplitInfo : a.Definition.PersonalBestSplit,
                        SumOfBestSplit = a.Latest != null && a.Latest.IsWellBounded && (a.Latest.Time < a.Definition.SumOfBestSplit.Time) ?
                            a.Latest.SplitInfo : a.Definition.SumOfBestSplit
                    };
                }).ToArray();

                generateRunsFromDefinition();
            };

            Save();
        }
예제 #11
0
 public void Start(Run run)
 {
     if (subscription != null) subscription.Dispose();
     clearOverrideTimer();
     subscription = frequencyObservable.Subscribe(_ => setTimer(run.TimeSinceStart));
 }