From 02442c862a57780123d7fe86254e2983b85e36b8 Mon Sep 17 00:00:00 2001 From: Ben Olden-Cooligan Date: Tue, 6 Aug 2024 22:12:56 -0700 Subject: [PATCH] Refactor common UI into DeviceSelectorWidget --- .../EtoForms/Desktop/DesktopScanController.cs | 2 +- NAPS2.Lib/EtoForms/IFormBase.cs | 6 + NAPS2.Lib/EtoForms/Ui/EditProfileForm.cs | 146 ++++-------------- NAPS2.Lib/EtoForms/Ui/SharedDeviceForm.cs | 131 +++------------- .../Widgets/DeviceChangedEventArgs.cs | 10 ++ .../EtoForms/Widgets/DeviceSelectorWidget.cs | 139 +++++++++++++++++ 6 files changed, 211 insertions(+), 223 deletions(-) create mode 100644 NAPS2.Lib/EtoForms/Widgets/DeviceChangedEventArgs.cs create mode 100644 NAPS2.Lib/EtoForms/Widgets/DeviceSelectorWidget.cs diff --git a/NAPS2.Lib/EtoForms/Desktop/DesktopScanController.cs b/NAPS2.Lib/EtoForms/Desktop/DesktopScanController.cs index a61e4b4f51..254cef6f1d 100644 --- a/NAPS2.Lib/EtoForms/Desktop/DesktopScanController.cs +++ b/NAPS2.Lib/EtoForms/Desktop/DesktopScanController.cs @@ -76,7 +76,7 @@ public async Task ScanWithDevice(string deviceID) // Populate the device field automatically (because we can do that!) using var deviceManager = new WiaDeviceManager(); using var device = deviceManager.FindDevice(deviceID); - editSettingsForm.CurrentDevice = DeviceChoice.ForDevice(new ScanDevice(Driver.Wia, deviceID, device.Name())); + editSettingsForm.SetDevice(new ScanDevice(Driver.Wia, deviceID, device.Name())); } catch (WiaException) { diff --git a/NAPS2.Lib/EtoForms/IFormBase.cs b/NAPS2.Lib/EtoForms/IFormBase.cs index 0ab0b15171..dbfdc78d0b 100644 --- a/NAPS2.Lib/EtoForms/IFormBase.cs +++ b/NAPS2.Lib/EtoForms/IFormBase.cs @@ -1,3 +1,5 @@ +using NAPS2.EtoForms.Layout; + namespace NAPS2.EtoForms; public interface IFormBase @@ -7,4 +9,8 @@ public interface IFormBase IFormFactory FormFactory { get; set; } Naps2Config Config { get; set; } + + LayoutController LayoutController { get; } + + IntPtr NativeHandle { get; } } \ No newline at end of file diff --git a/NAPS2.Lib/EtoForms/Ui/EditProfileForm.cs b/NAPS2.Lib/EtoForms/Ui/EditProfileForm.cs index a18fe2bcbd..0803bb92fe 100644 --- a/NAPS2.Lib/EtoForms/Ui/EditProfileForm.cs +++ b/NAPS2.Lib/EtoForms/Ui/EditProfileForm.cs @@ -11,17 +11,12 @@ namespace NAPS2.EtoForms.Ui; public class EditProfileForm : EtoDialogBase { - private readonly IScanPerformer _scanPerformer; private readonly ErrorOutput _errorOutput; private readonly ProfileNameTracker _profileNameTracker; private readonly DeviceCapsCache _deviceCapsCache; private readonly TextBox _displayName = new(); - private readonly ImageView _deviceIcon = new(); - private readonly Label _deviceName = new(); - private readonly Label _deviceDriver = new(); - private readonly LayoutVisibility _deviceVis = new(false); - private readonly Button _chooseDevice = new() { Text = UiStrings.ChooseDevice }; + private readonly DeviceSelectorWidget _deviceSelectorWidget; private readonly RadioButton _predefinedSettings; private readonly RadioButton _nativeUi; private readonly DropDown _paperSource = C.EnumDropDown(); @@ -38,21 +33,24 @@ public class EditProfileForm : EtoDialogBase private readonly SliderWithTextBox _contrastSlider = new(); private ScanProfile _scanProfile = null!; - private DeviceChoice _currentDevice = DeviceChoice.None; private bool _isDefault; private bool _result; private bool _suppressChangeEvent; private bool _suppressPageSizeEvent; - private CancellationTokenSource? _loadIconCts; private CancellationTokenSource? _updateCapsCts; public EditProfileForm(Naps2Config config, IScanPerformer scanPerformer, ErrorOutput errorOutput, ProfileNameTracker profileNameTracker, DeviceCapsCache deviceCapsCache) : base(config) { - _scanPerformer = scanPerformer; _errorOutput = errorOutput; _profileNameTracker = profileNameTracker; _deviceCapsCache = deviceCapsCache; + _deviceSelectorWidget = new(scanPerformer, deviceCapsCache, this) + { + ProfileFunc = GetUpdatedScanProfile, + AllowAlwaysAsk = true + }; + _deviceSelectorWidget.DeviceChanged += DeviceChanged; _predefinedSettings = new RadioButton { Text = UiStrings.UsePredefinedSettings }; _nativeUi = new RadioButton(_predefinedSettings) { Text = UiStrings.UseNativeUi }; @@ -60,12 +58,30 @@ public EditProfileForm(Naps2Config config, IScanPerformer scanPerformer, ErrorOu _predefinedSettings.CheckedChanged += PredefinedSettings_CheckedChanged; _nativeUi.CheckedChanged += NativeUi_CheckedChanged; - _chooseDevice.Click += ChooseDevice; _enableAutoSave.CheckedChanged += EnableAutoSave_CheckedChanged; _autoSaveSettings.Click += AutoSaveSettings_LinkClicked; _advanced.Click += Advanced_Click; } + public void SetDevice(ScanDevice device) + { + _deviceSelectorWidget.Choice = DeviceChoice.ForDevice(device); + } + + private void DeviceChanged(object? sender, DeviceChangedEventArgs e) + { + if (e.NewChoice.Device != null && (string.IsNullOrEmpty(_displayName.Text) || + e.PreviousChoice.Device?.Name == _displayName.Text)) + { + _displayName.Text = e.NewChoice.Device.Name; + } + DeviceDriver = e.NewChoice.Driver; + IconUri = e.NewChoice.Device?.IconUri; + + UpdateCaps(); + UpdateEnabledControls(); + } + protected override void BuildLayout() { Title = UiStrings.EditProfileFormTitle; @@ -78,18 +94,7 @@ protected override void BuildLayout() C.Label(UiStrings.DisplayNameLabel), _displayName, C.Spacer(), - L.GroupBox(UiStrings.DeviceLabel, - L.Row( - _deviceIcon.Visible(_deviceVis).AlignCenter().NaturalWidth(48), - L.Column( - C.Filler(), - _deviceName, - _deviceDriver, - C.Filler() - ).Spacing(5).Visible(_deviceVis).Scale(), - _chooseDevice.AlignCenter() - ) - ), + _deviceSelectorWidget, C.Spacer(), PlatformCompat.System.IsWiaDriverSupported || PlatformCompat.System.IsTwainDriverSupported ? L.Row( @@ -145,71 +150,8 @@ public ScanProfile ScanProfile public bool NewProfile { get; set; } - public DeviceChoice CurrentDevice - { - get => _currentDevice; - set - { - _currentDevice = value; - if (value == DeviceChoice.None) - { - _deviceName.Text = ""; - _deviceVis.IsVisible = false; - } - else - { - _deviceName.Text = value.Device?.Name ?? UiStrings.AlwaysAsk; - _deviceDriver.Text = value.Driver switch - { - Driver.Wia => UiStrings.WiaDriver, - Driver.Twain => UiStrings.TwainDriver, - Driver.Sane => UiStrings.SaneDriver, - Driver.Escl => UiStrings.EsclDriver, - Driver.Apple => UiStrings.AppleDriver, - _ => "" - }; - _deviceVis.IsVisible = true; - } - } - } - private void UpdateUiForCaps() { - SetDeviceIcon(IconUri); - } - - private void SetDeviceIcon(string? iconUri) - { - var cachedIcon = _deviceCapsCache.GetCachedIcon(iconUri); - _deviceIcon.Image = - cachedIcon ?? (_currentDevice.AlwaysAsk ? Icons.ask.ToEtoImage() : Icons.device.ToEtoImage()); - LayoutController.Invalidate(); - if (cachedIcon == null && iconUri != null) - { - ReloadDeviceIcon(iconUri); - } - } - - private void ReloadDeviceIcon(string iconUri) - { - var cts = new CancellationTokenSource(); - _loadIconCts?.Cancel(); - _loadIconCts = cts; - Task.Run(async () => - { - var icon = await _deviceCapsCache.LoadIcon(iconUri); - if (icon != null) - { - Invoker.Current.Invoke(() => - { - if (!cts.IsCancellationRequested) - { - _deviceIcon.Image = icon; - LayoutController.Invalidate(); - } - }); - } - }); } private void UpdateCaps() @@ -272,16 +214,16 @@ protected override void OnLoad(EventArgs e) IconUri = ScanProfile.Device?.IconUri; _displayName.Text = ScanProfile.DisplayName; - if (CurrentDevice == DeviceChoice.None) + if (_deviceSelectorWidget.Choice == DeviceChoice.None) { var device = ScanProfile.Device?.ToScanDevice(DeviceDriver); if (device != null) { - CurrentDevice = DeviceChoice.ForDevice(device); + _deviceSelectorWidget.Choice = DeviceChoice.ForDevice(device); } else if (!NewProfile) { - CurrentDevice = DeviceChoice.ForAlwaysAsk(DeviceDriver); + _deviceSelectorWidget.Choice = DeviceChoice.ForAlwaysAsk(DeviceDriver); } } _isDefault = ScanProfile.IsDefault; @@ -318,26 +260,6 @@ protected override void OnLoad(EventArgs e) UpdateEnabledControls(); } - private async void ChooseDevice(object? sender, EventArgs args) - { - ScanProfile.DriverName = DeviceDriver.ToString().ToLowerInvariant(); - var choice = await _scanPerformer.PromptForDevice(ScanProfile, true, NativeHandle); - if (choice.Device != null || choice.AlwaysAsk) - { - if ((string.IsNullOrEmpty(_displayName.Text) || - CurrentDevice.Device?.Name == _displayName.Text) && !choice.AlwaysAsk) - { - _displayName.Text = choice.Device!.Name; - } - CurrentDevice = choice; - DeviceDriver = choice.Driver; - IconUri = choice.Device?.IconUri; - - UpdateCaps(); - UpdateEnabledControls(); - } - } - private void UpdatePageSizeList() { _suppressPageSizeEvent = true; @@ -421,7 +343,7 @@ private bool SaveSettings() _errorOutput.DisplayError(MiscResources.NameMissing); return false; } - if (CurrentDevice == DeviceChoice.None) + if (_deviceSelectorWidget.Choice == DeviceChoice.None) { _errorOutput.DisplayError(MiscResources.NoDeviceSelected); return false; @@ -432,7 +354,7 @@ private bool SaveSettings() { if (!ScanProfile.IsDeviceLocked) { - ScanProfile.Device = ScanProfileDevice.FromScanDevice(CurrentDevice.Device); + ScanProfile.Device = ScanProfileDevice.FromScanDevice(_deviceSelectorWidget.Choice.Device); } return true; } @@ -451,7 +373,7 @@ private ScanProfile GetUpdatedScanProfile() { Version = ScanProfile.CURRENT_VERSION, - Device = ScanProfileDevice.FromScanDevice(CurrentDevice.Device), + Device = ScanProfileDevice.FromScanDevice(_deviceSelectorWidget.Choice.Device), Caps = ScanProfile.Caps, IsDefault = _isDefault, DriverName = DeviceDriver.ToString().ToLowerInvariant(), @@ -515,7 +437,7 @@ private void UpdateEnabledControls() bool settingsEnabled = !locked && (_predefinedSettings.Checked || !canUseNativeUi); _displayName.Enabled = !locked; - _chooseDevice.Enabled = !deviceLocked; + _deviceSelectorWidget.Enabled = !deviceLocked; _predefinedSettings.Enabled = _nativeUi.Enabled = !locked; _paperSource.Enabled = settingsEnabled; diff --git a/NAPS2.Lib/EtoForms/Ui/SharedDeviceForm.cs b/NAPS2.Lib/EtoForms/Ui/SharedDeviceForm.cs index 879e09fc98..f6115afc10 100644 --- a/NAPS2.Lib/EtoForms/Ui/SharedDeviceForm.cs +++ b/NAPS2.Lib/EtoForms/Ui/SharedDeviceForm.cs @@ -1,7 +1,7 @@ -using System.Threading; using Eto.Drawing; using Eto.Forms; using NAPS2.EtoForms.Layout; +using NAPS2.EtoForms.Widgets; using NAPS2.Remoting.Server; using NAPS2.Scan; using NAPS2.Scan.Internal; @@ -13,30 +13,33 @@ public class SharedDeviceForm : EtoDialogBase private const int BASE_PORT = 9801; private const int BASE_TLS_PORT = 9901; - private readonly IScanPerformer _scanPerformer; private readonly ErrorOutput _errorOutput; private readonly ISharedDeviceManager _sharedDeviceManager; - private readonly DeviceCapsCache _deviceCapsCache; private readonly TextBox _displayName = new(); - private readonly ImageView _deviceIcon = new(); - private readonly Label _deviceName = new(); - private readonly Label _deviceDriver = new(); - private readonly LayoutVisibility _deviceVis = new(false); - private readonly Button _chooseDevice = new() { Text = UiStrings.ChooseDevice }; - - private DeviceChoice _currentDevice = DeviceChoice.None; - private CancellationTokenSource? _loadIconCts; + private readonly DeviceSelectorWidget _deviceSelectorWidget; public SharedDeviceForm(Naps2Config config, IScanPerformer scanPerformer, ErrorOutput errorOutput, ISharedDeviceManager sharedDeviceManager, DeviceCapsCache deviceCapsCache) : base(config) { - _scanPerformer = scanPerformer; _errorOutput = errorOutput; _sharedDeviceManager = sharedDeviceManager; - _deviceCapsCache = deviceCapsCache; + _deviceSelectorWidget = new(scanPerformer, deviceCapsCache, this) + { + ProfileFunc = () => new ScanProfile { DriverName = DeviceDriver.ToString().ToLowerInvariant() }, + AllowAlwaysAsk = false + }; + _deviceSelectorWidget.DeviceChanged += DeviceChanged; + } - _chooseDevice.Click += ChooseDevice; + private void DeviceChanged(object? sender, DeviceChangedEventArgs e) + { + if (e.NewChoice.Device != null && (string.IsNullOrEmpty(_displayName.Text) || + e.PreviousChoice.Device?.Name == _displayName.Text)) + { + _displayName.Text = e.NewChoice.Device.Name; + } + DeviceDriver = e.NewChoice.Driver; } protected override void BuildLayout() @@ -51,18 +54,7 @@ protected override void BuildLayout() C.Label(UiStrings.DisplayNameLabel), _displayName, C.Spacer(), - L.GroupBox(UiStrings.DeviceLabel, - L.Row( - _deviceIcon.Visible(_deviceVis).AlignCenter().NaturalWidth(48), - L.Column( - C.Filler(), - _deviceName, - _deviceDriver, - C.Filler() - ).Spacing(5).Visible(_deviceVis).Scale(), - _chooseDevice.AlignCenter() - ) - ), + _deviceSelectorWidget, C.Filler(), L.Row( C.Filler(), @@ -77,34 +69,6 @@ protected override void BuildLayout() public SharedDevice? SharedDevice { get; set; } - public DeviceChoice CurrentDevice - { - get => _currentDevice; - set - { - _currentDevice = value; - if (value == DeviceChoice.None) - { - _deviceName.Text = ""; - _deviceVis.IsVisible = false; - } - else - { - _deviceName.Text = value.Device?.Name; - _deviceDriver.Text = value.Driver switch - { - Driver.Wia => UiStrings.WiaDriver, - Driver.Twain => UiStrings.TwainDriver, - Driver.Sane => UiStrings.SaneDriver, - Driver.Escl => UiStrings.EsclDriver, - Driver.Apple => UiStrings.AppleDriver, - _ => "" - }; - _deviceVis.IsVisible = true; - } - } - } - private int Port { get; set; } private int TlsPort { get; set; } @@ -118,30 +82,11 @@ protected override void OnLoad(EventArgs e) if (SharedDevice != null) { _displayName.Text = SharedDevice.Name; - CurrentDevice = DeviceChoice.ForDevice(SharedDevice.Device); + _deviceSelectorWidget.Choice = DeviceChoice.ForDevice(SharedDevice.Device); Port = SharedDevice.Port; TlsPort = SharedDevice.TlsPort; DeviceDriver = SharedDevice.Device.Driver; } - - SetDeviceIcon(CurrentDevice.Device?.IconUri); - } - - private async void ChooseDevice(object? sender, EventArgs args) - { - var profile = new ScanProfile { DriverName = DeviceDriver.ToString().ToLowerInvariant() }; - var device = await _scanPerformer.PromptForDevice(profile, false, NativeHandle); - if (device.Device != null) - { - if (string.IsNullOrEmpty(_displayName.Text) || - CurrentDevice != DeviceChoice.None && CurrentDevice.Device?.Name == _displayName.Text) - { - _displayName.Text = device.Device.Name; - } - CurrentDevice = device; - DeviceDriver = device.Driver; - SetDeviceIcon(CurrentDevice.Device?.IconUri); - } } private bool SaveSettings() @@ -151,7 +96,7 @@ private bool SaveSettings() _errorOutput.DisplayError(MiscResources.NameMissing); return false; } - if (CurrentDevice.Device == null) + if (_deviceSelectorWidget.Choice.Device == null) { _errorOutput.DisplayError(MiscResources.NoDeviceSelected); return false; @@ -159,7 +104,7 @@ private bool SaveSettings() SharedDevice = new SharedDevice { Name = _displayName.Text, - Device = CurrentDevice.Device, + Device = _deviceSelectorWidget.Choice.Device, Port = Port == 0 ? NextPort() : Port, TlsPort = TlsPort == 0 ? NextTlsPort() : TlsPort }; @@ -188,38 +133,4 @@ private int NextTlsPort() } return tlsPort; } - - private void SetDeviceIcon(string? iconUri) - { - var cachedIcon = _deviceCapsCache.GetCachedIcon(iconUri); - _deviceIcon.Image = - cachedIcon ?? (_currentDevice.AlwaysAsk ? Icons.ask.ToEtoImage() : Icons.device.ToEtoImage()); - LayoutController.Invalidate(); - if (cachedIcon == null && iconUri != null) - { - ReloadDeviceIcon(iconUri); - } - } - - private void ReloadDeviceIcon(string iconUri) - { - var cts = new CancellationTokenSource(); - _loadIconCts?.Cancel(); - _loadIconCts = cts; - Task.Run(async () => - { - var icon = await _deviceCapsCache.LoadIcon(iconUri); - if (icon != null) - { - Invoker.Current.Invoke(() => - { - if (!cts.IsCancellationRequested) - { - _deviceIcon.Image = icon; - LayoutController.Invalidate(); - } - }); - } - }); - } } \ No newline at end of file diff --git a/NAPS2.Lib/EtoForms/Widgets/DeviceChangedEventArgs.cs b/NAPS2.Lib/EtoForms/Widgets/DeviceChangedEventArgs.cs new file mode 100644 index 0000000000..3ba18961d7 --- /dev/null +++ b/NAPS2.Lib/EtoForms/Widgets/DeviceChangedEventArgs.cs @@ -0,0 +1,10 @@ +using NAPS2.Scan; + +namespace NAPS2.EtoForms.Widgets; + +public class DeviceChangedEventArgs : EventArgs +{ + public required DeviceChoice PreviousChoice { get; init; } + + public required DeviceChoice NewChoice { get; init; } +} \ No newline at end of file diff --git a/NAPS2.Lib/EtoForms/Widgets/DeviceSelectorWidget.cs b/NAPS2.Lib/EtoForms/Widgets/DeviceSelectorWidget.cs new file mode 100644 index 0000000000..61a000ad0f --- /dev/null +++ b/NAPS2.Lib/EtoForms/Widgets/DeviceSelectorWidget.cs @@ -0,0 +1,139 @@ +using System.Threading; +using Eto.Forms; +using NAPS2.EtoForms.Layout; +using NAPS2.Scan; + +namespace NAPS2.EtoForms.Widgets; + +public class DeviceSelectorWidget +{ + private readonly IScanPerformer _scanPerformer; + private readonly DeviceCapsCache _deviceCapsCache; + private readonly IFormBase _parentWindow; + + private readonly ImageView _deviceIcon = new(); + private readonly Label _deviceName = new(); + private readonly Label _deviceDriver = new(); + private readonly LayoutVisibility _deviceVis = new(false); + private readonly Button _chooseDevice = new() { Text = UiStrings.ChooseDevice }; + + private DeviceChoice _choice = DeviceChoice.None; + private CancellationTokenSource? _loadIconCts; + + public DeviceSelectorWidget(IScanPerformer scanPerformer, DeviceCapsCache deviceCapsCache, IFormBase parentWindow) + { + _scanPerformer = scanPerformer; + _deviceCapsCache = deviceCapsCache; + _parentWindow = parentWindow; + _chooseDevice.Click += ChooseDevice; + } + + public required Func ProfileFunc { get; init; } + + public bool AllowAlwaysAsk { get; init; } + + public DeviceChoice Choice + { + get => _choice; + set + { + _choice = value; + if (value == DeviceChoice.None) + { + _deviceName.Text = ""; + _deviceVis.IsVisible = false; + } + else + { + _deviceName.Text = value.Device?.Name ?? UiStrings.AlwaysAsk; + _deviceDriver.Text = value.Driver switch + { + Driver.Wia => UiStrings.WiaDriver, + Driver.Twain => UiStrings.TwainDriver, + Driver.Sane => UiStrings.SaneDriver, + Driver.Escl => UiStrings.EsclDriver, + Driver.Apple => UiStrings.AppleDriver, + _ => "" + }; + _deviceVis.IsVisible = true; + SetDeviceIcon(Choice.Device?.IconUri); + } + } + } + + public bool Enabled + { + get => _chooseDevice.Enabled; + set => _chooseDevice.Enabled = value; + } + + public event EventHandler? DeviceChanged; + + private async void ChooseDevice(object? sender, EventArgs args) + {; + var choice = await _scanPerformer.PromptForDevice(ProfileFunc(), AllowAlwaysAsk, _parentWindow.NativeHandle); + if (choice.Device != null || choice.AlwaysAsk) + { + var previousChoice = Choice; + Choice = choice; + SetDeviceIcon(Choice.Device?.IconUri); + DeviceChanged?.Invoke(this, + new DeviceChangedEventArgs { PreviousChoice = previousChoice, NewChoice = choice }); + } + } + + public void SetDeviceIcon(string? iconUri) + { + var cachedIcon = _deviceCapsCache.GetCachedIcon(iconUri); + _deviceIcon.Image = + cachedIcon ?? (_choice.AlwaysAsk ? Icons.ask.ToEtoImage() : Icons.device.ToEtoImage()); + _parentWindow.LayoutController.Invalidate(); + if (cachedIcon == null && iconUri != null) + { + ReloadDeviceIcon(iconUri); + } + } + + private void ReloadDeviceIcon(string iconUri) + { + var cts = new CancellationTokenSource(); + _loadIconCts?.Cancel(); + _loadIconCts = cts; + Task.Run(async () => + { + var icon = await _deviceCapsCache.LoadIcon(iconUri); + if (icon != null) + { + Invoker.Current.Invoke(() => + { + if (!cts.IsCancellationRequested) + { + _deviceIcon.Image = icon; + _parentWindow.LayoutController.Invalidate(); + } + }); + } + }); + } + + public static implicit operator LayoutElement(DeviceSelectorWidget control) + { + return control.AsControl(); + } + + public LayoutElement AsControl() + { + return L.GroupBox(UiStrings.DeviceLabel, + L.Row( + _deviceIcon.Visible(_deviceVis).AlignCenter().NaturalWidth(48), + L.Column( + C.Filler(), + _deviceName, + _deviceDriver, + C.Filler() + ).Spacing(5).Visible(_deviceVis).Scale(), + _chooseDevice.AlignCenter() + ) + ); + } +} \ No newline at end of file