From 29a118172ea97fea9d630954cf691a981fbd3da6 Mon Sep 17 00:00:00 2001 From: VladimirKhil <vladimir.khil@gmail.com> Date: Sun, 29 Oct 2023 22:12:44 +0100 Subject: [PATCH] https://github.com/VladimirKhil/SI/issues/18 Make SIQuester.ViewModel cross-platform; enable new format for new packages; add more localization --- .../Contracts/Host/IClipboardService.cs | 6 ++ .../Contracts/IDocumentViewModelFactory.cs | 16 ++++ .../SIQuester.ViewModel/ModelViewBase.cs | 16 ---- .../PlatformSpecific/PlatformManager.cs | 5 ++ .../SIQuester.ViewModel.csproj | 3 +- .../ServiceCollectionExtensions.cs | 26 ++++++ .../Services/DocumentViewModelFactory.cs | 29 ++++++ .../Services/PackageTemplatesRepository.cs | 2 +- .../Dialogs/SelectThemesViewModel.cs | 14 +-- .../Workspaces/ImportDBStorageViewModel.cs | 21 +++-- .../Workspaces/ImportSIStorageViewModel.cs | 19 ++-- .../Workspaces/ImportTextViewModel.cs | 17 ++-- .../Workspaces/MainViewModel.cs | 90 ++++++++----------- .../Workspaces/NewViewModel.cs | 8 +- .../Workspaces/QDocument.cs | 37 ++++++-- .../Workspaces/SettingsViewModel.cs | 3 +- src/SIQuester/SIQuester/App.xaml.cs | 17 +++- .../Behaviors/CommandBindingsManager.cs | 44 --------- .../Helpers/DocumentCollectionController.cs | 17 ++++ .../Implementation/DesktopManager.cs | 2 + src/SIQuester/SIQuester/MainWindow.xaml | 49 +++++----- src/SIQuester/SIQuester/MainWindow.xaml.cs | 19 ++++ .../Properties/Resources.Designer.cs | 36 ++++++++ .../SIQuester/Properties/Resources.en-US.resx | 12 +++ .../SIQuester/Properties/Resources.resx | 12 +++ src/SIQuester/SIQuester/SIQuester.csproj | 1 + .../Services/Host/ClipboardService.cs | 3 + .../SIQuester/View/SpardEditorView.xaml | 23 ++--- src/SIQuester/SIQuester/appsettings.json | 2 +- 29 files changed, 346 insertions(+), 203 deletions(-) create mode 100644 src/SIQuester/SIQuester.ViewModel/Contracts/IDocumentViewModelFactory.cs create mode 100644 src/SIQuester/SIQuester.ViewModel/ServiceCollectionExtensions.cs create mode 100644 src/SIQuester/SIQuester.ViewModel/Services/DocumentViewModelFactory.cs delete mode 100644 src/SIQuester/SIQuester/Behaviors/CommandBindingsManager.cs create mode 100644 src/SIQuester/SIQuester/Helpers/DocumentCollectionController.cs diff --git a/src/SIQuester/SIQuester.ViewModel/Contracts/Host/IClipboardService.cs b/src/SIQuester/SIQuester.ViewModel/Contracts/Host/IClipboardService.cs index 5c49befb..00040995 100644 --- a/src/SIQuester/SIQuester.ViewModel/Contracts/Host/IClipboardService.cs +++ b/src/SIQuester/SIQuester.ViewModel/Contracts/Host/IClipboardService.cs @@ -5,6 +5,12 @@ /// </summary> public interface IClipboardService { + /// <summary> + /// Queries the Clipboard for the presence of data in a specified data format. + /// </summary> + /// <param name="format">Data format.</param> + bool ContainsData(string format); + /// <summary> /// Retrieves data in a specified format from the Clipboard. /// </summary> diff --git a/src/SIQuester/SIQuester.ViewModel/Contracts/IDocumentViewModelFactory.cs b/src/SIQuester/SIQuester.ViewModel/Contracts/IDocumentViewModelFactory.cs new file mode 100644 index 00000000..c6c82264 --- /dev/null +++ b/src/SIQuester/SIQuester.ViewModel/Contracts/IDocumentViewModelFactory.cs @@ -0,0 +1,16 @@ +using SIPackages; + +namespace SIQuester.ViewModel.Contracts; + +/// <summary> +/// Provides method for creating documents. +/// </summary> +public interface IDocumentViewModelFactory +{ + /// <summary> + /// Creates view model for the document. + /// </summary> + /// <param name="document">Package document.</param> + /// <param name="fileName">Package file name.</param> + QDocument CreateViewModelFor(SIDocument document, string? fileName = null); +} diff --git a/src/SIQuester/SIQuester.ViewModel/ModelViewBase.cs b/src/SIQuester/SIQuester.ViewModel/ModelViewBase.cs index fe62e1e1..068e93cf 100644 --- a/src/SIQuester/SIQuester.ViewModel/ModelViewBase.cs +++ b/src/SIQuester/SIQuester.ViewModel/ModelViewBase.cs @@ -1,7 +1,6 @@ using SIPackages.Core; using System.ComponentModel; using System.Runtime.CompilerServices; -using System.Windows.Input; namespace SIQuester.ViewModel; @@ -10,21 +9,6 @@ namespace SIQuester.ViewModel; /// </summary> public abstract class ModelViewBase : INotifyPropertyChanged, IDisposable { - /// <summary> - /// Allows to keep bindings to common application commands. - /// </summary> - public CommandBindingCollection CommandBindings { get; } = new(); - - protected void AddCommandBinding(ICommand command, ExecutedRoutedEventHandler executed, CanExecuteRoutedEventHandler canExecute = null) - { - var commandBinding = canExecute != null ? - new CommandBinding(command, executed, canExecute) - : new CommandBinding(command, executed); - - CommandManager.RegisterClassCommandBinding(GetType(), commandBinding); - CommandBindings.Add(commandBinding); - } - protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); diff --git a/src/SIQuester/SIQuester.ViewModel/PlatformSpecific/PlatformManager.cs b/src/SIQuester/SIQuester.ViewModel/PlatformSpecific/PlatformManager.cs index 5ad0f0d8..6e181b32 100644 --- a/src/SIQuester/SIQuester.ViewModel/PlatformSpecific/PlatformManager.cs +++ b/src/SIQuester/SIQuester.ViewModel/PlatformSpecific/PlatformManager.cs @@ -16,6 +16,11 @@ public abstract class PlatformManager public IServiceProvider ServiceProvider { get; set; } + /// <summary> + /// Gets well-known font family names. + /// </summary> + public abstract string[] FontFamilies { get; } + protected PlatformManager() { Instance = this; diff --git a/src/SIQuester/SIQuester.ViewModel/SIQuester.ViewModel.csproj b/src/SIQuester/SIQuester.ViewModel/SIQuester.ViewModel.csproj index cd639f8c..1b31668a 100644 --- a/src/SIQuester/SIQuester.ViewModel/SIQuester.ViewModel.csproj +++ b/src/SIQuester/SIQuester.ViewModel/SIQuester.ViewModel.csproj @@ -1,6 +1,6 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net6.0-windows</TargetFramework> + <TargetFramework>net6.0</TargetFramework> <AssemblyTitle>SIQuester.ViewModel</AssemblyTitle> <Product>SIQuester.ViewModel</Product> <Description>SIQuester business logic</Description> @@ -32,7 +32,6 @@ <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" /> <PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" /> - <PackageReference Include="MahApps.Metro" Version="2.4.10" /> <PackageReference Include="System.Text.Encoding.CodePages" Version="7.0.0" /> <PackageReference Include="YamlDotNet" Version="13.1.1" /> </ItemGroup> diff --git a/src/SIQuester/SIQuester.ViewModel/ServiceCollectionExtensions.cs b/src/SIQuester/SIQuester.ViewModel/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..3c874d49 --- /dev/null +++ b/src/SIQuester/SIQuester.ViewModel/ServiceCollectionExtensions.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.DependencyInjection; +using SIQuester.ViewModel.Contracts; +using SIQuester.ViewModel.Services; +using SIStorageService.ViewModel; + +namespace SIQuester.ViewModel; + +/// <summary> +/// Allows to register SIQuester view model in <see cref="IServiceCollection" />. +/// </summary> +public static class ServiceCollectionExtensions +{ + /// <summary> + /// Registers SIQuester view model in <see cref="IServiceCollection" />. + /// </summary> + /// <param name="services">Services collection.</param> + public static IServiceCollection AddSIQuester(this IServiceCollection services) + { + services.AddSingleton<IPackageTemplatesRepository, PackageTemplatesRepository>(); + services.AddSingleton<StorageViewModel>(); + services.AddSingleton<StorageContextViewModel>(); + services.AddSingleton<IDocumentViewModelFactory, DocumentViewModelFactory>(); + + return services; + } +} diff --git a/src/SIQuester/SIQuester.ViewModel/Services/DocumentViewModelFactory.cs b/src/SIQuester/SIQuester.ViewModel/Services/DocumentViewModelFactory.cs new file mode 100644 index 00000000..39c853c6 --- /dev/null +++ b/src/SIQuester/SIQuester.ViewModel/Services/DocumentViewModelFactory.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Logging; +using SIPackages; +using SIQuester.ViewModel.Contracts; +using SIQuester.ViewModel.Contracts.Host; + +namespace SIQuester.ViewModel.Services; + +/// <inheritdoc /> +internal class DocumentViewModelFactory : IDocumentViewModelFactory +{ + private readonly StorageContextViewModel _storageContextViewModel; + private readonly IClipboardService _clipboardService; + private readonly ILoggerFactory _loggerFactory; + + public DocumentViewModelFactory( + StorageContextViewModel storageContextViewModel, + IClipboardService clipboardService, + ILoggerFactory loggerFactory) + { + _storageContextViewModel = storageContextViewModel; + _clipboardService = clipboardService; + _loggerFactory = loggerFactory; + } + + public QDocument CreateViewModelFor(SIDocument document, string? fileName = null) => new(document, _storageContextViewModel, _clipboardService, _loggerFactory) + { + FileName = fileName ?? document.Package.Name + }; +} diff --git a/src/SIQuester/SIQuester.ViewModel/Services/PackageTemplatesRepository.cs b/src/SIQuester/SIQuester.ViewModel/Services/PackageTemplatesRepository.cs index bcef9b34..6ba4dda8 100644 --- a/src/SIQuester/SIQuester.ViewModel/Services/PackageTemplatesRepository.cs +++ b/src/SIQuester/SIQuester.ViewModel/Services/PackageTemplatesRepository.cs @@ -4,7 +4,7 @@ namespace SIQuester.ViewModel.Services; -/// <inheritdoc /> +/// <inheritdoc cref="IPackageTemplatesRepository" /> public sealed class PackageTemplatesRepository : IPackageTemplatesRepository { /// <summary> diff --git a/src/SIQuester/SIQuester.ViewModel/Workspaces/Dialogs/SelectThemesViewModel.cs b/src/SIQuester/SIQuester.ViewModel/Workspaces/Dialogs/SelectThemesViewModel.cs index ab4f6f69..5545e28e 100644 --- a/src/SIQuester/SIQuester.ViewModel/Workspaces/Dialogs/SelectThemesViewModel.cs +++ b/src/SIQuester/SIQuester.ViewModel/Workspaces/Dialogs/SelectThemesViewModel.cs @@ -1,8 +1,8 @@ -using Microsoft.Extensions.Logging; -using SIPackages; +using SIPackages; using SIPackages.Core; using SIQuester.Model; using SIQuester.ViewModel.Configuration; +using SIQuester.ViewModel.Contracts; using SIQuester.ViewModel.Properties; using System.Windows.Input; using Utils.Commands; @@ -57,13 +57,13 @@ public int To public ICommand Select2 { get; private set; } - private readonly ILoggerFactory _loggerFactory; + private readonly IDocumentViewModelFactory _documentViewModelFactory; - public SelectThemesViewModel(QDocument document, AppOptions appOptions, ILoggerFactory loggerFactory) + public SelectThemesViewModel(QDocument document, AppOptions appOptions, IDocumentViewModelFactory documentViewModelFactory) { _document = document; _appOptions = appOptions; - _loggerFactory = loggerFactory; + _documentViewModelFactory = documentViewModelFactory; Themes = _document.Document.Package.Rounds .SelectMany(round => round.Themes) @@ -86,7 +86,7 @@ private async void Select_Executed(object? arg) var allthemes = new List<Theme>(); _document.Document.Package.Rounds.ForEach(round => round.Themes.ForEach(allthemes.Add)); - var targetDocument = new QDocument(newDocument, _document.StorageContext, _loggerFactory) { FileName = newDocument.Package.Name }; + var targetDocument = _documentViewModelFactory.CreateViewModelFor(newDocument); for (var index = _from; index <= _to; index++) { @@ -120,7 +120,7 @@ private async void Select2_Executed(object? arg) var allthemes = Themes.Where(st => st.IsSelected).Select(st => st.Theme); - var targetDocument = new QDocument(newDocument, _document.StorageContext, _loggerFactory) { FileName = newDocument.Package.Name }; + var targetDocument = _documentViewModelFactory.CreateViewModelFor(newDocument); foreach (var theme in allthemes) { diff --git a/src/SIQuester/SIQuester.ViewModel/Workspaces/ImportDBStorageViewModel.cs b/src/SIQuester/SIQuester.ViewModel/Workspaces/ImportDBStorageViewModel.cs index f46e3653..1b981588 100644 --- a/src/SIQuester/SIQuester.ViewModel/Workspaces/ImportDBStorageViewModel.cs +++ b/src/SIQuester/SIQuester.ViewModel/Workspaces/ImportDBStorageViewModel.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Logging; -using Notions; +using Notions; using SIPackages; using SIPackages.Core; using SIQuester.ViewModel.Configuration; @@ -71,29 +70,29 @@ private async void LoadTours() } } - private readonly StorageContextViewModel _storageContextViewModel; + private readonly IDocumentViewModelFactory _documentViewModelFactory; private readonly AppOptions _appOptions; private readonly IChgkDbClient _chgkDbClient; - private readonly ILoggerFactory _loggerFactory; public ImportDBStorageViewModel( - StorageContextViewModel storageContextViewModel, + IDocumentViewModelFactory documentViewModelFactory, IChgkDbClient chgkDbClient, - AppOptions appOptions, - ILoggerFactory loggerFactory) + AppOptions appOptions) { - _storageContextViewModel = storageContextViewModel; + _documentViewModelFactory = documentViewModelFactory; _appOptions = appOptions; _chgkDbClient = chgkDbClient; - _loggerFactory = loggerFactory; } public async Task SelectNodeAsync(DBNode item) { async Task<QDocument> loader(CancellationToken cancellationToken) { - var siDoc = await SelectAsync(item, cancellationToken); - return new QDocument(siDoc, _storageContextViewModel, _loggerFactory) { FileName = siDoc.Package.Name, Changed = true }; + var siDocument = await SelectAsync(item, cancellationToken); + var documentViewModel = _documentViewModelFactory.CreateViewModelFor(siDocument); + documentViewModel.Changed = true; + + return documentViewModel; }; var loaderViewModel = new DocumentLoaderViewModel(item.Name); diff --git a/src/SIQuester/SIQuester.ViewModel/Workspaces/ImportSIStorageViewModel.cs b/src/SIQuester/SIQuester.ViewModel/Workspaces/ImportSIStorageViewModel.cs index b11b070b..6e74ca58 100644 --- a/src/SIQuester/SIQuester.ViewModel/Workspaces/ImportSIStorageViewModel.cs +++ b/src/SIQuester/SIQuester.ViewModel/Workspaces/ImportSIStorageViewModel.cs @@ -1,5 +1,5 @@ -using Microsoft.Extensions.Logging; -using SIQuester.ViewModel.Configuration; +using SIQuester.ViewModel.Configuration; +using SIQuester.ViewModel.Contracts; using SIQuester.ViewModel.Properties; using SIStorageService.ViewModel; using System.ComponentModel; @@ -7,6 +7,9 @@ namespace SIQuester.ViewModel; +/// <summary> +/// Allows to import package from SIStorage. +/// </summary> public sealed class ImportSIStorageViewModel : WorkspaceViewModel { private static readonly HttpClient HttpClient = new() { DefaultRequestVersion = HttpVersion.Version20 }; @@ -15,24 +18,20 @@ public sealed class ImportSIStorageViewModel : WorkspaceViewModel public override string Header => Resources.SIStorage; - private readonly StorageContextViewModel _storageContextViewModel; - public bool IsProgress => Storage.IsLoading || Storage.IsLoadingPackages; private readonly AppOptions _appOptions; - private readonly ILoggerFactory _loggerFactory; + private readonly IDocumentViewModelFactory _documentViewModelFactory; private readonly CancellationTokenSource _cancellationTokenSource = new(); public ImportSIStorageViewModel( - StorageContextViewModel storageContextViewModel, StorageViewModel siStorage, AppOptions appOptions, - ILoggerFactory loggerFactory) + IDocumentViewModelFactory documentViewModelFactory) { - _storageContextViewModel = storageContextViewModel; _appOptions = appOptions; - _loggerFactory = loggerFactory; + _documentViewModelFactory = documentViewModelFactory; Storage = siStorage; @@ -78,7 +77,7 @@ async Task<QDocument> loader(Uri uri, CancellationToken cancellationToken) doc.Upgrade(); } - return new QDocument(doc, _storageContextViewModel, _loggerFactory) { FileName = doc.Package.Name }; + return _documentViewModelFactory.CreateViewModelFor(doc); }; var package = Storage.CurrentPackage; diff --git a/src/SIQuester/SIQuester.ViewModel/Workspaces/ImportTextViewModel.cs b/src/SIQuester/SIQuester.ViewModel/Workspaces/ImportTextViewModel.cs index 42d52bad..ac40a86a 100644 --- a/src/SIQuester/SIQuester.ViewModel/Workspaces/ImportTextViewModel.cs +++ b/src/SIQuester/SIQuester.ViewModel/Workspaces/ImportTextViewModel.cs @@ -260,7 +260,6 @@ public int Progress private readonly CancellationTokenSource _tokenSource = new(); private readonly TaskScheduler _scheduler; - private readonly StorageContextViewModel _storageContextViewModel; private readonly AppOptions _appOptions; private string _badTextCopy = ""; @@ -352,7 +351,7 @@ public string SkipToolTip public event Action<int, int, string?, bool>? HighlightText; - private readonly ILoggerFactory _loggerFactory; + private readonly IDocumentViewModelFactory _documentViewModelFactory; private Encoding _textEncoding = Encoding.UTF8; @@ -377,15 +376,13 @@ public Encoding TextEncoding /// <summary> /// Initializes a new instance of <see cref="ImportTextViewModel" /> class. /// </summary> - /// <param name="storageContextViewModel">Well-known SIStorage facets holder.</param> /// <param name="appOptions">Application options.</param> /// <param name="clipboardService">Clipboard access service.</param> - /// <param name="loggerFactory">Factory to create loggers.</param> - public ImportTextViewModel(StorageContextViewModel storageContextViewModel, AppOptions appOptions, IClipboardService clipboardService, ILoggerFactory loggerFactory) + /// <param name="documentViewModelFactory">Factory to create documents.</param> + public ImportTextViewModel(AppOptions appOptions, IClipboardService clipboardService, IDocumentViewModelFactory documentViewModelFactory) { - _storageContextViewModel = storageContextViewModel; _appOptions = appOptions; - _loggerFactory = loggerFactory; + _documentViewModelFactory = documentViewModelFactory; _scheduler = TaskScheduler.FromCurrentSynchronizationContext(); var trashAlias = new EditAlias(Resources.Trash, "#FFD3D3D3"); @@ -569,10 +566,10 @@ private void AnalyzeFinished(Task<Tuple<bool, int>> task) var themesNum = task.Result.Item2; if (task.Result.Item1) { - if (!task.IsCanceled) + if (!task.IsCanceled && _existing != null) { PlatformManager.Instance.Inform($"{Resources.Success} {themesNum}."); - OnNewItem(new QDocument(_existing, _storageContextViewModel, _loggerFactory) { FileName = _existing.Package.Name }); + OnNewItem(_documentViewModelFactory.CreateViewModelFor(_existing)); } } } @@ -825,7 +822,7 @@ public void Clean() exc => OnError(exc, ""), CancellationToken.None); - OnNewItem(new QDocument(_existing, _storageContextViewModel, _loggerFactory) { FileName = _existing.Package.Name }); + OnNewItem(_documentViewModelFactory.CreateViewModelFor(_existing)); } } diff --git a/src/SIQuester/SIQuester.ViewModel/Workspaces/MainViewModel.cs b/src/SIQuester/SIQuester.ViewModel/Workspaces/MainViewModel.cs index c174e19b..0611a58a 100644 --- a/src/SIQuester/SIQuester.ViewModel/Workspaces/MainViewModel.cs +++ b/src/SIQuester/SIQuester.ViewModel/Workspaces/MainViewModel.cs @@ -11,13 +11,11 @@ using SIQuester.ViewModel.Properties; using SIQuester.ViewModel.Serializers; using SIQuester.ViewModel.Services; -using SIStorage.Service.Contract; using SIStorageService.ViewModel; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Text; -using System.Windows.Data; using System.Windows.Input; using Utils; using Utils.Commands; @@ -38,6 +36,11 @@ public sealed class MainViewModel : ModelViewBase, INotifyPropertyChanged #region Commands + /// <summary> + /// Creates a new workspace. + /// </summary> + public ICommand New { get; private set; } + /// <summary> /// Opens a file. /// </summary> @@ -93,6 +96,16 @@ public sealed class MainViewModel : ModelViewBase, INotifyPropertyChanged public ICommand SearchFolder { get; private set; } + /// <summary> + /// Opens a help file. + /// </summary> + public ICommand Help { get; private set; } + + /// <summary> + /// Closes main view. + /// </summary> + public IAsyncCommand Close { get; private set; } + #endregion /// <summary> @@ -122,8 +135,8 @@ public QDocument? ActiveDocument private readonly string[] _args; private readonly AppOptions _appOptions; - private readonly StorageContextViewModel _storageContextViewModel; private readonly IClipboardService _clipboardService; + private readonly IDocumentViewModelFactory _documentViewModelFactory; private readonly IServiceProvider _serviceProvider; public AppOptions AppOptions => _appOptions; @@ -131,18 +144,18 @@ public QDocument? ActiveDocument public MainViewModel( string[] args, AppOptions appOptions, - ISIStorageServiceClient siStorageServiceClient, IClipboardService clipboardService, IServiceProvider serviceProvider, + IDocumentViewModelFactory documentViewModelFactory, ILoggerFactory loggerFactory) { _loggerFactory = loggerFactory; _clipboardService = clipboardService; + _documentViewModelFactory = documentViewModelFactory; _logger = loggerFactory.CreateLogger<MainViewModel>(); _appOptions = appOptions; DocList.CollectionChanged += DocList_CollectionChanged; - CollectionViewSource.GetDefaultView(DocList).CurrentChanged += MainViewModel_CurrentChanged; Open = new SimpleCommand(Open_Executed); OpenRecent = new SimpleCommand(OpenRecent_Executed); @@ -163,28 +176,17 @@ public MainViewModel( SetSettings = new SimpleCommand(SetSettings_Executed); SearchFolder = new SimpleCommand(SearchFolder_Executed); - _storageContextViewModel = new StorageContextViewModel(siStorageServiceClient); - _storageContextViewModel.Load(); - _serviceProvider = serviceProvider; - AddCommandBinding(ApplicationCommands.New, New_Executed); - AddCommandBinding(ApplicationCommands.Open, (sender, e) => Open_Executed(e.Parameter)); - AddCommandBinding(ApplicationCommands.Help, Help_Executed); - AddCommandBinding(ApplicationCommands.Close, Close_Executed); - - AddCommandBinding(ApplicationCommands.SaveAs, (s, e) => ActiveDocument?.SaveAs_Executed(), CanExecuteDocumentCommand); - - AddCommandBinding(ApplicationCommands.Copy, (s, e) => ActiveDocument?.Copy_Executed(), CanExecuteDocumentCommand); - AddCommandBinding(ApplicationCommands.Paste, (s, e) => ActiveDocument?.Paste_Executed(), CanExecuteDocumentCommand); + New = new SimpleCommand(New_Executed); + Help = new SimpleCommand(Help_Executed); + Close = new AsyncCommand(Close_Executed); _args = args; UI.Initialize(); } - private void CanExecuteDocumentCommand(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = ActiveDocument != null; - public async Task InitializeAsync() { if (_args.Length > 0) @@ -244,9 +246,9 @@ public async Task InitializeAsync() private void SetSettings_Executed(object? arg) => DocList.Add(new SettingsViewModel()); - private void Help_Executed(object? sender, ExecutedRoutedEventArgs e) => PlatformManager.Instance.ShowHelp(); + private void Help_Executed(object? arg) => PlatformManager.Instance.ShowHelp(); - private async void Close_Executed(object? sender, ExecutedRoutedEventArgs e) + private async Task Close_Executed(object? arg) { _logger.LogInformation("Close_Executed"); @@ -294,9 +296,6 @@ private static void OpenUri(string uri) private void About_Executed(object? arg) => DocList.Add(new AboutViewModel()); - private void MainViewModel_CurrentChanged(object? sender, EventArgs e) => - ActiveDocument = CollectionViewSource.GetDefaultView(DocList).CurrentItem as QDocument; - private void DocList_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) @@ -309,7 +308,6 @@ private void DocList_CollectionChanged(object? sender, NotifyCollectionChangedEv item.Closed += Item_Closed; } - CollectionViewSource.GetDefaultView(DocList).MoveCurrentToLast(); CheckSaveAllCanBeExecuted(this, EventArgs.Empty); break; @@ -351,10 +349,8 @@ private void CheckSaveAllCanBeExecuted(object sender, EventArgs e) /// <summary> /// Новый /// </summary> - private void New_Executed(object? sender, ExecutedRoutedEventArgs e) - { - DocList.Add(new NewViewModel(_storageContextViewModel, _serviceProvider.GetRequiredService<IPackageTemplatesRepository>(), _appOptions, _loggerFactory)); - } + private void New_Executed(object? arg) => + DocList.Add(new NewViewModel(_serviceProvider.GetRequiredService<IPackageTemplatesRepository>(), _appOptions, _documentViewModelFactory, _loggerFactory)); /// <summary> /// Открыть существующий пакет @@ -426,11 +422,8 @@ Task<QDocument> loader(CancellationToken cancellationToken) => Task.Run(() => _logger.LogInformation("Document has been successfully opened. Path: {path}", path); - var docViewModel = new QDocument(doc, _storageContextViewModel, _loggerFactory) - { - Path = path, - FileName = Path.GetFileNameWithoutExtension(path) - }; + var docViewModel = _documentViewModelFactory.CreateViewModelFor(doc, Path.GetFileNameWithoutExtension(path)); + docViewModel.Path = path; docViewModel.CheckFileSize(); @@ -522,7 +515,7 @@ private void ImportTxt_Executed(object? arg) _ => throw new InvalidOperationException($"Incorrect text source: {arg}"), }; - var model = new ImportTextViewModel(_storageContextViewModel, _appOptions, _clipboardService, _loggerFactory); + var model = new ImportTextViewModel(_appOptions, _clipboardService, _documentViewModelFactory); DocList.Add(model); if (textSource != null) @@ -550,12 +543,10 @@ private void ImportXml_Executed(object? arg) using var stream = File.OpenRead(file); var doc = SIDocument.LoadXml(stream); - var docViewModel = new QDocument(doc, _storageContextViewModel, _loggerFactory) - { - Path = "", - Changed = true, - FileName = Path.GetFileNameWithoutExtension(file) - }; + var docViewModel = _documentViewModelFactory.CreateViewModelFor(doc, Path.GetFileNameWithoutExtension(file)); + + docViewModel.Path = ""; + docViewModel.Changed = true; var mediaFolder = Path.GetDirectoryName(file); @@ -597,12 +588,9 @@ private void ImportYaml_Executed(object? arg) doc.Upgrade(); } - var docViewModel = new QDocument(doc, _storageContextViewModel, _loggerFactory) - { - Path = "", - Changed = true, - FileName = Path.GetFileNameWithoutExtension(file) - }; + var docViewModel = _documentViewModelFactory.CreateViewModelFor(doc, Path.GetFileNameWithoutExtension(file)); + docViewModel.Path = ""; + docViewModel.Changed = true; var mediaFolder = Path.GetDirectoryName(file); @@ -624,10 +612,9 @@ private void ImportYaml_Executed(object? arg) /// </summary> private void ImportBase_Executed(object? arg) => DocList.Add(new ImportDBStorageViewModel( - _storageContextViewModel, + _documentViewModelFactory, _serviceProvider.GetRequiredService<IChgkDbClient>(), - _appOptions, - _loggerFactory)); + _appOptions)); /// <summary> /// Imports package from SI Storage. @@ -635,10 +622,9 @@ private void ImportBase_Executed(object? arg) => private async void ImportFromSIStore_Executed(object? arg) { var importViewModel = new ImportSIStorageViewModel( - _storageContextViewModel, _serviceProvider.GetRequiredService<StorageViewModel>(), _appOptions, - _loggerFactory); + _documentViewModelFactory); DocList.Add(importViewModel); diff --git a/src/SIQuester/SIQuester.ViewModel/Workspaces/NewViewModel.cs b/src/SIQuester/SIQuester.ViewModel/Workspaces/NewViewModel.cs index d13b48fe..8d160c61 100644 --- a/src/SIQuester/SIQuester.ViewModel/Workspaces/NewViewModel.cs +++ b/src/SIQuester/SIQuester.ViewModel/Workspaces/NewViewModel.cs @@ -88,21 +88,21 @@ public string PackageAuthor /// </summary> public List<string> Errors { get; } = new(); - private readonly StorageContextViewModel _storageContextViewModel; private readonly IPackageTemplatesRepository _packageTemplatesRepository; private readonly AppOptions _appOptions; private readonly ILoggerFactory _loggerFactory; + private readonly IDocumentViewModelFactory _documentViewModelFactory; private readonly ILogger<NewViewModel> _logger; public NewViewModel( - StorageContextViewModel storageContextViewModel, IPackageTemplatesRepository packageTemplatesRepository, AppOptions appOptions, + IDocumentViewModelFactory documentViewModelFactory, ILoggerFactory loggerFactory) { - _storageContextViewModel = storageContextViewModel; _packageTemplatesRepository = packageTemplatesRepository; _appOptions = appOptions; + _documentViewModelFactory = documentViewModelFactory; _loggerFactory = loggerFactory; _logger = _loggerFactory.CreateLogger<NewViewModel>(); @@ -167,7 +167,7 @@ private void Create_Executed(object? arg) siDocument.Upgrade(); } - OnNewItem(new QDocument(siDocument, _storageContextViewModel, _loggerFactory) { FileName = siDocument.Package.Name }); + OnNewItem(_documentViewModelFactory.CreateViewModelFor(siDocument)); _logger.LogInformation("New document created. Name: {name}", siDocument.Package.Name); OnClosed(); } diff --git a/src/SIQuester/SIQuester.ViewModel/Workspaces/QDocument.cs b/src/SIQuester/SIQuester.ViewModel/Workspaces/QDocument.cs index 00e641f5..fd67192e 100644 --- a/src/SIQuester/SIQuester.ViewModel/Workspaces/QDocument.cs +++ b/src/SIQuester/SIQuester.ViewModel/Workspaces/QDocument.cs @@ -8,6 +8,7 @@ using SIQuester.Model; using SIQuester.ViewModel.Configuration; using SIQuester.ViewModel.Contracts; +using SIQuester.ViewModel.Contracts.Host; using SIQuester.ViewModel.Helpers; using SIQuester.ViewModel.Model; using SIQuester.ViewModel.PlatformSpecific; @@ -59,6 +60,21 @@ public sealed class QDocument : WorkspaceViewModel private bool _changed = false; + /// <summary> + /// Saves document under different name. + /// </summary> + public IAsyncCommand SaveAs { get; private set; } + + /// <summary> + /// Copies document item. + /// </summary> + public ICommand Copy { get; private set; } + + /// <summary> + /// Pastes document item. + /// </summary> + public ICommand Paste { get; private set; } + public OperationsManager OperationsManager { get; } = new(); private IItemViewModel? _activeNode = null; @@ -1202,11 +1218,13 @@ private void DetachParameterListeners(StepParameterRecord parameter) private bool _isLinksClearingBlocked; + private readonly IClipboardService _clipboardService; private readonly ILoggerFactory _loggerFactory; internal QDocument( SIDocument document, StorageContextViewModel storageContextViewModel, + IClipboardService clipboardService, ILoggerFactory loggerFactory) { Lock = new Lock(document.Package.Name); @@ -1214,6 +1232,7 @@ internal QDocument( OperationsManager.Changed += OperationsManager_Changed; OperationsManager.Error += OperationsManager_Error; + _clipboardService = clipboardService; _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger<QDocument>(); @@ -1222,6 +1241,7 @@ internal QDocument( ImportSiq = new SimpleCommand(ImportSiq_Executed); Save = new AsyncCommand(Save_Executed); + SaveAs = new AsyncCommand(SaveAs_Executed); SaveAsTemplate = new AsyncCommand(SaveAsTemplate_Executed); ExportHtml = new SimpleCommand(ExportHtml_Executed); @@ -1256,6 +1276,9 @@ internal QDocument( Delete = new SimpleCommand(Delete_Executed); + Copy = new SimpleCommand(Copy_Executed); + Paste = new SimpleCommand(Paste_Executed); + NextSearchResult = new SimpleCommand(NextSearchResult_Executed) { CanBeExecuted = false }; PreviousSearchResult = new SimpleCommand(PreviousSearchResult_Executed) { CanBeExecuted = false }; ClearSearchText = new SimpleCommand(ClearSearchText_Executed) { CanBeExecuted = false }; @@ -1545,7 +1568,7 @@ private static void CheckCommonFiles(ICollection<string> images, ICollection<str } } - internal void Copy_Executed() + internal void Copy_Executed(object? arg) { if (_activeNode == null) { @@ -1555,7 +1578,7 @@ internal void Copy_Executed() try { var itemData = new InfoOwnerData(this, _activeNode); - Clipboard.SetData(ClipboardKey, itemData); + _clipboardService.SetData(ClipboardKey, itemData); } catch (Exception exc) { @@ -1563,14 +1586,14 @@ internal void Copy_Executed() } } - internal void Paste_Executed() + internal void Paste_Executed(object? arg) { if (_activeNode == null) { return; } - if (!Clipboard.ContainsData(ClipboardKey)) + if (!_clipboardService.ContainsData(ClipboardKey)) { return; } @@ -1579,7 +1602,7 @@ internal void Paste_Executed() { using var change = OperationsManager.BeginComplexChange(); - var itemData = (InfoOwnerData)Clipboard.GetData(ClipboardKey); + var itemData = (InfoOwnerData)_clipboardService.GetData(ClipboardKey); var level = itemData.ItemLevel; if (level == InfoOwnerData.Level.Round) @@ -2607,7 +2630,7 @@ private void ClearTempFolder() } } - internal async void SaveAs_Executed() => await SaveAsAsync(); + internal Task SaveAs_Executed(object? arg) => SaveAsAsync(); private async Task SaveAsAsync() { @@ -3258,7 +3281,7 @@ private void SelectThemes_Executed(object? arg) var selectThemesViewModel = new SelectThemesViewModel( this, PlatformManager.Instance.ServiceProvider.GetRequiredService<IOptions<AppOptions>>().Value, - _loggerFactory); + PlatformManager.Instance.ServiceProvider.GetRequiredService<IDocumentViewModelFactory>()); selectThemesViewModel.NewItem += OnNewItem; Dialog = selectThemesViewModel; diff --git a/src/SIQuester/SIQuester.ViewModel/Workspaces/SettingsViewModel.cs b/src/SIQuester/SIQuester.ViewModel/Workspaces/SettingsViewModel.cs index 2b823327..a427a81e 100644 --- a/src/SIQuester/SIQuester.ViewModel/Workspaces/SettingsViewModel.cs +++ b/src/SIQuester/SIQuester.ViewModel/Workspaces/SettingsViewModel.cs @@ -1,4 +1,5 @@ using SIQuester.Model; +using SIQuester.ViewModel.PlatformSpecific; using SIQuester.ViewModel.Properties; using System.Windows.Input; using Utils.Commands; @@ -14,7 +15,7 @@ public sealed class SettingsViewModel : WorkspaceViewModel public override string Header => Resources.Options; - public string[] Fonts => System.Windows.Media.Fonts.SystemFontFamilies.Select(ff => ff.Source).OrderBy(f => f).ToArray(); + public string[] Fonts => PlatformManager.Instance.FontFamilies; public bool SpellCheckingEnabled => Environment.OSVersion.Version > new Version(6, 2); diff --git a/src/SIQuester/SIQuester/App.xaml.cs b/src/SIQuester/SIQuester/App.xaml.cs index 8e005841..f2def116 100644 --- a/src/SIQuester/SIQuester/App.xaml.cs +++ b/src/SIQuester/SIQuester/App.xaml.cs @@ -8,6 +8,7 @@ using NLog.Extensions.Logging; using NLog.Web; using SIPackages; +using SIQuester.Helpers; using SIQuester.Model; using SIQuester.Services.Host; using SIQuester.ViewModel; @@ -30,6 +31,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Windows; +using System.Windows.Data; using System.Windows.Threading; using System.Xaml; #if !DEBUG @@ -166,9 +168,14 @@ protected override void OnStartup(StartupEventArgs e) var siStorageClient = _host.Services.GetRequiredService<ISIStorageServiceClient>(); var clipboardService = _host.Services.GetRequiredService<IClipboardService>(); var options = _host.Services.GetRequiredService<IOptions<AppOptions>>(); + var documentViewModelFactory = _host.Services.GetRequiredService<IDocumentViewModelFactory>(); var loggerFactory = _host.Services.GetRequiredService<ILoggerFactory>(); - _mainViewModel = new MainViewModel(e.Args, options.Value, siStorageClient, clipboardService, _host.Services, loggerFactory); + _mainViewModel = new MainViewModel(e.Args, options.Value, clipboardService, _host.Services, documentViewModelFactory, loggerFactory); + DocumentCollectionController.AttachTo(_mainViewModel); + + var storageContextViewModel = _host.Services.GetRequiredService<StorageContextViewModel>(); + storageContextViewModel.Load(); MainWindow = new MainWindow { DataContext = _mainViewModel }; MainWindow.Show(); @@ -197,11 +204,13 @@ private void ConfigureServices(HostBuilderContext ctx, IServiceCollection servic services.AddAppServiceClient(ctx.Configuration); services.AddSIStorageServiceClient(ctx.Configuration); services.AddChgkServiceClient(ctx.Configuration); + services.AddSingleton(AppSettings.Default); - services.AddSingleton<IPackageTemplatesRepository, PackageTemplatesRepository>(); - services.AddSingleton<IClipboardService, ClipboardService>(); - services.AddSingleton<StorageViewModel>(); services.Configure<AppOptions>(ctx.Configuration.GetSection(AppOptions.ConfigurationSectionName)); + + services.AddSingleton<IClipboardService, ClipboardService>(); + + services.AddSIQuester(); } /// <summary> diff --git a/src/SIQuester/SIQuester/Behaviors/CommandBindingsManager.cs b/src/SIQuester/SIQuester/Behaviors/CommandBindingsManager.cs deleted file mode 100644 index 2ec83f21..00000000 --- a/src/SIQuester/SIQuester/Behaviors/CommandBindingsManager.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Windows; -using System.Windows.Input; - -namespace SIQuester.ViewModel; - -/// <summary> -/// Класс, позволяющий передать привязки команд визуальному элементу -/// </summary> -public sealed class CommandBindingsManager : DependencyObject -{ - public static CommandBindingCollection GetRegisterCommandBindings(DependencyObject obj) => - (CommandBindingCollection)obj.GetValue(RegisterCommandBindingsProperty); - - public static void SetRegisterCommandBindings(DependencyObject obj, CommandBindingCollection value) => - obj.SetValue(RegisterCommandBindingsProperty, value); - - public static readonly DependencyProperty RegisterCommandBindingsProperty = - DependencyProperty.RegisterAttached( - "RegisterCommandBindings", - typeof(CommandBindingCollection), - typeof(CommandBindingsManager), - new UIPropertyMetadata(null, OnRegisterCommandBindingChanged)); - - private static void OnRegisterCommandBindingChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) - { - if (sender is not UIElement element) - { - return; - } - - if (e.OldValue is CommandBindingCollection bindings) - { - foreach (CommandBinding item in bindings) - { - element.CommandBindings.Remove(item); - } - } - - if (e.NewValue is CommandBindingCollection newBindings) - { - element.CommandBindings.AddRange(newBindings); - } - } -} diff --git a/src/SIQuester/SIQuester/Helpers/DocumentCollectionController.cs b/src/SIQuester/SIQuester/Helpers/DocumentCollectionController.cs new file mode 100644 index 00000000..42498513 --- /dev/null +++ b/src/SIQuester/SIQuester/Helpers/DocumentCollectionController.cs @@ -0,0 +1,17 @@ +using SIQuester.ViewModel; +using System.ComponentModel; +using System.Windows.Data; + +namespace SIQuester.Helpers; + +/// <summary> +/// Manages main view document collection. +/// </summary> +internal static class DocumentCollectionController +{ + internal static void AttachTo(MainViewModel mainViewModel) + { + var collectionView = CollectionViewSource.GetDefaultView(mainViewModel.DocList); + collectionView.CurrentChanged += (sender, e) => mainViewModel.ActiveDocument = ((ICollectionView?)sender)?.CurrentItem as QDocument; + } +} diff --git a/src/SIQuester/SIQuester/Implementation/DesktopManager.cs b/src/SIQuester/SIQuester/Implementation/DesktopManager.cs index 36178947..2f7c0853 100644 --- a/src/SIQuester/SIQuester/Implementation/DesktopManager.cs +++ b/src/SIQuester/SIQuester/Implementation/DesktopManager.cs @@ -32,6 +32,8 @@ internal sealed class DesktopManager : PlatformManager, IDisposable private const int MAX_PATH = 260; + public override string[] FontFamilies => System.Windows.Media.Fonts.SystemFontFamilies.Select(ff => ff.Source).OrderBy(f => f).ToArray(); + public override Tuple<int, int, int>? GetCurrentItemSelectionArea() => ActionMenuViewModel.Instance.PlacementTarget is TextList box ? box.GetSelectionInfo() : null; diff --git a/src/SIQuester/SIQuester/MainWindow.xaml b/src/SIQuester/SIQuester/MainWindow.xaml index f0f42fb2..8c2aeb50 100644 --- a/src/SIQuester/SIQuester/MainWindow.xaml +++ b/src/SIQuester/SIQuester/MainWindow.xaml @@ -69,7 +69,7 @@ </DataTrigger> </DataTemplate.Triggers> </DataTemplate> - + <Style x:Key="TabControlStyle1" TargetType="{x:Type TabControl}"> <Setter Property="Padding" Value="0" /> <Setter Property="HorizontalContentAlignment" Value="Center" /> @@ -79,17 +79,17 @@ <Setter Property="BorderBrush" Value="#FFACACAC" /> <Setter Property="BorderThickness" Value="0,1,0,0" /> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" /> - + <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TabControl}"> <Grid x:Name="templateRoot" ClipToBounds="True" SnapsToDevicePixels="True" KeyboardNavigation.TabNavigation="Local"> - + <Grid.ColumnDefinitions> <ColumnDefinition x:Name="ColumnDefinition0"/> <ColumnDefinition x:Name="ColumnDefinition1" Width="0"/> </Grid.ColumnDefinitions> - + <Grid.RowDefinitions> <RowDefinition x:Name="RowDefinition0" Height="Auto"/> <RowDefinition x:Name="RowDefinition1" Height="*"/> @@ -126,7 +126,7 @@ SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> </Border> </Grid> - + <ControlTemplate.Triggers> <Trigger Property="TabStripPlacement" Value="Bottom"> <Setter Property="Grid.Row" TargetName="HeaderPanel" Value="1"/> @@ -135,7 +135,7 @@ <Setter Property="Height" TargetName="RowDefinition1" Value="Auto"/> <Setter Property="Margin" TargetName="HeaderPanel" Value="2,0,2,2"/> </Trigger> - + <Trigger Property="TabStripPlacement" Value="Left"> <Setter Property="Grid.Row" TargetName="HeaderPanel" Value="0"/> <Setter Property="Grid.Row" TargetName="ContentPanel" Value="0"/> @@ -147,7 +147,7 @@ <Setter Property="Height" TargetName="RowDefinition1" Value="0"/> <Setter Property="Margin" TargetName="HeaderPanel" Value="2,2,0,2"/> </Trigger> - + <Trigger Property="TabStripPlacement" Value="Right"> <Setter Property="Grid.Row" TargetName="HeaderPanel" Value="0"/> <Setter Property="Grid.Row" TargetName="ContentPanel" Value="0"/> @@ -159,7 +159,7 @@ <Setter Property="Height" TargetName="RowDefinition1" Value="0"/> <Setter Property="Margin" TargetName="HeaderPanel" Value="0,2,2,2"/> </Trigger> - + <Trigger Property="IsEnabled" Value="False"> <Setter Property="TextElement.Foreground" @@ -170,11 +170,11 @@ </ControlTemplate> </Setter.Value> </Setter> - + <Style.Triggers> <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},Path=Items.Count}" Value="0"> <Setter Property="BorderThickness" Value="0" /> - + <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="TabControl"> @@ -184,14 +184,14 @@ DataContext="{Binding Settings.History.Files}" HorizontalAlignment="Center" VerticalAlignment="Center"> - + <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> - + <TextBlock FontSize="24" HorizontalAlignment="Center" Text="{x:Static lp:Resources.RecentFiles}" /> - + <ItemsControl Grid.Row="1" ItemsSource="{Binding}" Margin="0,20,0,0"> <ItemsControl.ItemTemplate> <DataTemplate> @@ -219,7 +219,7 @@ Height="20"> <Path Data="M0,0L1,1M0,1L1,0" Stroke="Gray" Stretch="Fill" Margin="3" /> </Button> - + <TextBlock TextTrimming="CharacterEllipsis" Foreground="#FF040629" TextAlignment="Left"> <Run Text="{Binding Mode=OneWay, Converter={StaticResource FileNameConverter}}" FontSize="18" /> <LineBreak /> @@ -227,7 +227,7 @@ </TextBlock> </DockPanel> </Button> - + <DataTemplate.Triggers> <Trigger SourceName="open" Property="IsMouseOver" Value="True"> <Setter TargetName="remove" Property="Visibility" Value="Visible" /> @@ -255,14 +255,17 @@ <Setter Property="Margin" Value="0" /> </Style> </Window.Resources> - - <vm1:CommandBindingsManager.RegisterCommandBindings> - <MultiBinding Converter="{StaticResource UnionConverter}"> - <Binding Path="CommandBindings" /> - <Binding Path="ActiveDocument.CommandBindings" /> - </MultiBinding> - </vm1:CommandBindingsManager.RegisterCommandBindings> - + + <Window.CommandBindings> + <CommandBinding Command="ApplicationCommands.New" Executed="New_Executed" /> + <CommandBinding Command="ApplicationCommands.Open" Executed="Open_Executed" /> + <CommandBinding Command="ApplicationCommands.Help" Executed="Help_Executed" /> + <CommandBinding Command="ApplicationCommands.Close" Executed="Close_Executed" /> + <CommandBinding Command="ApplicationCommands.SaveAs" Executed="SaveAs_Executed" /> + <CommandBinding Command="ApplicationCommands.Copy" Executed="Copy_Executed" /> + <CommandBinding Command="ApplicationCommands.Paste" Executed="Paste_Executed" /> + </Window.CommandBindings> + <Window.InputBindings> <KeyBinding Gesture="CTRL+N" Command="ApplicationCommands.New" /> <KeyBinding Gesture="CTRL+S" Command="{Binding ActiveDocument.Save}" /> diff --git a/src/SIQuester/SIQuester/MainWindow.xaml.cs b/src/SIQuester/SIQuester/MainWindow.xaml.cs index 89005550..4b88e068 100644 --- a/src/SIQuester/SIQuester/MainWindow.xaml.cs +++ b/src/SIQuester/SIQuester/MainWindow.xaml.cs @@ -60,6 +60,11 @@ private void DocList_CollectionChanged(object? sender, NotifyCollectionChangedEv }; tabControl1.Items.Add(tabItem); + + if (sender != null) + { + CollectionViewSource.GetDefaultView(sender).MoveCurrentToLast(); + } break; case NotifyCollectionChangedAction.Remove: @@ -358,4 +363,18 @@ public CustomWindowAutomationPeer(FrameworkElement owner) : base(owner) { } protected override List<AutomationPeer> GetChildrenCore() => new(); } + + private void New_Executed(object sender, ExecutedRoutedEventArgs e) => ((MainViewModel)DataContext).New.Execute(null); + + private void Open_Executed(object sender, ExecutedRoutedEventArgs e) => ((MainViewModel) DataContext).Open.Execute(e.Parameter); + + private void Help_Executed(object sender, ExecutedRoutedEventArgs e) => ((MainViewModel)DataContext).Help.Execute(null); + + private void Close_Executed(object sender, ExecutedRoutedEventArgs e) => ((MainViewModel)DataContext).Close.ExecuteAsync(null); + + private void SaveAs_Executed(object sender, ExecutedRoutedEventArgs e) => ((MainViewModel)DataContext).ActiveDocument?.SaveAs.ExecuteAsync(null); + + private void Copy_Executed(object sender, ExecutedRoutedEventArgs e) => ((MainViewModel)DataContext).ActiveDocument?.Copy.Execute(null); + + private void Paste_Executed(object sender, ExecutedRoutedEventArgs e) => ((MainViewModel)DataContext).ActiveDocument?.Paste.Execute(null); } diff --git a/src/SIQuester/SIQuester/Properties/Resources.Designer.cs b/src/SIQuester/SIQuester/Properties/Resources.Designer.cs index 58c25b58..d7ff3d93 100644 --- a/src/SIQuester/SIQuester/Properties/Resources.Designer.cs +++ b/src/SIQuester/SIQuester/Properties/Resources.Designer.cs @@ -906,6 +906,15 @@ public static string Copy { } } + /// <summary> + /// Ищет локализованную строку, похожую на Копировать шаблон. + /// </summary> + public static string CopyTemplate { + get { + return ResourceManager.GetString("CopyTemplate", resourceCulture); + } + } + /// <summary> /// Ищет локализованную строку, похожую на Страна. /// </summary> @@ -960,6 +969,15 @@ public static string Cut { } } + /// <summary> + /// Ищет локализованную строку, похожую на Вырезать шаблон. + /// </summary> + public static string CutTemplate { + get { + return ResourceManager.GetString("CutTemplate", resourceCulture); + } + } + /// <summary> /// Ищет локализованную строку, похожую на Удалить. /// </summary> @@ -1789,6 +1807,15 @@ public static string OpenRecent { } } + /// <summary> + /// Ищет локализованную строку, похожую на Необязательный фрагмент. + /// </summary> + public static string OptionalFragment { + get { + return ResourceManager.GetString("OptionalFragment", resourceCulture); + } + } + /// <summary> /// Ищет локализованную строку, похожую на или. /// </summary> @@ -1870,6 +1897,15 @@ public static string Paste { } } + /// <summary> + /// Ищет локализованную строку, похожую на Вставить шаблон. + /// </summary> + public static string PasteTemplate { + get { + return ResourceManager.GetString("PasteTemplate", resourceCulture); + } + } + /// <summary> /// Ищет локализованную строку, похожую на Запустить вопрос. /// </summary> diff --git a/src/SIQuester/SIQuester/Properties/Resources.en-US.resx b/src/SIQuester/SIQuester/Properties/Resources.en-US.resx index 46cfdd5e..53e24aa9 100644 --- a/src/SIQuester/SIQuester/Properties/Resources.en-US.resx +++ b/src/SIQuester/SIQuester/Properties/Resources.en-US.resx @@ -399,6 +399,9 @@ <data name="Copy" xml:space="preserve"> <value>Copy</value> </data> + <data name="CopyTemplate" xml:space="preserve"> + <value>Copy template</value> + </data> <data name="Country" xml:space="preserve"> <value>Country</value> </data> @@ -417,6 +420,9 @@ <data name="Cut" xml:space="preserve"> <value>Cut</value> </data> + <data name="CutTemplate" xml:space="preserve"> + <value>Cut template</value> + </data> <data name="Delete" xml:space="preserve"> <value>Delete</value> </data> @@ -690,6 +696,9 @@ <data name="OpenRecent" xml:space="preserve"> <value>Open recent</value> </data> + <data name="OptionalFragment" xml:space="preserve"> + <value>Optional fragment</value> + </data> <data name="Or" xml:space="preserve"> <value>or</value> </data> @@ -717,6 +726,9 @@ <data name="Paste" xml:space="preserve"> <value>Insert</value> </data> + <data name="PasteTemplate" xml:space="preserve"> + <value>Paste template</value> + </data> <data name="PlayQuestion" xml:space="preserve"> <value>Start question</value> </data> diff --git a/src/SIQuester/SIQuester/Properties/Resources.resx b/src/SIQuester/SIQuester/Properties/Resources.resx index 4e1ae442..ac69cc6b 100644 --- a/src/SIQuester/SIQuester/Properties/Resources.resx +++ b/src/SIQuester/SIQuester/Properties/Resources.resx @@ -399,6 +399,9 @@ <data name="Copy" xml:space="preserve"> <value>Копировать</value> </data> + <data name="CopyTemplate" xml:space="preserve"> + <value>Копировать шаблон</value> + </data> <data name="Country" xml:space="preserve"> <value>Страна</value> </data> @@ -417,6 +420,9 @@ <data name="Cut" xml:space="preserve"> <value>Вырезать</value> </data> + <data name="CutTemplate" xml:space="preserve"> + <value>Вырезать шаблон</value> + </data> <data name="Delete" xml:space="preserve"> <value>Удалить</value> </data> @@ -694,6 +700,9 @@ <data name="OpenRecent" xml:space="preserve"> <value>Открыть недавние</value> </data> + <data name="OptionalFragment" xml:space="preserve"> + <value>Необязательный фрагмент</value> + </data> <data name="Or" xml:space="preserve"> <value>или</value> </data> @@ -721,6 +730,9 @@ <data name="Paste" xml:space="preserve"> <value>Вставить</value> </data> + <data name="PasteTemplate" xml:space="preserve"> + <value>Вставить шаблон</value> + </data> <data name="PlayQuestion" xml:space="preserve"> <value>Запустить вопрос</value> </data> diff --git a/src/SIQuester/SIQuester/SIQuester.csproj b/src/SIQuester/SIQuester/SIQuester.csproj index 05b42fbd..364e15bb 100644 --- a/src/SIQuester/SIQuester/SIQuester.csproj +++ b/src/SIQuester/SIQuester/SIQuester.csproj @@ -55,6 +55,7 @@ <PackageReference Include="NLog.Extensions.Logging" Version="5.2.0" /> <PackageReference Include="NLog.Web.AspNetCore" Version="5.2.0" /> <PackageReference Include="WindowsAPICodePackShell" Version="7.0.4" /> + <PackageReference Include="MahApps.Metro" Version="2.4.10" /> </ItemGroup> <ItemGroup> <None Include="licenses\MahApps.Metro.LICENSE"> diff --git a/src/SIQuester/SIQuester/Services/Host/ClipboardService.cs b/src/SIQuester/SIQuester/Services/Host/ClipboardService.cs index 0f4a379e..c7e529d3 100644 --- a/src/SIQuester/SIQuester/Services/Host/ClipboardService.cs +++ b/src/SIQuester/SIQuester/Services/Host/ClipboardService.cs @@ -3,8 +3,11 @@ namespace SIQuester.Services.Host; +/// <inheritdoc /> internal sealed class ClipboardService : IClipboardService { + public bool ContainsData(string format) => Clipboard.ContainsData(format); + public object GetData(string format) => Clipboard.GetData(format); public void SetData(string format, object data) => Clipboard.SetData(format, data); diff --git a/src/SIQuester/SIQuester/View/SpardEditorView.xaml b/src/SIQuester/SIQuester/View/SpardEditorView.xaml index 954eff41..cdf2dacb 100644 --- a/src/SIQuester/SIQuester/View/SpardEditorView.xaml +++ b/src/SIQuester/SIQuester/View/SpardEditorView.xaml @@ -5,6 +5,9 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:lp="clr-namespace:SIQuester.Properties" + xmlns:lvm="clr-namespace:SIQuester.ViewModel;assembly=SIQuester.ViewModel" + d:DataContext="{d:DesignInstance lvm:SpardTemplateViewModel}" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> @@ -15,8 +18,8 @@ <StackPanel Orientation="Vertical"/> </ItemsPanelTemplate> </Menu.ItemsPanel> - - <MenuItem Header="<>" ItemsSource="{Binding Aliases}" ToolTip="Объект" FontSize="13" Height="25"> + + <MenuItem Header="<>" ItemsSource="{Binding Aliases}" ToolTip="{x:Static lp:Resources.Object}" FontSize="13" Height="25"> <MenuItem.ItemContainerStyle> <Style TargetType="MenuItem"> <Setter Property="Command" Value="{Binding Path=DataContext.InsertAlias, RelativeSource={RelativeSource FindAncestor, AncestorType=Menu, AncestorLevel=1}}" /> @@ -30,24 +33,24 @@ </DataTemplate> </MenuItem.ItemTemplate> </MenuItem> - - <MenuItem Header="?" Command="{Binding InsertOptional}" ToolTip="Необязательный фрагмент" Height="25"> + + <MenuItem Header="?" Command="{Binding InsertOptional}" ToolTip="{x:Static lp:Resources.OptionalFragment}" Height="25"> <MenuItem.HeaderTemplate> <DataTemplate> <TextBlock Text="{Binding}" FontSize="18" Margin="5,0,0,0" HorizontalAlignment="Stretch" /> </DataTemplate> </MenuItem.HeaderTemplate> </MenuItem> - - <MenuItem Command="{Binding Cut}" ToolTip="Вырезать шаблон" Height="25"> + + <MenuItem Command="{Binding Cut}" ToolTip="{x:Static lp:Resources.CutTemplate}" Height="25"> <MenuItem.HeaderTemplate> <DataTemplate> <Path Stretch="Uniform" Fill="#FF555555" Width="18" Margin="2" Data="{Binding Source={StaticResource app_cut},Path=Data}" /> </DataTemplate> </MenuItem.HeaderTemplate> </MenuItem> - - <MenuItem Command="{Binding Copy}" ToolTip="Копировать шаблон" Height="25"> + + <MenuItem Command="{Binding Copy}" ToolTip="{x:Static lp:Resources.CopyTemplate}" Height="25"> <MenuItem.HeaderTemplate> <DataTemplate> <Path @@ -59,8 +62,8 @@ </DataTemplate> </MenuItem.HeaderTemplate> </MenuItem> - - <MenuItem Command="{Binding Paste}" ToolTip="Вставить шаблон" Height="25"> + + <MenuItem Command="{Binding Paste}" ToolTip="{x:Static lp:Resources.PasteTemplate}" Height="25"> <MenuItem.HeaderTemplate> <DataTemplate> <Path diff --git a/src/SIQuester/SIQuester/appsettings.json b/src/SIQuester/SIQuester/appsettings.json index 1aa90e24..c146bf6f 100644 --- a/src/SIQuester/SIQuester/appsettings.json +++ b/src/SIQuester/SIQuester/appsettings.json @@ -6,7 +6,7 @@ "ServiceUri": "https://vladimirkhil.com/sistorage/" }, "SIQuester": { - "UpgradeNewPackages": false, + "UpgradeNewPackages": true, "UpgradeOpenedPackages": false, "SelectAnswerTypeEnabled": false },