diff --git a/src/HexManiac.Core/HexManiac.Core.csproj b/src/HexManiac.Core/HexManiac.Core.csproj
index e2facafe..305a9e33 100644
--- a/src/HexManiac.Core/HexManiac.Core.csproj
+++ b/src/HexManiac.Core/HexManiac.Core.csproj
@@ -107,6 +107,7 @@
+
diff --git a/src/HexManiac.Core/ViewModels/EditorViewModel.cs b/src/HexManiac.Core/ViewModels/EditorViewModel.cs
index f6a29417..174fda23 100644
--- a/src/HexManiac.Core/ViewModels/EditorViewModel.cs
+++ b/src/HexManiac.Core/ViewModels/EditorViewModel.cs
@@ -62,6 +62,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
resetTheme = new StubCommand(),
clearError = new StubCommand(),
clearMessage = new StubCommand(),
+ displayAsText = new StubCommand(),
+ displayAsEventScript = new StubCommand(),
+ displayAsSprite = new StubCommand(),
+ displayAsColorPalette = new StubCommand(),
toggleMatrix = new StubCommand(),
toggleScrollAnimation = new StubCommand(),
toggleTableHeaders = new StubCommand();
@@ -104,6 +108,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
public ICommand ResetTheme => resetTheme;
public ICommand ClearError => clearError;
public ICommand ClearMessage => clearMessage;
+ public ICommand DisplayAsText => displayAsText;
+ public ICommand DisplayAsEventScript => displayAsEventScript;
+ public ICommand DisplayAsSprite => displayAsSprite;
+ public ICommand DisplayAsColorPalette => displayAsColorPalette;
public ICommand ToggleMatrix => toggleMatrix;
public ICommand ToggleScrollAnimation => toggleScrollAnimation;
public ICommand ToggleTableHeaders => toggleTableHeaders;
@@ -423,6 +431,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
back = CreateWrapperForSelected(tab => tab.Back);
forward = CreateWrapperForSelected(tab => tab.Forward);
resetAlignment = CreateWrapperForSelected(tab => tab.ResetAlignment);
+ displayAsText = CreateWrapperForSelected(tab => (tab as ViewPort)?.Shortcuts.DisplayAsText);
+ displayAsEventScript = CreateWrapperForSelected(tab => (tab as ViewPort)?.Shortcuts.DisplayAsEventScript);
+ displayAsSprite = CreateWrapperForSelected(tab => (tab as ViewPort)?.Shortcuts.DisplayAsSprite);
+ displayAsColorPalette = CreateWrapperForSelected(tab => (tab as ViewPort)?.Shortcuts.DisplayAsColorPalette);
saveAll = CreateWrapperForAll(tab => tab.Save);
closeAll = CreateWrapperForAll(tab => tab.Close);
@@ -442,6 +454,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
{ tab => tab.Back, (sender, e) => back.CanExecuteChanged.Invoke(this, e) },
{ tab => tab.Forward, (sender, e) => forward.CanExecuteChanged.Invoke(this, e) },
{ tab => tab.ResetAlignment, (sender, e) => resetAlignment.CanExecuteChanged.Invoke(this, e) },
+ { tab => (tab as ViewPort)?.Shortcuts.DisplayAsText, (sender, e) => displayAsText.RaiseCanExecuteChanged() },
+ { tab => (tab as ViewPort)?.Shortcuts.DisplayAsEventScript, (sender, e) => displayAsEventScript.RaiseCanExecuteChanged() },
+ { tab => (tab as ViewPort)?.Shortcuts.DisplayAsSprite, (sender, e) => displayAsSprite.RaiseCanExecuteChanged() },
+ { tab => (tab as ViewPort)?.Shortcuts.DisplayAsColorPalette, (sender, e) => displayAsColorPalette.RaiseCanExecuteChanged() },
};
var metadata = fileSystem.MetadataFor(ApplicationName) ?? new string[0];
diff --git a/src/HexManiac.Core/ViewModels/Shortcuts.cs b/src/HexManiac.Core/ViewModels/Shortcuts.cs
new file mode 100644
index 00000000..56b12909
--- /dev/null
+++ b/src/HexManiac.Core/ViewModels/Shortcuts.cs
@@ -0,0 +1,42 @@
+using HavenSoft.HexManiac.Core.Models.Runs;
+using System.Windows.Input;
+using static HavenSoft.HexManiac.Core.ICommandExtensions;
+
+namespace HavenSoft.HexManiac.Core.ViewModels {
+ public class Shortcuts : ViewModelCore {
+ private StubCommand displayAsEventScript, displayAsText, displayAsSprite, displayAsColorPalette;
+
+ public ViewPort ViewPort { get; }
+
+ public ICommand DisplayAsEventScript => StubCommand(ref displayAsEventScript, ExecuteDisplayAsEventScript, CanExecuteDisplayAs);
+ public ICommand DisplayAsText=> StubCommand(ref displayAsText, ExecuteDisplayAsText, CanExecuteDisplayAs);
+ public ICommand DisplayAsSprite=> StubCommand(ref displayAsSprite, ExecuteDisplayAsSprite, CanExecuteDisplayAs);
+ public ICommand DisplayAsColorPalette=> StubCommand(ref displayAsColorPalette, ExecuteDisplayAsColorPalette, CanExecuteDisplayAs);
+
+ public Shortcuts(ViewPort viewPort) => ViewPort = viewPort;
+
+ private bool CanExecuteDisplayAs() {
+ var spot = ViewPort.ConvertViewPointToAddress(ViewPort.SelectionStart);
+ var nextRun = ViewPort.Model.GetNextRun(spot);
+ return nextRun.Start > spot || nextRun is NoInfoRun;
+ }
+
+ private void ExecuteDisplayAsEventScript() {
+ ViewPort.Tools.CodeTool.IsEventScript.Execute();
+ ViewPort.Refresh();
+ ViewPort.UpdateToolsFromSelection(ViewPort.ConvertViewPointToAddress(ViewPort.SelectionStart));
+ }
+
+ private void ExecuteDisplayAsText() {
+ ViewPort.IsText.Execute();
+ }
+
+ private void ExecuteDisplayAsSprite() {
+ ViewPort.Tools.SpriteTool.IsSprite.Execute();
+ }
+
+ private void ExecuteDisplayAsColorPalette() {
+ ViewPort.Tools.SpriteTool.IsPalette.Execute();
+ }
+ }
+}
diff --git a/src/HexManiac.Core/ViewModels/ViewPort.cs b/src/HexManiac.Core/ViewModels/ViewPort.cs
index ce9d47b5..e5e6105d 100644
--- a/src/HexManiac.Core/ViewModels/ViewPort.cs
+++ b/src/HexManiac.Core/ViewModels/ViewPort.cs
@@ -675,6 +675,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
public event EventHandler RequestMenuClose;
#pragma warning restore 0067
+ public Shortcuts Shortcuts { get; }
+
#region Constructors
public ViewPort() : this(new LoadedFile(string.Empty, new byte[0])) { }
@@ -712,6 +714,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
ImplementCommands();
if (changeHistory == null) CascadeScripts(); // if we're sharing history with another viewmodel, our model has already been updated like this.
RefreshBackingData();
+ Shortcuts = new Shortcuts(this);
}
public ViewPort(LoadedFile file) : this(file.Name, new BasicModel(file.Contents), InstantDispatch.Instance) { }
diff --git a/src/HexManiac.Core/ViewModels/Visitors/ContextItemFactory.cs b/src/HexManiac.Core/ViewModels/Visitors/ContextItemFactory.cs
index 2cd384ad..c86eeae9 100644
--- a/src/HexManiac.Core/ViewModels/Visitors/ContextItemFactory.cs
+++ b/src/HexManiac.Core/ViewModels/Visitors/ContextItemFactory.cs
@@ -68,14 +68,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
public void Visit(None dataFormat, byte data) {
Results.Add(new ContextItemGroup("Display As...") {
- new ContextItem("Text", ViewPort.IsText.Execute),
- new ContextItem("Sprite", ViewPort.Tools.SpriteTool.IsSprite.Execute),
- new ContextItem("Palette", ViewPort.Tools.SpriteTool.IsPalette.Execute),
- new ContextItem("Event Script", arg => {
- ViewPort.Tools.CodeTool.IsEventScript.Execute();
- ViewPort.Refresh();
- ViewPort.UpdateToolsFromSelection(ViewPort.ConvertViewPointToAddress(ViewPort.SelectionStart));
- }),
+ new ContextItem("Text", ViewPort.Shortcuts.DisplayAsText.Execute) { ShortcutText = "Ctrl+D, T" },
+ new ContextItem("Sprite", ViewPort.Shortcuts.DisplayAsSprite.Execute) { ShortcutText = "Ctrl+D, S" },
+ new ContextItem("Color Palette", ViewPort.Shortcuts.DisplayAsColorPalette.Execute) { ShortcutText = "Ctrl+D, C" },
+ new ContextItem("Event Script", ViewPort.Shortcuts.DisplayAsEventScript.Execute) { ShortcutText = "Ctrl+D, E" },
});
if (data == 0xFF && ViewPort.SelectionEnd == ViewPort.SelectionStart && ViewPort[ViewPort.SelectionStart].Format is None) Results.Add(new ContextItemGroup("Create New...") {
diff --git a/src/HexManiac.Tests/ImageTests.cs b/src/HexManiac.Tests/ImageTests.cs
index f4fef0fe..a20700a9 100644
--- a/src/HexManiac.Tests/ImageTests.cs
+++ b/src/HexManiac.Tests/ImageTests.cs
@@ -363,7 +363,7 @@ namespace HavenSoft.HexManiac.Tests {
ViewPort.Edit("^bob @20 ^tom @bob ");
var items = ViewPort.GetContextMenuItems(new Point(0, 0));
var item = items.Single(element => element.Text == "Display As...");
- item = ((ContextItemGroup)item).Single(element => element.Text == "Palette");
+ item = ((ContextItemGroup)item).Single(element => element.Text == "Color Palette");
item.Command.Execute();
var anchor = Model.GetAnchorFromAddress(-1, 0);
@@ -485,7 +485,7 @@ namespace HavenSoft.HexManiac.Tests {
var items = ViewPort.GetContextMenuItems(new Point(0, 0));
var item = items.Single(element => element.Text == "Display As...");
- item = ((ContextItemGroup)item).Single(element => element.Text == "Palette");
+ item = ((ContextItemGroup)item).Single(element => element.Text == "Color Palette");
var makePalette = item.Command;
makePalette.Execute();
@@ -498,7 +498,7 @@ namespace HavenSoft.HexManiac.Tests {
var items = ViewPort.GetContextMenuItems(new Point(0, 0));
var item = items.Single(element => element.Text == "Display As...");
- item = ((ContextItemGroup)item).Single(element => element.Text == "Palette");
+ item = ((ContextItemGroup)item).Single(element => element.Text == "Color Palette");
var makePalette = item.Command;
makePalette.Execute();
@@ -654,7 +654,7 @@ namespace HavenSoft.HexManiac.Tests {
ViewPort.SelectionStart = new Point(1, 0);
var group = (ContextItemGroup)ViewPort.GetContextMenuItems(new Point(1, 0)).Single(item => item.Text == "Display As...");
- var contextItem = group.Single(item => item.Text == "Palette");
+ var contextItem = group.Single(item => item.Text == "Color Palette");
contextItem.Command.Execute();
Assert.Equal(ViewPort.Tools.SpriteTool, ViewPort.Tools.SelectedTool);
diff --git a/src/HexManiac.WPF/HexManiac.WPF.csproj b/src/HexManiac.WPF/HexManiac.WPF.csproj
index e66a18b6..35fcd233 100644
--- a/src/HexManiac.WPF/HexManiac.WPF.csproj
+++ b/src/HexManiac.WPF/HexManiac.WPF.csproj
@@ -91,6 +91,7 @@
TextBoxLookAlike.xaml
+
OptionDialog.xaml
diff --git a/src/HexManiac.WPF/Resources/MultiKeyGesture.cs b/src/HexManiac.WPF/Resources/MultiKeyGesture.cs
new file mode 100644
index 00000000..7f60eff4
--- /dev/null
+++ b/src/HexManiac.WPF/Resources/MultiKeyGesture.cs
@@ -0,0 +1,65 @@
+using HavenSoft.HexManiac.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Input;
+using System.Windows.Markup;
+
+namespace HavenSoft.HexManiac.WPF.Resources {
+ public class MultiKeyGesture : KeyGesture {
+
+ private readonly ModifierKeys mods;
+ private readonly IReadOnlyList keys;
+ private int nextKey;
+
+ public MultiKeyGesture(ModifierKeys mods, params Key[] keys) : base(keys[0], mods) {
+ this.mods = mods;
+ this.keys = keys;
+ }
+
+ public override bool Matches(object targetElement, InputEventArgs inputEventArgs) {
+ if (!(inputEventArgs is KeyEventArgs keyArgs)) return false;
+ if (keyArgs.IsRepeat) return false;
+
+ if (nextKey == 0) {
+ if (new KeyGesture(keys[0], mods).Matches(targetElement, keyArgs)) {
+ nextKey += 1;
+ } else {
+ nextKey = 0;
+ }
+ return false;
+ } else if (nextKey == keys.Count - 1) {
+ nextKey = 0;
+ return keyArgs.Key == keys[keys.Count - 1];
+ } else {
+ nextKey = keyArgs.Key == keys[nextKey] ? nextKey + 1 : 0;
+ return false;
+ }
+ }
+ }
+
+ public class MultiKeyGestureExtension : MarkupExtension {
+ private static readonly KeyConverter keyConverter = new KeyConverter();
+ private static readonly ModifierKeysConverter modifierConverter = new ModifierKeysConverter();
+ private readonly ModifierKeys mods;
+ private readonly Key[] keys;
+
+ public MultiKeyGestureExtension(string first, string second) : this(new[] { first, second }) { }
+ public MultiKeyGestureExtension(params string[] textPieces) {
+ var text = ", ".Join(textPieces);
+ if (text.Contains("+")) {
+ var parts = text.Split('+');
+ text = parts[1];
+ mods = (ModifierKeys)modifierConverter.ConvertFromString(parts[0]);
+ } else {
+ mods = ModifierKeys.None;
+ }
+
+ keys = text.Split(',').Select(k => (Key)keyConverter.ConvertFromString(k.Trim())).ToArray();
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider) {
+ return new MultiKeyGesture(mods, keys);
+ }
+ }
+}
diff --git a/src/HexManiac.WPF/Windows/MainWindow.xaml b/src/HexManiac.WPF/Windows/MainWindow.xaml
index 3fd9a158..87ad3daa 100644
--- a/src/HexManiac.WPF/Windows/MainWindow.xaml
+++ b/src/HexManiac.WPF/Windows/MainWindow.xaml
@@ -12,6 +12,11 @@
+
+
+
+
+
@@ -29,7 +34,7 @@
-
+
@@ -112,6 +117,12 @@
+
@@ -141,7 +152,7 @@
-
+