diff --git a/NAPS2.Lib.Mac/EtoForms/Mac/MacEtoPlatform.cs b/NAPS2.Lib.Mac/EtoForms/Mac/MacEtoPlatform.cs index c6837ccc8..421a2349a 100644 --- a/NAPS2.Lib.Mac/EtoForms/Mac/MacEtoPlatform.cs +++ b/NAPS2.Lib.Mac/EtoForms/Mac/MacEtoPlatform.cs @@ -143,6 +143,22 @@ public class MacEtoPlatform : EtoPlatform } } + public override void HandleKeyDown(Control control, Func handle) + { + var view = control.ToNative(); + var monitor = NSEvent.AddLocalMonitorForEventsMatchingMask(NSEventMask.KeyDown, evt => + { + if (ReferenceEquals(evt.Window, view.Window) && + view.HitTest(evt.LocationInWindow) != null!) + { + var args = evt.ToEtoKeyEventArgs(); + return handle(args.KeyData) ? null! : evt; + } + return evt; + }); + control.UnLoad += (_, _) => NSEvent.RemoveMonitor(monitor); + } + public override void AttachMouseWheelEvent(Control control, EventHandler eventHandler) { var view = control.ToNative(); diff --git a/NAPS2.Lib.Mac/EtoForms/Ui/MacDesktopForm.cs b/NAPS2.Lib.Mac/EtoForms/Ui/MacDesktopForm.cs index 60e683c7a..a02776043 100644 --- a/NAPS2.Lib.Mac/EtoForms/Ui/MacDesktopForm.cs +++ b/NAPS2.Lib.Mac/EtoForms/Ui/MacDesktopForm.cs @@ -50,10 +50,6 @@ public class MacDesktopForm : DesktopForm { Commands.MoveDown.ToolBarText = ""; Commands.MoveUp.ToolBarText = ""; - Commands.SaveAllPdf.Shortcut = Application.Instance.CommonModifier | Keys.S; - Commands.SaveSelectedPdf.Shortcut = Application.Instance.CommonModifier | Keys.Shift | Keys.S; - Commands.SaveAllImages.Shortcut = Application.Instance.CommonModifier | Keys.M; - Commands.SaveSelectedImages.Shortcut = Application.Instance.CommonModifier | Keys.Shift | Keys.M; Menu = new MenuBar { diff --git a/NAPS2.Lib.Mac/EtoForms/Ui/MacPreviewForm.cs b/NAPS2.Lib.Mac/EtoForms/Ui/MacPreviewForm.cs index 34dd374cd..e0405ab4d 100644 --- a/NAPS2.Lib.Mac/EtoForms/Ui/MacPreviewForm.cs +++ b/NAPS2.Lib.Mac/EtoForms/Ui/MacPreviewForm.cs @@ -9,8 +9,8 @@ public class MacPreviewForm : PreviewForm private readonly NSSlider _zoomSlider; public MacPreviewForm(Naps2Config config, DesktopCommands desktopCommands, UiImageList imageList, - IIconProvider iconProvider, KeyboardShortcutManager ksm, ColorScheme colorScheme) : base(config, - desktopCommands, imageList, iconProvider, ksm, colorScheme) + IIconProvider iconProvider, ColorScheme colorScheme) : base(config, + desktopCommands, imageList, iconProvider, colorScheme) { _zoomSlider = new NSSlider { diff --git a/NAPS2.Lib.Tests/Config/ConfigData.resx b/NAPS2.Lib.Tests/Config/ConfigData.resx index 205b5b818..c4389201a 100644 --- a/NAPS2.Lib.Tests/Config/ConfigData.resx +++ b/NAPS2.Lib.Tests/Config/ConfigData.resx @@ -277,6 +277,9 @@ <OcrDefaultAfterScanning>true</OcrDefaultAfterScanning> <ForcePdfCompat>PdfA1B</ForcePdfCompat> <EventLogging>None</EventLogging> + <KeyboardShortcuts> + <ScanDefault>Ctrl+Enter</ScanDefault> + </KeyboardShortcuts> </AppConfig> diff --git a/NAPS2.Lib.Tests/Config/FileConfigScopeTests.cs b/NAPS2.Lib.Tests/Config/FileConfigScopeTests.cs index 00dd8c8fe..9d9987a7a 100644 --- a/NAPS2.Lib.Tests/Config/FileConfigScopeTests.cs +++ b/NAPS2.Lib.Tests/Config/FileConfigScopeTests.cs @@ -127,6 +127,10 @@ public class FileConfigScopeTests : ContextualTests Assert.False(defaultsScope.Has(c => c.PdfSettings.Compat)); Assert.True(defaultsScope.TryGet(c => c.AlwaysRememberDevice, out var alwaysRememberDevice)); Assert.True(alwaysRememberDevice); + + Assert.False(lockedScope.Has(c => c.KeyboardShortcuts.ScanDefault)); + Assert.True(defaultsScope.TryGet(c => c.KeyboardShortcuts.ScanDefault, out var scanDefault)); + Assert.Equal("Mod+Enter", scanDefault); } [Fact] diff --git a/NAPS2.Lib.WinForms/EtoForms/WinForms/WinFormsEtoPlatform.cs b/NAPS2.Lib.WinForms/EtoForms/WinForms/WinFormsEtoPlatform.cs index 91d5e93c7..b78d707bb 100644 --- a/NAPS2.Lib.WinForms/EtoForms/WinForms/WinFormsEtoPlatform.cs +++ b/NAPS2.Lib.WinForms/EtoForms/WinForms/WinFormsEtoPlatform.cs @@ -268,6 +268,11 @@ public class WinFormsEtoPlatform : EtoPlatform return SD.Icon.ExtractAssociatedIcon(exePath)?.ToBitmap().ToEto(); } + public override void HandleKeyDown(Control control, Func handle) + { + control.ToNative().KeyDown += (_, args) => args.Handled = handle(args.KeyData.ToEto()); + } + public override void AttachMouseWheelEvent(Control control, EventHandler eventHandler) { if (control is Scrollable scrollable) diff --git a/NAPS2.Lib/Config/ConfigSerializer.cs b/NAPS2.Lib/Config/ConfigSerializer.cs index ecc14fc23..a6a8c844d 100644 --- a/NAPS2.Lib/Config/ConfigSerializer.cs +++ b/NAPS2.Lib/Config/ConfigSerializer.cs @@ -124,7 +124,7 @@ public class ConfigSerializer : VersionedSerializer> storage.Set(x => x.EsclSecurityPolicy, c.EsclSecurityPolicy); storage.Set(x => x.EsclServerCertificatePath, c.EsclServerCertificatePath); storage.Set(x => x.EventLogging, c.EventLogging); - storage.Set(x => x.KeyboardShortcuts, c.KeyboardShortcuts ?? new KeyboardShortcuts()); + storage.Set(x => x.KeyboardShortcuts, MapKeyboardShortcuts(c.KeyboardShortcuts ?? new KeyboardShortcuts())); return storage; } @@ -191,6 +191,18 @@ public class ConfigSerializer : VersionedSerializer> return flags; } + private KeyboardShortcuts MapKeyboardShortcuts(KeyboardShortcuts keyboardShortcuts) + { + // To be platform-independent, replace "Ctrl+" with "Mod+", which means "Ctrl" on Window/Linux and "Cmd" on Mac + var serializer = new XmlSerializer(); + var doc = serializer.SerializeToXDocument(keyboardShortcuts); + foreach (var node in doc.Root!.Elements()) + { + node.Value = node.Value.Replace("Ctrl+", "Mod+"); + } + return serializer.DeserializeFromXDocument(doc)!; + } + private static ConfigStorage UserConfigV0ToCommonConfig(UserConfigV0 c) { var storage = new ConfigStorage(); diff --git a/NAPS2.Lib/Config/InternalDefaults.cs b/NAPS2.Lib/Config/InternalDefaults.cs index 0911470d3..3859b421b 100644 --- a/NAPS2.Lib/Config/InternalDefaults.cs +++ b/NAPS2.Lib/Config/InternalDefaults.cs @@ -129,7 +129,7 @@ public static class InternalDefaults }, KeyboardShortcuts = new KeyboardShortcuts { - ScanDefault = "Ctrl+Enter", + ScanDefault = "Mod+Enter", ScanProfile1 = "F2", ScanProfile2 = "F3", ScanProfile3 = "F4", @@ -142,21 +142,25 @@ public static class InternalDefaults ScanProfile10 = "F11", ScanProfile11 = "F12", ScanProfile12 = "", - NewProfile = "", - BatchScan = "Ctrl+B", - Profiles = "", - Ocr = "", - Import = "Ctrl+O", - SavePDF = "Ctrl+S", - SavePDFAll = "", - SavePDFSelected = "", + NewProfile = "Mod+N", + BatchScan = "Mod+B", + Profiles = "Mod+L", + ScannerSharing = "Mod+G", + Ocr = "Mod+Alt+O", + Import = "Mod+O", + SavePDF = "", + SavePDFAll = "Mod+S", + SavePDFSelected = "Mod+Shift+S", + PDFSettings = "Mod+Alt+P", SaveImages = "", - SaveImagesAll = "", - SaveImagesSelected = "", + SaveImagesAll = "Mod+I", + SaveImagesSelected = "Mod+Shift+I", + ImageSettings = "Mod+Alt+I", EmailPDF = "", - EmailPDFAll = "", - EmailPDFSelected = "", - Print = "Ctrl+P", + EmailPDFAll = "Mod+E", + EmailPDFSelected = "Mod+Shift+E", + EmailSettings = "Mod+Alt+E", + Print = "Mod+P", ImageView = "", ImageBlackWhite = "", ImageBrightness = "", @@ -166,12 +170,12 @@ public static class InternalDefaults ImageReset = "", ImageSaturation = "", ImageSharpen = "", - RotateLeft = "", - RotateRight = "", - RotateFlip = "", + RotateLeft = "Mod+Shift+Left", + RotateRight = "Mod+Shift+Right", + RotateFlip = "Mod+Shift+Down", RotateCustom = "", - MoveUp = "Ctrl+Up", - MoveDown = "Ctrl+Down", + MoveUp = "Mod+Up", + MoveDown = "Mod+Down", ReorderInterleave = "", ReorderDeinterleave = "", ReorderAltInterleave = "", @@ -179,10 +183,10 @@ public static class InternalDefaults ReorderReverseAll = "", ReorderReverseSelected = "", Delete = "", - Clear = "Ctrl+Shift+Del", + Clear = "Mod+Shift+Del", About = "F1", - ZoomIn = "Ctrl+Oemplus", - ZoomOut = "Ctrl+OemMinus" + ZoomIn = "Mod+Oemplus", + ZoomOut = "Mod+OemMinus" }, DefaultProfileSettings = new ScanProfile { Version = ScanProfile.CURRENT_VERSION } }; diff --git a/NAPS2.Lib/Config/KeyboardShortcuts.cs b/NAPS2.Lib/Config/KeyboardShortcuts.cs index 21a306ba2..5c9f6291a 100644 --- a/NAPS2.Lib/Config/KeyboardShortcuts.cs +++ b/NAPS2.Lib/Config/KeyboardShortcuts.cs @@ -19,22 +19,24 @@ public class KeyboardShortcuts public string? BatchScan { get; set; } public string? Profiles { get; set; } - + public string? ScannerSharing { get; set; } public string? Ocr { get; set; } - public string? Import { get; set; } public string? SavePDF { get; set; } public string? SavePDFAll { get; set; } public string? SavePDFSelected { get; set; } + public string? PDFSettings { get; set; } public string? SaveImages { get; set; } public string? SaveImagesAll { get; set; } public string? SaveImagesSelected { get; set; } + public string? ImageSettings { get; set; } public string? EmailPDF { get; set; } public string? EmailPDFAll { get; set; } public string? EmailPDFSelected { get; set; } + public string? EmailSettings { get; set; } public string? Print { get; set; } diff --git a/NAPS2.Lib/EtoForms/Desktop/DesktopKeyboardShortcuts.cs b/NAPS2.Lib/EtoForms/Desktop/DesktopKeyboardShortcuts.cs index 9753232db..a67cb1123 100644 --- a/NAPS2.Lib/EtoForms/Desktop/DesktopKeyboardShortcuts.cs +++ b/NAPS2.Lib/EtoForms/Desktop/DesktopKeyboardShortcuts.cs @@ -16,28 +16,20 @@ public class DesktopKeyboardShortcuts public void Assign(DesktopCommands commands) { - // Defaults - _ksm.Assign("Ctrl+Enter", commands.Scan); - _ksm.Assign("Ctrl+B", commands.BatchScan); - _ksm.Assign("Ctrl+O", commands.Import); - _ksm.Assign("Ctrl+S", commands.SavePdf); - _ksm.Assign("Ctrl+P", commands.Print); - _ksm.Assign("Ctrl+Up", commands.MoveUp); - _ksm.Assign("Ctrl+Left", commands.MoveUp); - _ksm.Assign("Ctrl+Down", commands.MoveDown); - _ksm.Assign("Ctrl+Right", commands.MoveDown); - _ksm.Assign("Ctrl+Shift+Del", commands.ClearAll); - _ksm.Assign("F1", commands.About); - _ksm.Assign("Ctrl+OemMinus", commands.ZoomOut); - _ksm.Assign("Ctrl+Oemplus", commands.ZoomIn); + // Unconfigurable defaults + _ksm.Assign("Mod+.", commands.Scan); + _ksm.Assign("Mod+Up", commands.MoveUp); + _ksm.Assign("Mod+Left", commands.MoveUp); + _ksm.Assign("Mod+Down", commands.MoveDown); + _ksm.Assign("Mod+Right", commands.MoveDown); _ksm.Assign("Del", commands.Delete); - _ksm.Assign("Ctrl+A", commands.SelectAll); - _ksm.Assign("Ctrl+C", commands.Copy); - _ksm.Assign("Ctrl+V", commands.Paste); - _ksm.Assign("Ctrl+Z", commands.Undo); - _ksm.Assign(EtoPlatform.Current.IsGtk ? "Ctrl+Shift+Z" : "Ctrl+Y", commands.Redo); + _ksm.Assign("Mod+A", commands.SelectAll); + _ksm.Assign("Mod+C", commands.Copy); + _ksm.Assign("Mod+V", commands.Paste); + _ksm.Assign("Mod+Z", commands.Undo); + _ksm.Assign(EtoPlatform.Current.IsWinForms ? "Mod+Y" : "Mod+Shift+Z", commands.Redo); - // Configured + // Configured defaults var ks = _config.Get(c => c.KeyboardShortcuts); @@ -50,6 +42,7 @@ public class DesktopKeyboardShortcuts _ksm.Assign(ks.EmailPDF, commands.EmailPdf); _ksm.Assign(ks.EmailPDFAll, commands.EmailAll); _ksm.Assign(ks.EmailPDFSelected, commands.EmailSelected); + _ksm.Assign(ks.EmailSettings, commands.EmailSettings); } _ksm.Assign(ks.ImageBlackWhite, commands.BlackWhite); _ksm.Assign(ks.ImageBrightness, commands.BrightCont); @@ -84,13 +77,24 @@ public class DesktopKeyboardShortcuts _ksm.Assign(ks.RotateFlip, commands.Flip); _ksm.Assign(ks.RotateLeft, commands.RotateLeft); _ksm.Assign(ks.RotateRight, commands.RotateRight); - _ksm.Assign(ks.SaveImages, commands.SaveImages); - _ksm.Assign(ks.SaveImagesAll, commands.SaveAllImages); - _ksm.Assign(ks.SaveImagesSelected, commands.SaveSelectedImages); - _ksm.Assign(ks.SavePDF, commands.SavePdf); - _ksm.Assign(ks.SavePDFAll, commands.SaveAllPdf); - _ksm.Assign(ks.SavePDFSelected, commands.SaveSelectedPdf); + if (PlatformCompat.System.CombinedPdfAndImageSaving) + { + _ksm.Assign(ks.SavePDFAll, commands.SaveAll); + _ksm.Assign(ks.SavePDFSelected, commands.SaveSelected); + } + else + { + _ksm.Assign(ks.SaveImages, commands.SaveImages); + _ksm.Assign(ks.SaveImagesAll, commands.SaveAllImages); + _ksm.Assign(ks.SaveImagesSelected, commands.SaveSelectedImages); + _ksm.Assign(ks.SavePDF, commands.SavePdf); + _ksm.Assign(ks.SavePDFAll, commands.SaveAllPdf); + _ksm.Assign(ks.SavePDFSelected, commands.SaveSelectedPdf); + } + _ksm.Assign(ks.PDFSettings, commands.PdfSettings); + _ksm.Assign(ks.ImageSettings, commands.ImageSettings); _ksm.Assign(ks.ScanDefault, commands.Scan); + _ksm.Assign(ks.ScannerSharing, commands.ScannerSharing); _ksm.Assign(ks.ZoomIn, commands.ZoomIn); _ksm.Assign(ks.ZoomOut, commands.ZoomOut); diff --git a/NAPS2.Lib/EtoForms/EtoDialogBase.cs b/NAPS2.Lib/EtoForms/EtoDialogBase.cs index 2e7011509..338944227 100644 --- a/NAPS2.Lib/EtoForms/EtoDialogBase.cs +++ b/NAPS2.Lib/EtoForms/EtoDialogBase.cs @@ -16,6 +16,11 @@ public abstract class EtoDialogBase : Dialog, IFormBase ShowInTaskbar = true; LayoutController.Bind(this); LayoutController.Invalidated += (_, _) => FormStateController.UpdateLayoutSize(LayoutController); + if (EtoPlatform.Current.IsMac) + { + // Always have a basic menu on Mac, otherwise system keyboard shortcuts like Copy/Paste don't work + Menu = new MenuBar(); + } } protected abstract void BuildLayout(); diff --git a/NAPS2.Lib/EtoForms/EtoPlatform.cs b/NAPS2.Lib/EtoForms/EtoPlatform.cs index 6f829a4be..85e176345 100644 --- a/NAPS2.Lib/EtoForms/EtoPlatform.cs +++ b/NAPS2.Lib/EtoForms/EtoPlatform.cs @@ -131,6 +131,11 @@ public abstract class EtoPlatform public virtual Bitmap? ExtractAssociatedIcon(string exePath) => throw new NotSupportedException(); + public virtual void HandleKeyDown(Control control, Func handle) + { + control.KeyDown += (_, args) => args.Handled = handle(args.KeyData); + } + public virtual void AttachMouseWheelEvent(Control control, EventHandler eventHandler) { control.MouseWheel += eventHandler; diff --git a/NAPS2.Lib/EtoForms/Desktop/KeyboardShortcutManager.cs b/NAPS2.Lib/EtoForms/KeyboardShortcutManager.cs similarity index 94% rename from NAPS2.Lib/EtoForms/Desktop/KeyboardShortcutManager.cs rename to NAPS2.Lib/EtoForms/KeyboardShortcutManager.cs index 0b923c357..f754e3324 100644 --- a/NAPS2.Lib/EtoForms/Desktop/KeyboardShortcutManager.cs +++ b/NAPS2.Lib/EtoForms/KeyboardShortcutManager.cs @@ -1,6 +1,6 @@ using Eto.Forms; -namespace NAPS2.EtoForms.Desktop; +namespace NAPS2.EtoForms; /// /// A helper class to assign keyboard shortcuts to commands. @@ -12,12 +12,16 @@ public class KeyboardShortcutManager private readonly Dictionary _customMap = new() { + { "mod", Application.Instance.CommonModifier }, + { "cmd", Keys.Application }, { "ctrl", Keys.Control }, { "del", Keys.Delete }, { "ins", Keys.Insert }, { "break", Keys.Pause }, { "oemplus", Keys.Equal }, { "oemminus", Keys.Minus }, + { "esc", Keys.Escape }, + { ".", Keys.Period }, { "0", Keys.D0 }, { "1", Keys.D1 }, { "2", Keys.D2 }, diff --git a/NAPS2.Lib/EtoForms/Ui/DesktopCommands.cs b/NAPS2.Lib/EtoForms/Ui/DesktopCommands.cs index fba199c43..26d0050c5 100644 --- a/NAPS2.Lib/EtoForms/Ui/DesktopCommands.cs +++ b/NAPS2.Lib/EtoForms/Ui/DesktopCommands.cs @@ -32,56 +32,47 @@ public class DesktopCommands Scan = new ActionCommand(desktopScanController.ScanDefault) { Text = UiStrings.Scan, - Image = iconProvider.GetIcon("control_play_blue"), - Shortcut = Application.Instance.CommonModifier | Keys.Period + Image = iconProvider.GetIcon("control_play_blue") }; NewProfile = new ActionCommand(desktopScanController.ScanWithNewProfile) { Text = UiStrings.NewProfile, - Image = iconProvider.GetIcon("add_small"), - Shortcut = Application.Instance.CommonModifier | Keys.N + Image = iconProvider.GetIcon("add_small") }; BatchScan = new ActionCommand(desktopSubFormController.ShowBatchScanForm) { Text = UiStrings.BatchScan, - Image = iconProvider.GetIcon("application_cascade"), - Shortcut = Application.Instance.CommonModifier | Keys.B + Image = iconProvider.GetIcon("application_cascade") }; ScannerSharing = new ActionCommand(desktopSubFormController.ShowScannerSharingForm) { Text = UiStrings.ScannerSharing, - Image = iconProvider.GetIcon("wireless16"), - Shortcut = Application.Instance.CommonModifier | Keys.G + Image = iconProvider.GetIcon("wireless16") }; Profiles = new ActionCommand(desktopSubFormController.ShowProfilesForm) { Text = UiStrings.Profiles, - Image = iconProvider.GetIcon("blueprints"), - Shortcut = Application.Instance.CommonModifier | Keys.L + Image = iconProvider.GetIcon("blueprints") }; Ocr = new ActionCommand(desktopSubFormController.ShowOcrForm) { Text = UiStrings.Ocr, - Image = iconProvider.GetIcon("text"), - Shortcut = Application.Instance.CommonModifier | Keys.Alt | Keys.O + Image = iconProvider.GetIcon("text") }; Import = new ActionCommand(desktopController.Import) { Text = UiStrings.Import, - Image = iconProvider.GetIcon("folder_picture"), - Shortcut = Application.Instance.CommonModifier | Keys.O + Image = iconProvider.GetIcon("folder_picture") }; SaveAll = new ActionCommand(_imageListActions.SaveAllAsPdfOrImages) { Text = UiStrings.SaveAll, - Image = iconProvider.GetIcon("diskette"), - Shortcut = Application.Instance.CommonModifier | Keys.S + Image = iconProvider.GetIcon("diskette") }; SaveSelected = new ActionCommand(_imageListActions.SaveSelectedAsPdfOrImages) { Text = UiStrings.SaveSelected, - Image = iconProvider.GetIcon("diskette"), - Shortcut = Application.Instance.CommonModifier | Keys.Shift | Keys.S + Image = iconProvider.GetIcon("diskette") }; SavePdf = new ActionCommand(desktopController.SavePdf) { @@ -98,8 +89,7 @@ public class DesktopCommands }; PdfSettings = new ActionCommand(desktopSubFormController.ShowPdfSettingsForm) { - Text = UiStrings.PdfSettings, - Shortcut = Application.Instance.CommonModifier | Keys.Alt | Keys.P + Text = UiStrings.PdfSettings }; SaveImages = new ActionCommand(desktopController.SaveImages) { @@ -116,8 +106,7 @@ public class DesktopCommands }; ImageSettings = new ActionCommand(desktopSubFormController.ShowImageSettingsForm) { - Text = UiStrings.ImageSettings, - Shortcut = Application.Instance.CommonModifier | Keys.Alt | Keys.I + Text = UiStrings.ImageSettings }; EmailPdf = new ActionCommand(desktopController.EmailPdf) { @@ -126,24 +115,20 @@ public class DesktopCommands }; EmailAll = new ActionCommand(imageListActions.EmailAllAsPdf) { - Text = UiStrings.EmailAll, - Shortcut = Application.Instance.CommonModifier | Keys.E + Text = UiStrings.EmailAll }; EmailSelected = new ActionCommand(imageListActions.EmailSelectedAsPdf) { - Text = UiStrings.EmailSelected, - Shortcut = Application.Instance.CommonModifier | Keys.Shift | Keys.E + Text = UiStrings.EmailSelected }; EmailSettings = new ActionCommand(desktopSubFormController.ShowEmailSettingsForm) { - Text = UiStrings.EmailSettings, - Shortcut = Application.Instance.CommonModifier | Keys.Alt | Keys.E + Text = UiStrings.EmailSettings }; Print = new ActionCommand(desktopController.Print) { Text = UiStrings.Print, - Image = iconProvider.GetIcon("printer"), - Shortcut = Application.Instance.CommonModifier | Keys.P + Image = iconProvider.GetIcon("printer") }; ImageMenu = new ActionCommand { @@ -274,15 +259,13 @@ public class DesktopCommands Delete = new ActionCommand(desktopController.Delete) { Text = UiStrings.Delete, - Image = iconProvider.GetIcon("cross"), - Shortcut = Keys.Delete + Image = iconProvider.GetIcon("cross") }; ClearAll = new ActionCommand(desktopController.Clear) { ToolBarText = UiStrings.Clear, MenuText = UiStrings.ClearAll, - Image = iconProvider.GetIcon("cancel"), - Shortcut = Application.Instance.CommonModifier | Keys.Shift | Keys.Delete + Image = iconProvider.GetIcon("cancel") }; LanguageMenu = new ActionCommand { @@ -313,34 +296,27 @@ public class DesktopCommands }; SelectAll = new ActionCommand(imageListActions.SelectAll) { - Text = UiStrings.SelectAll, - Shortcut = Application.Instance.CommonModifier | Keys.A + Text = UiStrings.SelectAll }; Copy = new ActionCommand(desktopController.Copy) { Text = UiStrings.Copy, - Image = iconProvider.GetIcon("copy"), - Shortcut = Application.Instance.CommonModifier | Keys.C + Image = iconProvider.GetIcon("copy") }; Paste = new ActionCommand(desktopController.Paste) { Text = UiStrings.Paste, - Image = iconProvider.GetIcon("paste"), - Shortcut = Application.Instance.CommonModifier | Keys.V + Image = iconProvider.GetIcon("paste") }; Undo = new ActionCommand(imageListActions.Undo) { Text = UiStrings.Undo, - Image = iconProvider.GetIcon("undo"), - Shortcut = Application.Instance.CommonModifier | Keys.Z + Image = iconProvider.GetIcon("undo") }; Redo = new ActionCommand(imageListActions.Redo) { Text = UiStrings.Redo, - Image = iconProvider.GetIcon("redo"), - Shortcut = EtoPlatform.Current.IsWinForms - ? Application.Instance.CommonModifier | Keys.Y - : Application.Instance.CommonModifier | Keys.Shift | Keys.Z + Image = iconProvider.GetIcon("redo") }; } diff --git a/NAPS2.Lib/EtoForms/Ui/DesktopForm.cs b/NAPS2.Lib/EtoForms/Ui/DesktopForm.cs index fc30f09cd..c6463a9db 100644 --- a/NAPS2.Lib/EtoForms/Ui/DesktopForm.cs +++ b/NAPS2.Lib/EtoForms/Ui/DesktopForm.cs @@ -69,12 +69,7 @@ public abstract class DesktopForm : EtoFormBase _desktopSubFormController = desktopSubFormController; Commands = commands; - if (!EtoPlatform.Current.IsMac) - { - // For Mac the menu shortcuts work without needing manual hooks - // Maybe at some point we can support custom assignment on Mac, though we'll need to fix Ctrl vs Command - _keyboardShortcuts.Assign(Commands); - } + _keyboardShortcuts.Assign(Commands); CreateToolbarsAndMenus(); UpdateScanButton(); UpdateProfilesToolbar(); @@ -91,10 +86,10 @@ public abstract class DesktopForm : EtoFormBase // TODO: Fix Eto so that we don't need to set an item here (otherwise the first time we right click nothing happens) _contextMenu.Items.Add(Commands.SelectAll); _contextMenu.Opening += OpeningContextMenu; - KeyDown += OnKeyDown; - _listView.Control.KeyDown += OnKeyDown; _listView.Control.MouseWheel += ListViewMouseWheel; _listView.Control.MouseMove += ListViewMouseMove; + EtoPlatform.Current.HandleKeyDown(this, _keyboardShortcuts.Perform); + EtoPlatform.Current.HandleKeyDown(_listView.Control, _keyboardShortcuts.Perform); // // Shown += FDesktop_Shown; @@ -527,11 +522,6 @@ public abstract class DesktopForm : EtoFormBase _scanMenuCommands.Value = commandList; } - private void OnKeyDown(object? sender, KeyEventArgs e) - { - e.Handled = _keyboardShortcuts.Perform(e.KeyData); - } - protected virtual void UpdateTitle(ScanProfile? defaultProfile) { Title = string.Format(UiStrings.Naps2TitleFormat, defaultProfile?.DisplayName ?? UiStrings.Naps2FullName); diff --git a/NAPS2.Lib/EtoForms/Ui/PreviewForm.cs b/NAPS2.Lib/EtoForms/Ui/PreviewForm.cs index 9572a7edb..346ba6a54 100644 --- a/NAPS2.Lib/EtoForms/Ui/PreviewForm.cs +++ b/NAPS2.Lib/EtoForms/Ui/PreviewForm.cs @@ -9,19 +9,18 @@ public class PreviewForm : EtoDialogBase { private readonly DesktopCommands _desktopCommands; private readonly IIconProvider _iconProvider; - private readonly KeyboardShortcutManager _ksm; + private readonly KeyboardShortcutManager _previewKsm; private readonly ButtonToolItem _pageNumberButton = new(); private readonly ButtonToolItem _zoomPercentButton = new(); private UiImage? _currentImage; public PreviewForm(Naps2Config config, DesktopCommands desktopCommands, UiImageList imageList, - IIconProvider iconProvider, KeyboardShortcutManager ksm, ColorScheme colorScheme) : base(config) + IIconProvider iconProvider, ColorScheme colorScheme) : base(config) { _desktopCommands = desktopCommands; ImageList = imageList; _iconProvider = iconProvider; - _ksm = ksm; ImageViewer.ColorScheme = colorScheme; ImageViewer.ZoomChanged += ImageViewerZoomChanged; @@ -63,6 +62,9 @@ public class PreviewForm : EtoDialogBase Text = UiStrings.Delete, Image = iconProvider.GetIcon("cross") }; + + _previewKsm = new KeyboardShortcutManager(); + EtoPlatform.Current.HandleKeyDown(this, _previewKsm.Perform); } private void ImageList_ImagesUpdated(object? sender, ImageListEventArgs e) @@ -313,65 +315,59 @@ public class PreviewForm : EtoDialogBase } } - protected override async void OnKeyDown(KeyEventArgs e) - { - if (!(e.Control || e.Shift || e.Alt)) - { - switch (e.Key) - { - case Keys.Escape: - Close(); - return; - // TODO: Left/right should maybe not change page if we're not at max zoom out (i.e. if we can pan) - case Keys.PageDown: - case Keys.Right: - case Keys.Down: - await GoTo(ImageIndex + 1); - return; - case Keys.PageUp: - case Keys.Left: - case Keys.Up: - await GoTo(ImageIndex - 1); - return; - } - } - - e.Handled = _ksm.Perform(e.KeyData); - } - private void AssignKeyboardShortcuts() { - // Defaults + // Unconfigurable defaults - _ksm.Assign("Del", DeleteCurrentImageCommand); - _ksm.Assign("Ctrl+Oemplus", ZoomInCommand); - _ksm.Assign("Ctrl+OemMinus", ZoomOutCommand); - _ksm.Assign("Ctrl+0", ZoomActualCommand); - _ksm.Assign("Ctrl+Z", Commands.Undo); - _ksm.Assign(EtoPlatform.Current.IsGtk ? "Ctrl+Shift+Z" : "Ctrl+Y", Commands.Redo); + _previewKsm.Assign("Esc", Close); + _previewKsm.Assign("Del", DeleteCurrentImageCommand); + _previewKsm.Assign("Mod+0", ZoomActualCommand); + _previewKsm.Assign("Mod+Z", Commands.Undo); + _previewKsm.Assign(EtoPlatform.Current.IsWinForms ? "Mod+Y" : "Mod+Shift+Z", Commands.Redo); - // Configured + _previewKsm.Assign("PageDown", () => _ = GoTo(ImageIndex + 1)); + _previewKsm.Assign("Right", () => _ = GoTo(ImageIndex + 1)); + _previewKsm.Assign("Down", () => _ = GoTo(ImageIndex + 1)); + + _previewKsm.Assign("PageUp", () => _ = GoTo(ImageIndex - 1)); + _previewKsm.Assign("Left", () => _ = GoTo(ImageIndex - 1)); + _previewKsm.Assign("Up", () => _ = GoTo(ImageIndex - 1)); + + // Configured defaults var ks = Config.Get(c => c.KeyboardShortcuts); - _ksm.Assign(ks.Delete, DeleteCurrentImageCommand); - _ksm.Assign(ks.ImageBlackWhite, Commands.BlackWhite); - _ksm.Assign(ks.ImageBrightness, Commands.BrightCont); - _ksm.Assign(ks.ImageContrast, Commands.BrightCont); - _ksm.Assign(ks.ImageCrop, Commands.Crop); - _ksm.Assign(ks.ImageHue, Commands.HueSat); - _ksm.Assign(ks.ImageSaturation, Commands.HueSat); - _ksm.Assign(ks.ImageSharpen, Commands.Sharpen); - _ksm.Assign(ks.ImageDocumentCorrection, Commands.DocumentCorrection); - _ksm.Assign(ks.ImageSplit, Commands.Split); - _ksm.Assign(ks.ImageCombine, Commands.Combine); + _previewKsm.Assign(ks.Delete, DeleteCurrentImageCommand); + _previewKsm.Assign(ks.ImageBlackWhite, Commands.BlackWhite); + _previewKsm.Assign(ks.ImageBrightness, Commands.BrightCont); + _previewKsm.Assign(ks.ImageContrast, Commands.BrightCont); + _previewKsm.Assign(ks.ImageCrop, Commands.Crop); + _previewKsm.Assign(ks.ImageHue, Commands.HueSat); + _previewKsm.Assign(ks.ImageSaturation, Commands.HueSat); + _previewKsm.Assign(ks.ImageSharpen, Commands.Sharpen); + _previewKsm.Assign(ks.ImageDocumentCorrection, Commands.DocumentCorrection); + _previewKsm.Assign(ks.ImageSplit, Commands.Split); + _previewKsm.Assign(ks.ImageCombine, Commands.Combine); - _ksm.Assign(ks.RotateCustom, Commands.CustomRotate); - _ksm.Assign(ks.RotateFlip, Commands.Flip); - _ksm.Assign(ks.RotateLeft, Commands.RotateLeft); - _ksm.Assign(ks.RotateRight, Commands.RotateRight); - _ksm.Assign(ks.SaveImages, Commands.SaveSelectedImages); - _ksm.Assign(ks.SavePDF, Commands.SaveSelectedPdf); + _previewKsm.Assign(ks.RotateCustom, Commands.CustomRotate); + _previewKsm.Assign(ks.RotateFlip, Commands.Flip); + _previewKsm.Assign(ks.RotateLeft, Commands.RotateLeft); + _previewKsm.Assign(ks.RotateRight, Commands.RotateRight); + + if (PlatformCompat.System.CombinedPdfAndImageSaving) + { + _previewKsm.Assign(ks.SavePDFAll, Commands.SaveSelected); + } + else + { + _previewKsm.Assign(ks.SavePDF, Commands.SaveSelectedPdf); + _previewKsm.Assign(ks.SaveImages, Commands.SaveSelectedImages); + _previewKsm.Assign(ks.SavePDFAll, Commands.SaveSelectedPdf); + _previewKsm.Assign(ks.SaveImagesAll, Commands.SaveSelectedImages); + } + + _previewKsm.Assign(ks.ZoomIn, ZoomInCommand); + _previewKsm.Assign(ks.ZoomOut, ZoomOutCommand); } protected override void Dispose(bool disposing) diff --git a/NAPS2.Lib/EtoForms/Ui/ProfilesForm.cs b/NAPS2.Lib/EtoForms/Ui/ProfilesForm.cs index a2971db9b..bd67d4d3f 100644 --- a/NAPS2.Lib/EtoForms/Ui/ProfilesForm.cs +++ b/NAPS2.Lib/EtoForms/Ui/ProfilesForm.cs @@ -65,8 +65,7 @@ public class ProfilesForm : EtoDialogBase _deleteCommand = new ActionCommand(DoDelete) { MenuText = UiStrings.Delete, - Image = Icons.cross_small.ToEtoImage(), - Shortcut = Keys.Delete + Image = Icons.cross_small.ToEtoImage() }; _setDefaultCommand = new ActionCommand(DoSetDefault) { @@ -75,13 +74,11 @@ public class ProfilesForm : EtoDialogBase }; _copyCommand = new ActionCommand(DoCopy) { - MenuText = UiStrings.Copy, - Shortcut = Application.Instance.CommonModifier | Keys.C + MenuText = UiStrings.Copy }; _pasteCommand = new ActionCommand(DoPaste) { - MenuText = UiStrings.Paste, - Shortcut = Application.Instance.CommonModifier | Keys.V + MenuText = UiStrings.Paste }; _scannerSharingCommand = new ActionCommand(OpenScannerSharingForm) { @@ -89,6 +86,12 @@ public class ProfilesForm : EtoDialogBase Image = Icons.wireless16.ToEtoImage() }; + var profilesKsm = new KeyboardShortcutManager(); + profilesKsm.Assign("Del", _deleteCommand); + profilesKsm.Assign("Mod+C", _copyCommand); + profilesKsm.Assign("Mod+V", _pasteCommand); + EtoPlatform.Current.HandleKeyDown(_listView.Control, profilesKsm.Perform); + _listView.ImageSize = 48; _listView.ItemClicked += ItemClicked; _listView.SelectionChanged += SelectionChanged; diff --git a/NAPS2.Lib/EtoForms/Ui/ScannerSharingForm.cs b/NAPS2.Lib/EtoForms/Ui/ScannerSharingForm.cs index c685cde0f..66fc7ffe3 100644 --- a/NAPS2.Lib/EtoForms/Ui/ScannerSharingForm.cs +++ b/NAPS2.Lib/EtoForms/Ui/ScannerSharingForm.cs @@ -43,10 +43,13 @@ public class ScannerSharingForm : EtoDialogBase _deleteCommand = new ActionCommand(DoDelete) { MenuText = UiStrings.Delete, - Image = Icons.cross_small.ToEtoImage(), - Shortcut = Keys.Delete + Image = Icons.cross_small.ToEtoImage() }; + var profilesKsm = new KeyboardShortcutManager(); + profilesKsm.Assign("Del", _deleteCommand); + EtoPlatform.Current.HandleKeyDown(_listView.Control, profilesKsm.Perform); + // TODO: Enable // _shareAsService.Checked = _osServiceManager.IsRegistered; // _shareAsService.CheckedChanged += ShareAsServiceCheckedChanged; diff --git a/NAPS2.Sdk/Platform/ISystemCompat.cs b/NAPS2.Sdk/Platform/ISystemCompat.cs index 98cae99ec..734efb986 100644 --- a/NAPS2.Sdk/Platform/ISystemCompat.cs +++ b/NAPS2.Sdk/Platform/ISystemCompat.cs @@ -26,6 +26,8 @@ internal interface ISystemCompat bool CanPrint { get; } + bool CombinedPdfAndImageSaving { get; } + bool ShouldRememberBackgroundOperations { get; } bool RenderInWorker { get; } diff --git a/NAPS2.Sdk/Platform/LinuxSystemCompat.cs b/NAPS2.Sdk/Platform/LinuxSystemCompat.cs index 399c9c000..9b15cd527 100644 --- a/NAPS2.Sdk/Platform/LinuxSystemCompat.cs +++ b/NAPS2.Sdk/Platform/LinuxSystemCompat.cs @@ -32,6 +32,8 @@ internal class LinuxSystemCompat : ISystemCompat public bool CanPrint => true; + public bool CombinedPdfAndImageSaving => false; + public bool ShouldRememberBackgroundOperations => true; public bool RenderInWorker => false; diff --git a/NAPS2.Sdk/Platform/MacSystemCompat.cs b/NAPS2.Sdk/Platform/MacSystemCompat.cs index e92bf1dec..4ee7bcd14 100644 --- a/NAPS2.Sdk/Platform/MacSystemCompat.cs +++ b/NAPS2.Sdk/Platform/MacSystemCompat.cs @@ -32,6 +32,8 @@ internal class MacSystemCompat : ISystemCompat public bool CanPrint => true; + public bool CombinedPdfAndImageSaving => true; + public bool ShouldRememberBackgroundOperations => true; public bool RenderInWorker => false; diff --git a/NAPS2.Sdk/Platform/WindowsSystemCompat.cs b/NAPS2.Sdk/Platform/WindowsSystemCompat.cs index e33e080cd..41131db88 100644 --- a/NAPS2.Sdk/Platform/WindowsSystemCompat.cs +++ b/NAPS2.Sdk/Platform/WindowsSystemCompat.cs @@ -29,6 +29,8 @@ internal abstract class WindowsSystemCompat : ISystemCompat public bool CanPrint => true; + public bool CombinedPdfAndImageSaving => false; + public bool ShouldRememberBackgroundOperations => true; public bool RenderInWorker => true; diff --git a/NAPS2.Setup/appsettings.xml b/NAPS2.Setup/appsettings.xml index 35b58047f..802855af0 100644 --- a/NAPS2.Setup/appsettings.xml +++ b/NAPS2.Setup/appsettings.xml @@ -98,20 +98,21 @@ F11 F12 - + Ctrl+N Ctrl+B - - + Ctrl+L + Ctrl+G + Ctrl+Alt+O Ctrl+O - Ctrl+S - - - - - - - - + Ctrl+S + Ctrl+Shift+S + Ctrl+Alt+P + Ctrl+I + Ctrl+Shift+I + Ctrl+Alt+I + Ctrl+E + Ctrl+Shift+E + Ctrl+Alt+E Ctrl+P @@ -122,9 +123,9 @@ - - - + Ctrl+Shift+Left + Ctrl+Shift+Right + Ctrl+Shift+Down Ctrl+Up Ctrl+Down