From 3ce5ecac25cf22d8d1f3a1a756c3cf2d11d06b4f Mon Sep 17 00:00:00 2001 From: diluculo Date: Thu, 25 Aug 2016 15:56:09 +0900 Subject: [PATCH] Add save prompt and modify CodeEditor and TextEditor --- .../TextEditor/ViewModels/EditorViewModel.cs | 16 ++++ .../Modules/TextEditor/Views/EditorView.xaml | 2 +- .../ViewModels/CodeEditorViewModel.cs | 59 ++++++++++---- src/Gemini/Framework/PersistedDocument.cs | 80 ++++++++++++++++++- 4 files changed, 138 insertions(+), 19 deletions(-) diff --git a/src/Gemini.Demo/Modules/TextEditor/ViewModels/EditorViewModel.cs b/src/Gemini.Demo/Modules/TextEditor/ViewModels/EditorViewModel.cs index 788f25dc..2af2e990 100644 --- a/src/Gemini.Demo/Modules/TextEditor/ViewModels/EditorViewModel.cs +++ b/src/Gemini.Demo/Modules/TextEditor/ViewModels/EditorViewModel.cs @@ -4,15 +4,19 @@ using Gemini.Demo.Modules.TextEditor.Views; using Gemini.Framework; using Gemini.Framework.Threading; +using System.ComponentModel.Composition; namespace Gemini.Demo.Modules.TextEditor.ViewModels { + [Export(typeof(EditorViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] #pragma warning disable 659 public class EditorViewModel : PersistedDocument #pragma warning restore 659 { private EditorView _view; private string _originalText; + private bool notYetLoaded = false; protected override Task DoNew() { @@ -38,6 +42,12 @@ protected override Task DoSave(string filePath) private void ApplyOriginalText() { + // At StartUp, _view is null, so notYetLoaded flag is added + if (_view == null) + { + notYetLoaded = true; + return; + } _view.textBox.Text = _originalText; _view.textBox.TextChanged += delegate @@ -49,6 +59,12 @@ private void ApplyOriginalText() protected override void OnViewLoaded(object view) { _view = (EditorView) view; + + if (notYetLoaded) + { + ApplyOriginalText(); + notYetLoaded = false; + } } public override bool Equals(object obj) diff --git a/src/Gemini.Demo/Modules/TextEditor/Views/EditorView.xaml b/src/Gemini.Demo/Modules/TextEditor/Views/EditorView.xaml index 334bf618..c911ed21 100644 --- a/src/Gemini.Demo/Modules/TextEditor/Views/EditorView.xaml +++ b/src/Gemini.Demo/Modules/TextEditor/Views/EditorView.xaml @@ -11,5 +11,5 @@ FontFamily="Consolas" FontSize="14" ScrollViewer.VerticalScrollBarVisibility="Visible" - ScrollViewer.HorizontalScrollBarVisibility="Visible"/> + ScrollViewer.HorizontalScrollBarVisibility="Visible" TextWrapping="Wrap" AcceptsReturn="True"/> diff --git a/src/Gemini.Modules.CodeEditor/ViewModels/CodeEditorViewModel.cs b/src/Gemini.Modules.CodeEditor/ViewModels/CodeEditorViewModel.cs index ef4e9270..3748aaae 100644 --- a/src/Gemini.Modules.CodeEditor/ViewModels/CodeEditorViewModel.cs +++ b/src/Gemini.Modules.CodeEditor/ViewModels/CodeEditorViewModel.cs @@ -5,6 +5,9 @@ using Gemini.Framework; using Gemini.Framework.Threading; using Gemini.Modules.CodeEditor.Views; +using Gemini.Modules.StatusBar; +using System.ComponentModel; +using Caliburn.Micro; namespace Gemini.Modules.CodeEditor.ViewModels { @@ -17,6 +20,8 @@ public class CodeEditorViewModel : PersistedDocument private readonly LanguageDefinitionManager _languageDefinitionManager; private string _originalText; private ICodeEditorView _view; + private IStatusBar _statusBar; + private bool notYetLoaded = false; [ImportingConstructor] public CodeEditorViewModel(LanguageDefinitionManager languageDefinitionManager) @@ -24,24 +29,16 @@ public CodeEditorViewModel(LanguageDefinitionManager languageDefinitionManager) _languageDefinitionManager = languageDefinitionManager; } - public override bool ShouldReopenOnStart - { - get { return true; } - } - - public override void SaveState(BinaryWriter writer) - { - writer.Write(FilePath); - } - - public override void LoadState(BinaryReader reader) - { - Load(reader.ReadString()); - } - protected override void OnViewLoaded(object view) { _view = (ICodeEditorView) view; + _statusBar = IoC.Get(); + + if (notYetLoaded) + { + ApplyOriginalText(); + notYetLoaded = false; + } } public override bool Equals(object obj) @@ -76,6 +73,12 @@ protected override Task DoSave(string filePath) private void ApplyOriginalText() { + // At StartUp, _view is null, so notYetLoaded flag is added + if (_view == null) + { + notYetLoaded = true; + return; + } _view.TextEditor.Text = _originalText; _view.TextEditor.TextChanged += delegate @@ -83,6 +86,14 @@ private void ApplyOriginalText() IsDirty = string.Compare(_originalText, _view.TextEditor.Text) != 0; }; + UpdateStatusBar(); + + // To update status bar items, Caret PositionChanged event is added + _view.TextEditor.TextArea.Caret.PositionChanged += delegate + { + UpdateStatusBar(); + }; + var fileExtension = Path.GetExtension(FileName).ToLower(); ILanguageDefinition languageDefinition = _languageDefinitionManager.GetDefinitionByExtension(fileExtension); @@ -90,6 +101,24 @@ private void ApplyOriginalText() SetLanguage(languageDefinition); } + /// + /// Update Column and Line position properties when caret position is changed + /// + private void UpdateStatusBar() + { + int lineNumber = _view.TextEditor.Document.GetLineByOffset(_view.TextEditor.CaretOffset).LineNumber; + int colPosition = _view.TextEditor.TextArea.Caret.VisualColumn + 1; + + // TODO: Now I don't know about Ch# + //int charPosition = _view.TextEditor.CaretOffset; + + if (_statusBar != null && _statusBar.Items.Count >= 3) + { + _statusBar.Items[1].Message = string.Format("Ln {0}", lineNumber); + _statusBar.Items[2].Message = string.Format("Col {0}", colPosition); + } + } + private void SetLanguage(ILanguageDefinition languageDefinition) { _view.TextEditor.SyntaxHighlighting = (languageDefinition != null) diff --git a/src/Gemini/Framework/PersistedDocument.cs b/src/Gemini/Framework/PersistedDocument.cs index 0c5191a2..088448e6 100644 --- a/src/Gemini/Framework/PersistedDocument.cs +++ b/src/Gemini/Framework/PersistedDocument.cs @@ -1,5 +1,11 @@ +using Caliburn.Micro; +using Gemini.Framework.Services; +using Gemini.Properties; +using Microsoft.Win32; using System.IO; +using System.Linq; using System.Threading.Tasks; +using System.Windows; namespace Gemini.Framework { @@ -25,10 +31,78 @@ public bool IsDirty } } - public override void CanClose(System.Action callback) + // ShouldReopenOnStart, SaveState and LoadState are default methods of PersistedDocument. + public override bool ShouldReopenOnStart { - // TODO: Show save prompt. - callback(!IsDirty); + get { return (FilePath != null); } // if FilePath is null, SaveState() will generate an NullExceptionError + } + + public override void SaveState(BinaryWriter writer) + { + writer.Write(FilePath); + } + + public override async void LoadState(BinaryReader reader) + { + await Load(reader.ReadString()); + } + + public override async void CanClose(System.Action callback) + { + if (IsDirty) + { + // Show save prompt. + // Note that CanClose method of Demo ShellViewModel blocks this. + string title = IoC.Get().Title; + string fileName = Path.GetFileNameWithoutExtension(FileName); + string fileExtension = Path.GetExtension(FileName); + var fileType = IoC.GetAll() + .SelectMany(x => x.FileTypes) + .SingleOrDefault(x => x.FileExtension == fileExtension); + + string message = string.Format(Resources.SaveChangesBeforeClosingMessage, fileType.Name, fileName); + var result = MessageBox.Show(message, title, MessageBoxButton.YesNoCancel, MessageBoxImage.Warning); + + if (result == MessageBoxResult.Yes) + { + if (IsNew) + { + // Ask new file path. + var filter = string.Empty; + if (fileType != null) + filter = fileType.Name + "|*" + fileType.FileExtension + "|"; + filter += Properties.Resources.AllFiles + "|*.*"; + + var dialog = new SaveFileDialog() { FileName = this.FileName, Filter = filter }; + if (dialog.ShowDialog() == true) + { + // Save file. + await Save(dialog.FileName); + + // Add to recent files. Temporally, commented out. + //IShell _shell = IoC.Get(); + //_shell.RecentFiles.Update(dialog.FileName); + } + else + { + callback(false); + return; + } + } + else + { + // Save file. + await Save(FilePath); + } + } + else if (result == MessageBoxResult.Cancel) + { + callback(false); + return; + } + } + + callback(true); } private void UpdateDisplayName()