naps2/NAPS2.Lib/EtoForms/Ui/BatchScanForm.cs
2024-08-11 21:01:31 -07:00

403 lines
15 KiB
C#

using System.Globalization;
using System.Threading;
using Eto.Drawing;
using Eto.Forms;
using NAPS2.Config.Model;
using NAPS2.EtoForms.Layout;
using NAPS2.EtoForms.Widgets;
using NAPS2.ImportExport;
using NAPS2.Scan;
using NAPS2.Scan.Batch;
using NAPS2.Scan.Exceptions;
using Button = Eto.Forms.Button;
using Control = Eto.Forms.Control;
using DialogResult = Eto.Forms.DialogResult;
using Label = Eto.Forms.Label;
using MessageBox = Eto.Forms.MessageBox;
using MessageBoxButtons = Eto.Forms.MessageBoxButtons;
using RadioButton = Eto.Forms.RadioButton;
namespace NAPS2.EtoForms.Ui;
public class BatchScanForm : EtoDialogBase
{
private readonly IProfileManager _profileManager;
private readonly IBatchScanPerformer _batchScanPerformer;
private readonly ErrorOutput _errorOutput;
private const string PATCH_CODE_INFO_URL = "https://www.naps2.com/doc/batch-scan#patch-t";
private readonly Label _status = new() { Text = UiStrings.PressStartWhenReady };
private readonly Button _start = new() { Text = UiStrings.Start };
private readonly Button _cancel = new() { Text = UiStrings.Cancel };
private readonly DropDownWidget<ScanProfile> _profile = new();
private readonly RadioButton _singleScan;
private readonly RadioButton _multipleScansPrompt;
private readonly RadioButton _multipleScansDelay;
private readonly LayoutVisibility _delayVis = new(false);
private readonly NumericMaskedTextBox<int> _numberOfScans = new();
private readonly NumericMaskedTextBox<decimal> _timeBetweenScans = new();
private readonly RadioButton _load;
private readonly RadioButton _saveToSingleFile;
private readonly RadioButton _saveToMultipleFiles;
private readonly LayoutVisibility _multiVis = new(false);
private readonly RadioButton _filePerScan;
private readonly RadioButton _filePerPage;
private readonly RadioButton _separateByPatchT;
private readonly LinkButton _moreInfo = C.UrlLink(PATCH_CODE_INFO_URL, UiStrings.MoreInfo);
private readonly LayoutVisibility _fileVis = new(false);
private readonly FilePathWithPlaceholders _filePath;
private readonly TransactionConfigScope<CommonConfig> _userTransact;
private readonly Naps2Config _transactionConfig;
private bool _batchRunning;
private CancellationTokenSource _cts = new();
public BatchScanForm(Naps2Config config, DialogHelper dialogHelper, IProfileManager profileManager,
IBatchScanPerformer batchScanPerformer, ErrorOutput errorOutput, IIconProvider iconProvider)
: base(config)
{
_profileManager = profileManager;
_batchScanPerformer = batchScanPerformer;
_errorOutput = errorOutput;
_singleScan = new RadioButton { Text = UiStrings.SingleScan };
_multipleScansPrompt = new RadioButton(_singleScan) { Text = UiStrings.MultipleScansPrompt };
_multipleScansDelay = new RadioButton(_singleScan) { Text = UiStrings.MultipleScansDelay };
_load = new RadioButton { Text = UiStrings.LoadIn };
_saveToSingleFile = new RadioButton(_load) { Text = UiStrings.SaveToSingleFile };
_saveToMultipleFiles = new RadioButton(_load) { Text = UiStrings.SaveToMultipleFiles };
_filePerScan = new RadioButton { Text = UiStrings.OneFilePerScan };
_filePerPage = new RadioButton(_filePerScan) { Text = UiStrings.OneFilePerPage };
_separateByPatchT = new RadioButton(_filePerScan) { Text = UiStrings.SeparateByPatchT };
_filePath = new(this, dialogHelper);
_profile.Format = x => x.DisplayName;
_start.Click += Start;
_cancel.Click += Cancel;
_multipleScansDelay.CheckedChanged += UpdateVisibility;
_saveToSingleFile.CheckedChanged += UpdateVisibility;
_saveToMultipleFiles.CheckedChanged += UpdateVisibility;
_userTransact = Config.User.BeginTransaction();
_transactionConfig = Config.WithTransaction(_userTransact);
EditProfileCommand = new ActionCommand(EditProfile) { Image = iconProvider.GetIcon("pencil_small") };
NewProfileCommand = new ActionCommand(NewProfile) { Image = iconProvider.GetIcon("add_small") };
}
private void UpdateVisibility(object? sender, EventArgs e)
{
_delayVis.IsVisible = _multipleScansDelay.Checked;
_multiVis.IsVisible = _saveToMultipleFiles.Checked;
_fileVis.IsVisible = _saveToSingleFile.Checked || _saveToMultipleFiles.Checked;
LayoutController.Invalidate();
}
public Action<ProcessedImage> ImageCallback { get; set; } = null!;
private Command NewProfileCommand { get; }
private Command EditProfileCommand { get; }
protected override void BuildLayout()
{
NewProfileCommand.Enabled =
!(Config.Get(c => c.NoUserProfiles) && _profileManager.Profiles.Any(x => x.IsLocked));
UpdateUIFromSettings();
Title = UiStrings.BatchScanFormTitle;
Icon = new Icon(1f, Icons.application_cascade.ToEtoImage());
FormStateController.FixedHeightLayout = true;
AbortButton = _cancel;
LayoutController.Content = L.Column(
L.Row(
_status.Scale().AlignCenter().NaturalWidth(200),
L.OkCancel(
_start,
_cancel
)
),
L.GroupBox(
UiStrings.ScanConfig,
L.Column(
C.Label(UiStrings.ProfileLabel),
L.Row(
_profile.AsControl().Scale(),
C.Button(EditProfileCommand, ButtonImagePosition.Overlay).Width(30),
C.Button(NewProfileCommand, ButtonImagePosition.Overlay).Width(30)
),
_singleScan,
_multipleScansPrompt,
_multipleScansDelay,
L.Column(
C.Label(UiStrings.NumberOfScansLabel),
_numberOfScans.Width(50),
C.Label(UiStrings.TimeBetweenScansLabel),
_timeBetweenScans.Width(50)
).Padding(left: 20).Visible(_delayVis)
)
),
L.GroupBox(
UiStrings.Output,
L.Column(
_load,
_saveToSingleFile,
_saveToMultipleFiles,
L.Column(
_filePerScan,
_filePerPage,
_separateByPatchT,
_moreInfo
).Padding(left: 20).Visible(_multiVis),
L.Column(
C.Label(UiStrings.FilePathLabel),
_filePath
).Visible(_fileVis)
)
)
);
}
private void UpdateUIFromSettings()
{
UpdateProfiles();
_singleScan.Checked = _transactionConfig.Get(c => c.BatchSettings.ScanType) == BatchScanType.Single;
_multipleScansPrompt.Checked =
_transactionConfig.Get(c => c.BatchSettings.ScanType) == BatchScanType.MultipleWithPrompt;
_multipleScansDelay.Checked =
_transactionConfig.Get(c => c.BatchSettings.ScanType) == BatchScanType.MultipleWithDelay;
// TODO: Verify culture (+ vaildation ofc)
_numberOfScans.Text =
_transactionConfig.Get(c => c.BatchSettings.ScanCount).ToString(CultureInfo.CurrentCulture);
_timeBetweenScans.Text = _transactionConfig.Get(c => c.BatchSettings.ScanIntervalSeconds)
.ToString(CultureInfo.CurrentCulture);
_load.Checked = _transactionConfig.Get(c => c.BatchSettings.OutputType) == BatchOutputType.Load;
_saveToSingleFile.Checked =
_transactionConfig.Get(c => c.BatchSettings.OutputType) == BatchOutputType.SingleFile;
_saveToMultipleFiles.Checked =
_transactionConfig.Get(c => c.BatchSettings.OutputType) == BatchOutputType.MultipleFiles;
_filePerScan.Checked = _transactionConfig.Get(c => c.BatchSettings.SaveSeparator) == SaveSeparator.FilePerScan;
_filePerPage.Checked = _transactionConfig.Get(c => c.BatchSettings.SaveSeparator) == SaveSeparator.FilePerPage;
_separateByPatchT.Checked = _transactionConfig.Get(c => c.BatchSettings.SaveSeparator) == SaveSeparator.PatchT;
_filePath.Text = _transactionConfig.Get(c => c.BatchSettings.SavePath);
}
private bool ValidateSettings()
{
bool ok = true;
_userTransact.Set(c => c.BatchSettings.ProfileDisplayName, _profile.SelectedItem?.DisplayName);
if (_profile.SelectedItem == null)
{
ok = false;
_profile.AsControl().Focus();
}
_userTransact.Set(c => c.BatchSettings.ScanType, _multipleScansPrompt.Checked
? BatchScanType.MultipleWithPrompt
: _multipleScansDelay.Checked
? BatchScanType.MultipleWithDelay
: BatchScanType.Single);
if (_multipleScansDelay.Checked)
{
if (!int.TryParse(_numberOfScans.Text, out int scanCount) || scanCount <= 0)
{
ok = false;
scanCount = 0;
_numberOfScans.Focus();
}
_userTransact.Set(c => c.BatchSettings.ScanCount, scanCount);
if (!double.TryParse(_timeBetweenScans.Text, out double scanInterval) || scanInterval < 0)
{
ok = false;
scanInterval = 0;
_timeBetweenScans.Focus();
}
_userTransact.Set(c => c.BatchSettings.ScanIntervalSeconds, scanInterval);
}
_userTransact.Set(c => c.BatchSettings.OutputType, _saveToSingleFile.Checked ? BatchOutputType.SingleFile
: _saveToMultipleFiles.Checked ? BatchOutputType.MultipleFiles
: BatchOutputType.Load);
_userTransact.Set(c => c.BatchSettings.SaveSeparator, _filePerScan.Checked ? SaveSeparator.FilePerScan
: _separateByPatchT.Checked ? SaveSeparator.PatchT
: SaveSeparator.FilePerPage);
_userTransact.Set(c => c.BatchSettings.SavePath, _filePath.Text);
if (_transactionConfig.Get(c => c.BatchSettings.OutputType) != BatchOutputType.Load &&
string.IsNullOrWhiteSpace(_transactionConfig.Get(c => c.BatchSettings.SavePath)))
{
ok = false;
_filePath.Focus();
}
return ok;
}
private void UpdateProfiles()
{
_profile.Items = _profileManager.Profiles;
var selectedName = _transactionConfig.Get(c => c.BatchSettings.ProfileDisplayName);
var selectedProfile = selectedName != null
? _profileManager.Profiles.FirstOrDefault(x => x.DisplayName == selectedName)
: null;
_profile.SelectedItem = selectedProfile ?? _profileManager.DefaultProfile;
}
private void EditProfile()
{
var originalProfile = _profile.SelectedItem;
if (originalProfile != null)
{
var fedit = FormFactory.Create<EditProfileForm>();
fedit.ScanProfile = originalProfile;
fedit.ShowModal();
if (fedit.Result)
{
_profileManager.Mutate(new ListMutation<ScanProfile>.ReplaceWith(fedit.ScanProfile),
ListSelection.Of(originalProfile));
_userTransact.Set(c => c.BatchSettings.ProfileDisplayName, fedit.ScanProfile.DisplayName);
UpdateProfiles();
}
}
}
private void NewProfile()
{
if (!(Config.Get(c => c.NoUserProfiles) && _profileManager.Profiles.Any(x => x.IsLocked)))
{
var fedit = FormFactory.Create<EditProfileForm>();
fedit.NewProfile = true;
fedit.ScanProfile = Config.DefaultProfileSettings();
fedit.ShowModal();
if (fedit.Result)
{
_profileManager.Mutate(new ListMutation<ScanProfile>.Append(fedit.ScanProfile),
ListSelection.Empty<ScanProfile>());
_userTransact.Set(c => c.BatchSettings.ProfileDisplayName, fedit.ScanProfile.DisplayName);
UpdateProfiles();
}
}
}
private void Start(object? sender, EventArgs args)
{
if (_batchRunning)
{
return;
}
if (!ValidateSettings())
{
return;
}
// Update state
_batchRunning = true;
_cts = new CancellationTokenSource();
// Update UI
_start.Enabled = false;
_cancel.Enabled = true;
_cancel.Text = UiStrings.Cancel;
EnableDisableSettings(false);
// Start the batch
DoBatchScan().AssertNoAwait();
// Save settings for next time (could also do on form close)
_userTransact.Commit();
}
private void EnableDisableSettings(bool enabled)
{
var controls = new Control[]
{
_profile.AsControl(), _singleScan, _multipleScansPrompt, _multipleScansDelay, _numberOfScans,
_timeBetweenScans, _load, _saveToSingleFile, _saveToMultipleFiles, _filePerScan, _filePerPage,
_separateByPatchT, _moreInfo
};
foreach (var control in controls)
{
control.Enabled = enabled;
}
_filePath.Enabled = enabled;
EditProfileCommand.Enabled = enabled;
NewProfileCommand.Enabled = enabled;
}
private async Task DoBatchScan()
{
try
{
await _batchScanPerformer.PerformBatchScan(_transactionConfig.Get(c => c.BatchSettings), this,
image => Invoker.Current.Invoke(() => ImageCallback(image)), ProgressCallback, _cts.Token);
Invoker.Current.Invoke(() =>
{
_status.Text = _cts.IsCancellationRequested
? MiscResources.BatchStatusCancelled
: MiscResources.BatchStatusComplete;
});
}
catch (ScanDriverException ex)
{
if (ex is ScanDriverUnknownException)
{
Log.ErrorException("Error in batch scan", ex);
_errorOutput.DisplayError(ex.Message, ex);
}
else
{
_errorOutput.DisplayError(ex.Message);
}
}
catch (Exception ex)
{
Log.ErrorException("Error in batch scan", ex);
_errorOutput.DisplayError(MiscResources.BatchError, ex);
Invoker.Current.Invoke(() => { _status.Text = MiscResources.BatchStatusError; });
}
Invoker.Current.Invoke(() =>
{
_batchRunning = false;
_cts = new CancellationTokenSource();
_start.Enabled = true;
_cancel.Enabled = true;
_cancel.Text = MiscResources.Close;
EnableDisableSettings(true);
Focus();
});
}
private void ProgressCallback(string status)
{
Invoker.Current.Invoke(() => { _status.Text = status; });
}
private void Cancel(object? sender, EventArgs e)
{
if (_batchRunning)
{
if (MessageBox.Show(MiscResources.ConfirmCancelBatch, MiscResources.CancelBatch, MessageBoxButtons.YesNo,
MessageBoxType.Question, MessageBoxDefaultButton.Yes) == DialogResult.Yes)
{
_cts.Cancel();
_cancel.Enabled = false;
_status.Text = MiscResources.BatchStatusCancelling;
}
}
else
{
Close();
}
}
}