private void MainWindow_OnClosing(object sender, CancelEventArgs e)
        {
            if (ShouldPromptSaving)
            {
                var result = MessageBox.Show(Application.Current.FindResource <string>(App.ResourceKeys.ProjectChangedPrompt), App.Title, MessageBoxButton.YesNoCancel, MessageBoxImage.Exclamation);
                switch (result)
                {
                case MessageBoxResult.Yes:
                    if (CmdFileSaveProject.CanExecute(null))
                    {
                        CmdFileSaveProject.Execute(null);
                    }
                    if (!Project.IsSaved)
                    {
                        e.Cancel = true;
                        return;
                    }
                    break;

                case MessageBoxResult.No:
                    break;

                case MessageBoxResult.Cancel:
                    e.Cancel = true;
                    return;

                default:
                    throw new ArgumentOutOfRangeException(nameof(result), result, null);
                }
            }
            _autoSaveTimer.Stop();
            ClearBackup();
            if (ScorePreviewer.IsPreviewing)
            {
                ScorePreviewer.EndPreview();
            }

            _temporaryMessageTimer.Elapsed -= TemporaryMessageTimer_OnElapsed;
            _temporaryMessageTimer?.Dispose();
            _temporaryMessageTimer  = null;
            _autoSaveTimer.Elapsed -= AutoSaveTimer_OnElapsed;
            _autoSaveTimer?.Dispose();
            _autoSaveTimer = null;
        }
        private void CmdPreviewToggle_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            ScorePreviewer.IsPreviewing = !ScorePreviewer.IsPreviewing;
            if (ScorePreviewer.IsPreviewing)
            {
                var fps       = PreviewFps;
                var startTime = 0;
                var score     = Project.Scores[Project.Difficulty];

                // find start time, accurate even if BPM is variant
                if (!PreviewFromStart && ScrollViewer.ExtentHeight > 0)
                {
                    /*
                     * First, we find the percentage of scroll
                     * Since bar margin is constant, we can compute targetGrid = total #grids * percentage, which is the grid we should start at
                     * Then, we find where this grid is and set startTime to be its time
                     */
                    var perc    = (ScrollViewer.ExtentHeight - ScrollViewer.VerticalOffset - ScrollViewer.ViewportHeight) / ScrollViewer.ExtentHeight;
                    var lastBar = Editor.ScoreBars.LastOrDefault();

                    var totalGrids = 0;
                    foreach (var bar in score.Bars)
                    {
                        totalGrids += bar.TotalGridCount;
                    }

                    if (perc > 0 && lastBar != null)
                    {
                        var projectOffset = Project.Settings.StartTimeOffset;
                        var targetGrid    = totalGrids * perc;
                        var bar           = score.Bars[0];
                        var gridSum       = 0;
                        for (int i = 1; i < score.Bars.Count; ++i)
                        {
                            gridSum += score.Bars[i].TotalGridCount;
                            if (gridSum > targetGrid)
                            {
                                gridSum -= score.Bars[i].TotalGridCount;
                                bar      = score.Bars[i - 1];
                                break;
                            }
                        }

                        for (int i = 1; i < bar.TotalGridCount; ++i)
                        {
                            ++gridSum;
                            if (gridSum > targetGrid)
                            {
                                startTime = (int)(1000 * (bar.TimeAtGrid(i - 1) - projectOffset) + 1000 * projectOffset);
                                break;
                            }
                        }
                    }
                }

                startTime += (int)(PreviewStartOffset * 1000);
                if (startTime < 0)
                {
                    startTime = 0;
                }

                double approachTime = ArTable[PreviewSpeed];
                ScorePreviewer.BeginPreview(score, fps, startTime, approachTime);
            }
            else
            {
                ScorePreviewer.EndPreview();
            }
        }