diff --git a/src/HexManiac.Core/ViewModels/ChildViewPort.cs b/src/HexManiac.Core/ViewModels/ChildViewPort.cs index 660862c8..47ad639f 100644 --- a/src/HexManiac.Core/ViewModels/ChildViewPort.cs +++ b/src/HexManiac.Core/ViewModels/ChildViewPort.cs @@ -137,6 +137,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels { public ICommand Close => this[0].Close; ICommand ITabContent.Clear => this[0].Clear; + public bool CanDuplicate => false; + public void Duplicate() { } public event EventHandler PreviewScrollChanged { add => ForEach(child => child.PreviewScrollChanged += value); remove => ForEach(child => child.PreviewScrollChanged -= value); } public event EventHandler OnError { add => ForEach(child => child.OnError += value); remove => ForEach(child => child.OnError -= value); } diff --git a/src/HexManiac.Core/ViewModels/DexReorderTab.cs b/src/HexManiac.Core/ViewModels/DexReorderTab.cs index c223dabb..6164dedd 100644 --- a/src/HexManiac.Core/ViewModels/DexReorderTab.cs +++ b/src/HexManiac.Core/ViewModels/DexReorderTab.cs @@ -49,6 +49,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels { public ICommand Back { get; } = new StubCommand(); public ICommand Forward { get; } = new StubCommand(); public ICommand Close { get; } + public bool CanDuplicate => false; + public void Duplicate() { } public event EventHandler OnError; public event EventHandler Closed; diff --git a/src/HexManiac.Core/ViewModels/DiffViewPort.cs b/src/HexManiac.Core/ViewModels/DiffViewPort.cs index ea464e54..1ff1bc04 100644 --- a/src/HexManiac.Core/ViewModels/DiffViewPort.cs +++ b/src/HexManiac.Core/ViewModels/DiffViewPort.cs @@ -118,6 +118,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels { public ICommand Diff => null; public ICommand DiffLeft => null; public ICommand DiffRight => null; + public bool CanDuplicate => false; + public void Duplicate() { } public event EventHandler PreviewScrollChanged; public event EventHandler OnError; diff --git a/src/HexManiac.Core/ViewModels/EditorViewModel.cs b/src/HexManiac.Core/ViewModels/EditorViewModel.cs index f5b5b51e..c5e5ed83 100644 --- a/src/HexManiac.Core/ViewModels/EditorViewModel.cs +++ b/src/HexManiac.Core/ViewModels/EditorViewModel.cs @@ -31,6 +31,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { private readonly StubCommand newCommand = new StubCommand(), open = new StubCommand(), + duplicateCurrentTab = new StubCommand(), save = new StubCommand(), saveAs = new StubCommand(), exportBackup = new StubCommand(), @@ -79,6 +80,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { public ICommand New => newCommand; public ICommand Open => open; // parameter: file to open (or null) + public ICommand DuplicateCurrentTab => duplicateCurrentTab; public ICommand Save => save; public ICommand SaveAs => saveAs; public ICommand SaveAll => saveAll; @@ -382,6 +384,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { findPrevious.RaiseCanExecuteChanged(); findNext.RaiseCanExecuteChanged(); runFile.RaiseCanExecuteChanged(); + duplicateCurrentTab.RaiseCanExecuteChanged(); if (selectedIndex >= 0 && selectedIndex < tabs.Count) tabs[selectedIndex].Refresh(); UpdateGotoViewModel(); foreach (var edit in QuickEditsPokedex.Concat(QuickEditsExpansion)) edit.TabChanged(); @@ -557,6 +560,12 @@ namespace HavenSoft.HexManiac.Core.ViewModels { } }; + duplicateCurrentTab.CanExecute = arg => SelectedTab?.CanDuplicate ?? false; + duplicateCurrentTab.Execute = arg => { + SelectedTab?.Duplicate(); + gotoViewModel.ControlVisible = true; + }; + runFile.CanExecute = arg => SelectedTab is IEditableViewPort viewPort && !viewPort.ChangeHistory.HasDataChange; runFile.Execute = arg => { ((IFileSystem)arg).LaunchProcess(((ViewPort)SelectedTab).FullFileName); diff --git a/src/HexManiac.Core/ViewModels/ITabContent.cs b/src/HexManiac.Core/ViewModels/ITabContent.cs index 6b63ec3c..f6cd3c00 100644 --- a/src/HexManiac.Core/ViewModels/ITabContent.cs +++ b/src/HexManiac.Core/ViewModels/ITabContent.cs @@ -37,6 +37,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels { event EventHandler RequestDiff; event EventHandler RequestCanDiff; + bool CanDuplicate { get; } + void Duplicate(); + void Refresh(); bool TryImport(LoadedFile file, IFileSystem fileSystem); } diff --git a/src/HexManiac.Core/ViewModels/ImageEditorViewModel.cs b/src/HexManiac.Core/ViewModels/ImageEditorViewModel.cs index ee5af5e2..4fa0383d 100644 --- a/src/HexManiac.Core/ViewModels/ImageEditorViewModel.cs +++ b/src/HexManiac.Core/ViewModels/ImageEditorViewModel.cs @@ -52,6 +52,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels { public ICommand Back => null; public ICommand Forward => null; public ICommand Close => StubCommand(ref close, () => Closed?.Invoke(this, EventArgs.Empty)); + public bool CanDuplicate => false; + public void Duplicate() { } public event EventHandler OnError; public event EventHandler OnMessage; public event EventHandler ClearMessage; diff --git a/src/HexManiac.Core/ViewModels/SearchResultsViewPort.cs b/src/HexManiac.Core/ViewModels/SearchResultsViewPort.cs index df62eb12..15972d73 100644 --- a/src/HexManiac.Core/ViewModels/SearchResultsViewPort.cs +++ b/src/HexManiac.Core/ViewModels/SearchResultsViewPort.cs @@ -89,6 +89,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels { public ICommand Back { get; } = new StubCommand(); public ICommand Forward { get; } = new StubCommand(); public ICommand Close => close; + public bool CanDuplicate => false; + public void Duplicate() { } public bool UpdateInProgress => false; public double Progress => 0; diff --git a/src/HexManiac.Core/ViewModels/ViewPort.cs b/src/HexManiac.Core/ViewModels/ViewPort.cs index 9fcdcff7..4a265586 100644 --- a/src/HexManiac.Core/ViewModels/ViewPort.cs +++ b/src/HexManiac.Core/ViewModels/ViewPort.cs @@ -626,6 +626,13 @@ namespace HavenSoft.HexManiac.Core.ViewModels { #endregion + #region Duplicate + + public bool CanDuplicate => true; + public void Duplicate() => OpenInNewTab(scroll.DataIndex); + + #endregion + public int FreeSpaceStart { get => Model.FreeSpaceStart; set { if (Model.FreeSpaceStart != value) { Model.FreeSpaceStart = value; diff --git a/src/HexManiac.Tests/GeneralAppTests.cs b/src/HexManiac.Tests/GeneralAppTests.cs index 001c1f48..4461afac 100644 --- a/src/HexManiac.Tests/GeneralAppTests.cs +++ b/src/HexManiac.Tests/GeneralAppTests.cs @@ -544,6 +544,30 @@ ApplicationVersion = '''0.1.0''' Assert.False(editor.RunFile.CanExecute(default)); } + [Fact] + public void Tab_Duplicate_Duplicated() { + var tab = new StubTabContent { CanDuplicate = true }; + tab.Duplicate = () => tab.RequestTabChange.Invoke(tab, new StubTabContent()); + + editor.Add(tab); + editor.DuplicateCurrentTab.Execute(); + + Assert.Equal(2, editor.Count); + Assert.True(editor.GotoViewModel.ControlVisible); + } + + [Fact] + public void TabWithoutDuplicate_SwitchToTabWithDuplicate_Notify() { + editor.Add(new StubTabContent { CanDuplicate = false }); + editor.Add(new StubTabContent { CanDuplicate = true }); + editor.SelectedIndex = 0; + var view = new StubView(editor); + + editor.SelectedIndex = 1; + + Assert.Contains(nameof(editor.DuplicateCurrentTab), view.CommandCanExecuteChangedNotifications); + } + private StubTabContent CreateClosableTab() { var tab = new StubTabContent(); var close = new StubCommand { CanExecute = arg => true }; diff --git a/src/HexManiac.WPF/Resources/MarkupExtensions.cs b/src/HexManiac.WPF/Resources/MarkupExtensions.cs index 530347ce..bfb92881 100644 --- a/src/HexManiac.WPF/Resources/MarkupExtensions.cs +++ b/src/HexManiac.WPF/Resources/MarkupExtensions.cs @@ -80,6 +80,9 @@ namespace HavenSoft.HexManiac.WPF.Resources { public override object ProvideValue(IServiceProvider serviceProvider) { var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; if (valueProvider == null) return null; + if (valueProvider.TargetObject.GetType().FullName == "System.Windows.SharedDp") { + return this; // we're in a template: defer execution + } var element = (FrameworkElement)valueProvider.TargetObject; var context = element.DataContext; var command = new MethodCommand(context, CommandMethod); diff --git a/src/HexManiac.WPF/Windows/MainWindow.xaml b/src/HexManiac.WPF/Windows/MainWindow.xaml index 6af061c1..e50f36ae 100644 --- a/src/HexManiac.WPF/Windows/MainWindow.xaml +++ b/src/HexManiac.WPF/Windows/MainWindow.xaml @@ -19,6 +19,7 @@ + @@ -86,6 +87,7 @@ + @@ -473,6 +475,7 @@ +