WIP: Cross-platform notifications

This commit is contained in:
Ben Olden-Cooligan 2023-04-02 19:21:53 -07:00
parent ef05940838
commit bb195cb5d0
37 changed files with 516 additions and 1380 deletions

View File

@ -5,6 +5,7 @@ using Gdk;
using Gtk;
using NAPS2.EtoForms.Desktop;
using NAPS2.EtoForms.Gtk;
using NAPS2.EtoForms.Notifications;
using NAPS2.EtoForms.Widgets;
using NAPS2.ImportExport.Images;
using Command = Eto.Forms.Command;
@ -23,7 +24,7 @@ public class GtkDesktopForm : DesktopForm
public GtkDesktopForm(
Naps2Config config,
DesktopKeyboardShortcuts keyboardShortcuts,
INotificationManager notify,
NotificationManager notificationManager,
CultureHelper cultureHelper,
ColorScheme colorScheme,
IProfileManager profileManager,
@ -38,7 +39,7 @@ public class GtkDesktopForm : DesktopForm
DesktopFormProvider desktopFormProvider,
IDesktopSubFormController desktopSubFormController,
DesktopCommands commands)
: base(config, keyboardShortcuts, notify, cultureHelper, colorScheme, profileManager,
: base(config, keyboardShortcuts, notificationManager, cultureHelper, colorScheme, profileManager,
imageList, imageTransfer, thumbnailController, thumbnailProvider, desktopController, desktopScanController,
imageListActions, imageListViewBehavior, desktopFormProvider, desktopSubFormController, commands)
{

View File

@ -4,7 +4,6 @@ using NAPS2.EtoForms.Gtk;
using NAPS2.EtoForms.Ui;
using NAPS2.Images.Gtk;
using NAPS2.ImportExport;
using NAPS2.Update;
namespace NAPS2.Modules;
@ -14,7 +13,6 @@ public class GtkModule : GuiModule
{
base.Load(builder);
builder.RegisterType<StubNotificationManager>().As<INotificationManager>().SingleInstance();
builder.RegisterType<GtkScannedImagePrinter>().As<IScannedImagePrinter>();
builder.RegisterType<GtkDarkModeProvider>().As<IDarkModeProvider>();
builder.RegisterType<GtkImageContext>().As<ImageContext>();
@ -26,30 +24,3 @@ public class GtkModule : GuiModule
EtoPlatform.Current = new GtkEtoPlatform();
}
}
public class StubNotificationManager : INotificationManager
{
public void PdfSaved(string path)
{
}
public void ImagesSaved(int imageCount, string path)
{
}
public void DonatePrompt()
{
}
public void OperationProgress(OperationProgress opModalProgress, IOperation op)
{
}
public void UpdateAvailable(IUpdateChecker updateChecker, UpdateInfo update)
{
}
public void Rebuild()
{
}
}

View File

@ -3,6 +3,7 @@ using Eto.Forms;
using NAPS2.EtoForms.Desktop;
using NAPS2.EtoForms.Layout;
using NAPS2.EtoForms.Mac;
using NAPS2.EtoForms.Notifications;
using NAPS2.EtoForms.Widgets;
using NAPS2.ImportExport.Images;
using NAPS2.Scan;
@ -14,7 +15,7 @@ public class MacDesktopForm : DesktopForm
public MacDesktopForm(
Naps2Config config,
DesktopKeyboardShortcuts keyboardShortcuts,
INotificationManager notify,
NotificationManager notificationManager,
CultureHelper cultureHelper,
ColorScheme colorScheme,
IProfileManager profileManager,
@ -29,7 +30,7 @@ public class MacDesktopForm : DesktopForm
DesktopFormProvider desktopFormProvider,
IDesktopSubFormController desktopSubFormController,
DesktopCommands commands)
: base(config, keyboardShortcuts, notify, cultureHelper, colorScheme, profileManager,
: base(config, keyboardShortcuts, notificationManager, cultureHelper, colorScheme, profileManager,
imageList, imageTransfer, thumbnailController, thumbnailProvider, desktopController, desktopScanController,
imageListActions, imageListViewBehavior, desktopFormProvider, desktopSubFormController, commands)
{

View File

@ -5,8 +5,6 @@ using NAPS2.EtoForms.Ui;
using NAPS2.Images.Mac;
using NAPS2.ImportExport;
using NAPS2.ImportExport.Email;
using NAPS2.Pdf;
using NAPS2.Update;
namespace NAPS2.Modules;
@ -16,7 +14,6 @@ public class MacModule : GuiModule
{
base.Load(builder);
builder.RegisterType<StubNotificationManager>().As<INotificationManager>().SingleInstance();
builder.RegisterType<MacScannedImagePrinter>().As<IScannedImagePrinter>();
builder.RegisterType<AppleMailEmailProvider>().As<IAppleMailEmailProvider>();
builder.RegisterType<MacDarkModeProvider>().As<IDarkModeProvider>();
@ -30,39 +27,3 @@ public class MacModule : GuiModule
EtoPlatform.Current = new MacEtoPlatform();
}
}
public class StubNotificationManager : INotificationManager
{
public void PdfSaved(string path)
{
}
public void ImagesSaved(int imageCount, string path)
{
}
public void DonatePrompt()
{
}
public void OperationProgress(OperationProgress opModalProgress, IOperation op)
{
}
public void UpdateAvailable(IUpdateChecker updateChecker, UpdateInfo update)
{
}
public void Rebuild()
{
}
}
public class StubPdfPasswordProvider : IPdfPasswordProvider
{
public bool ProvidePassword(string fileName, int attemptCount, out string password)
{
password = null!;
return false;
}
}

View File

@ -1,6 +1,8 @@
using Moq;
using NAPS2.EtoForms;
using NAPS2.EtoForms.Desktop;
using NAPS2.EtoForms.Notifications;
using NAPS2.EtoForms.Widgets;
using NAPS2.ImportExport;
using NAPS2.ImportExport.Images;
using NAPS2.Platform.Windows;
@ -27,7 +29,7 @@ public class DesktopControllerTests : ContextualTests
private readonly Mock<IOperationFactory> _operationFactory;
private readonly StillImage _stillImage;
private readonly Mock<IUpdateChecker> _updateChecker;
private readonly Mock<INotificationManager> _notificationManager;
private readonly Mock<Notify> _notify;
private readonly ImageTransfer _imageTransfer;
private readonly ImageClipboard _imageClipboard;
private readonly Mock<IExportController> _exportHelper;
@ -51,7 +53,7 @@ public class DesktopControllerTests : ContextualTests
_operationFactory = new Mock<IOperationFactory>();
_stillImage = new StillImage();
_updateChecker = new Mock<IUpdateChecker>();
_notificationManager = new Mock<INotificationManager>();
_notify = new Mock<Notify>();
_imageTransfer = new ImageTransfer();
_imageClipboard = new ImageClipboard();
_exportHelper = new Mock<IExportController>();
@ -72,11 +74,11 @@ public class DesktopControllerTests : ContextualTests
_operationFactory.Object,
_stillImage,
_updateChecker.Object,
_notificationManager.Object,
_notify.Object,
_imageTransfer,
_imageClipboard,
new ImageListActions(_imageList, _operationFactory.Object, _operationProgress.Object,
_config, _thumbnailController, _exportHelper.Object, _notificationManager.Object),
_config, _thumbnailController, _exportHelper.Object, _notify.Object),
_dialogHelper.Object,
_desktopImagesController,
_desktopScanController.Object,
@ -105,7 +107,7 @@ public class DesktopControllerTests : ContextualTests
Assert.True(_config.Get(c => c.HasBeenRun));
DateAsserts.Recent(TimeSpan.FromMilliseconds(1000), _config.Get(c => c.FirstRunDate));
_notificationManager.VerifyNoOtherCalls();
_notify.VerifyNoOtherCalls();
}
[Fact]
@ -119,7 +121,7 @@ public class DesktopControllerTests : ContextualTests
Assert.True(_config.Get(c => c.HasBeenRun));
Assert.Equal(firstRunDate, _config.Get(c => c.FirstRunDate));
_notificationManager.VerifyNoOtherCalls();
_notify.VerifyNoOtherCalls();
}
[Fact]
@ -131,7 +133,7 @@ public class DesktopControllerTests : ContextualTests
await _desktopController.Initialize();
_notificationManager.Verify(x => x.DonatePrompt());
_notify.Verify(x => x.DonatePrompt());
Assert.True(_config.Get(c => c.HasBeenPromptedForDonation));
DateAsserts.Recent(TimeSpan.FromMilliseconds(1000), _config.Get(c => c.LastDonatePromptDate));
}
@ -150,7 +152,7 @@ public class DesktopControllerTests : ContextualTests
Assert.True(_config.Get(c => c.HasBeenPromptedForDonation));
Assert.Equal(donatePromptDate, _config.Get(c => c.LastDonatePromptDate));
_notificationManager.VerifyNoOtherCalls();
_notify.VerifyNoOtherCalls();
}
[Fact]
@ -187,7 +189,7 @@ public class DesktopControllerTests : ContextualTests
DateAsserts.Recent(TimeSpan.FromMilliseconds(1000), _config.Get(c => c.LastUpdateCheckDate));
_updateChecker.Verify(x => x.CheckForUpdates());
_updateChecker.VerifyNoOtherCalls();
_notificationManager.VerifyNoOtherCalls();
_notify.VerifyNoOtherCalls();
}
[Fact(Skip = "flaky")]
@ -204,9 +206,9 @@ public class DesktopControllerTests : ContextualTests
Assert.True(_config.Get(c => c.HasCheckedForUpdates));
DateAsserts.Recent(TimeSpan.FromMilliseconds(1000), _config.Get(c => c.LastUpdateCheckDate));
_updateChecker.Verify(x => x.CheckForUpdates());
_notificationManager.Verify(x => x.UpdateAvailable(_updateChecker.Object, mockUpdateInfo));
_notify.Verify(x => x.UpdateAvailable(_updateChecker.Object, mockUpdateInfo));
_updateChecker.VerifyNoOtherCalls();
_notificationManager.VerifyNoOtherCalls();
_notify.VerifyNoOtherCalls();
}
[Fact(Skip = "flaky")]
@ -221,7 +223,7 @@ public class DesktopControllerTests : ContextualTests
Assert.False(_config.Get(c => c.HasCheckedForUpdates));
Assert.Null(_config.Get(c => c.LastUpdateCheckDate));
_updateChecker.VerifyNoOtherCalls();
_notificationManager.VerifyNoOtherCalls();
_notify.VerifyNoOtherCalls();
}
[Fact(Skip = "flaky")]
@ -238,7 +240,7 @@ public class DesktopControllerTests : ContextualTests
Assert.True(_config.Get(c => c.HasCheckedForUpdates));
Assert.Equal(updateCheckDate, _config.Get(c => c.LastUpdateCheckDate));
_updateChecker.VerifyNoOtherCalls();
_notificationManager.VerifyNoOtherCalls();
_notify.VerifyNoOtherCalls();
}
[Fact(Skip = "flaky")]
@ -258,8 +260,8 @@ public class DesktopControllerTests : ContextualTests
Assert.True(_config.Get(c => c.HasCheckedForUpdates));
DateAsserts.Recent(TimeSpan.FromMilliseconds(1000), _config.Get(c => c.LastUpdateCheckDate));
_updateChecker.Verify(x => x.CheckForUpdates());
_notificationManager.Verify(x => x.UpdateAvailable(_updateChecker.Object, mockUpdateInfo));
_notify.Verify(x => x.UpdateAvailable(_updateChecker.Object, mockUpdateInfo));
_updateChecker.VerifyNoOtherCalls();
_notificationManager.VerifyNoOtherCalls();
_notify.VerifyNoOtherCalls();
}
}

View File

@ -5,6 +5,7 @@ using Eto.WinForms;
using Eto.WinForms.Forms.ToolBar;
using NAPS2.EtoForms.Desktop;
using NAPS2.EtoForms.Layout;
using NAPS2.EtoForms.Notifications;
using NAPS2.EtoForms.Widgets;
using NAPS2.EtoForms.WinForms;
using NAPS2.ImportExport.Images;
@ -26,7 +27,7 @@ public class WinFormsDesktopForm : DesktopForm
public WinFormsDesktopForm(
Naps2Config config,
DesktopKeyboardShortcuts keyboardShortcuts,
INotificationManager notify,
NotificationManager notificationManager,
CultureHelper cultureHelper,
ColorScheme colorScheme,
IProfileManager profileManager,
@ -41,7 +42,7 @@ public class WinFormsDesktopForm : DesktopForm
DesktopFormProvider desktopFormProvider,
IDesktopSubFormController desktopSubFormController,
DesktopCommands commands)
: base(config, keyboardShortcuts, notify, cultureHelper, colorScheme, profileManager,
: base(config, keyboardShortcuts, notificationManager, cultureHelper, colorScheme, profileManager,
imageList, imageTransfer, thumbnailController, thumbnailProvider, desktopController, desktopScanController,
imageListActions, imageListViewBehavior, desktopFormProvider, desktopSubFormController, commands)
{
@ -98,7 +99,7 @@ public class WinFormsDesktopForm : DesktopForm
FlatStyle = wf.FlatStyle.Flat
};
return L.Overlay(
mouseCatcher.ToEto(),
L.Row(mouseCatcher.ToEto().AlignTrailing()),
base.GetZoomButtons()
);
}

View File

@ -13,7 +13,6 @@ public class WinFormsModule : GuiModule
{
base.Load(builder);
builder.RegisterType<NotificationManager>().As<INotificationManager>().SingleInstance();
builder.RegisterType<PrintDocumentPrinter>().As<IScannedImagePrinter>();
builder.RegisterType<WinFormsDarkModeProvider>().As<IDarkModeProvider>().SingleInstance();

View File

@ -1,12 +0,0 @@
namespace NAPS2.WinForms;
public class DonatePromptNotifyWidget : NotifyWidget
{
public DonatePromptNotifyWidget()
: base(MiscResources.DonatePrompt, MiscResources.Donate, "https://www.naps2.com/donate", null)
{
hideTimer.Interval = 60 * 1000;
}
public override NotifyWidgetBase Clone() => new DonatePromptNotifyWidget();
}

View File

@ -1,16 +0,0 @@
namespace NAPS2.WinForms;
public class ImagesSavedNotifyWidget : NotifyWidget
{
private readonly int _imageCount;
private readonly string _path;
public ImagesSavedNotifyWidget(int imageCount, string path)
: base(string.Format(MiscResources.ImagesSaved, imageCount), Path.GetFileName(path), path, Path.GetDirectoryName(path))
{
_imageCount = imageCount;
_path = path;
}
public override NotifyWidgetBase Clone() => new ImagesSavedNotifyWidget(_imageCount, _path);
}

View File

@ -1,140 +0,0 @@
using System.Drawing;
using Eto.Forms;
using NAPS2.EtoForms;
using NAPS2.EtoForms.Desktop;
using NAPS2.Update;
using wf = System.Windows.Forms;
namespace NAPS2.WinForms;
public class NotificationManager : INotificationManager
{
private const int PADDING_X = 25, PADDING_Y = 25;
private const int SPACING_Y = 20;
private readonly Naps2Config _config;
private readonly List<NotifyWidgetBase?> _slots = new();
private wf.Form? _parentForm;
public NotificationManager(Naps2Config config, DesktopFormProvider desktopFormProvider)
{
_config = config;
desktopFormProvider.DesktopFormChanged += (_, _) =>
{
if (_parentForm != null)
{
_parentForm.Resize -= parentForm_Resize;
}
_parentForm = desktopFormProvider.DesktopForm.ToNative();
_parentForm.Resize += parentForm_Resize;
};
}
public void PdfSaved(string path)
{
Show(new PdfSavedNotifyWidget(path));
}
public void ImagesSaved(int imageCount, string path)
{
if (imageCount == 1)
{
Show(new OneImageSavedNotifyWidget(path));
}
else if (imageCount > 1)
{
Show(new ImagesSavedNotifyWidget(imageCount, path));
}
}
public void DonatePrompt()
{
Show(new DonatePromptNotifyWidget());
}
public void OperationProgress(OperationProgress opModalProgress, IOperation op)
{
Show(new OperationProgressNotifyWidget(opModalProgress, op));
}
public void UpdateAvailable(IUpdateChecker updateChecker, UpdateInfo update)
{
Show(new UpdateAvailableNotifyWidget(updateChecker, update));
}
public void Rebuild()
{
var old = _slots.ToList();
_slots.Clear();
for (int i = 0; i < old.Count; i++)
{
var slot = old[i];
if (slot != null)
{
Show(slot.Clone());
}
}
}
private void Show(NotifyWidgetBase n)
{
if (_config.Get(c => c.DisableSaveNotifications) && n is NotifyWidget)
{
return;
}
Invoker.Current.Invoke(() =>
{
int slot = FillNextSlot(n);
n.Location = GetPosition(n, slot);
n.Resize += parentForm_Resize;
n.BringToFront();
n.HideNotify += (sender, args) => ClearSlot(n);
n.ShowNotify();
});
}
private void parentForm_Resize(object? sender, EventArgs e)
{
for (int i = 0; i < _slots.Count; i++)
{
var slot = _slots[i];
if (slot != null)
{
slot.Location = GetPosition(slot, i);
}
}
}
private void ClearSlot(NotifyWidgetBase n)
{
var index = _slots.IndexOf(n);
if (index != -1)
{
_parentForm!.Controls.Remove(n);
_slots[index] = null;
}
}
private int FillNextSlot(NotifyWidgetBase n)
{
var index = _slots.IndexOf(null);
if (index == -1)
{
index = _slots.Count;
_slots.Add(n);
}
else
{
_slots[index] = n;
}
_parentForm!.Controls.Add(n);
return index;
}
private Point GetPosition(NotifyWidgetBase n, int slot)
{
return new Point(_parentForm!.ClientSize.Width - n.Width - PADDING_X,
_parentForm.ClientSize.Height - n.Height - PADDING_Y - (n.Height + SPACING_Y) * slot);
}
}

View File

@ -1,112 +0,0 @@
using Eto.WinForms;
using NAPS2.EtoForms;
namespace NAPS2.WinForms
{
partial class NotifyWidget
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(NotifyWidget));
this.lblTitle = new System.Windows.Forms.Label();
this.linkLabel1 = new System.Windows.Forms.LinkLabel();
this.btnClose = new System.Windows.Forms.Button();
this.hideTimer = new System.Windows.Forms.Timer(this.components);
this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components);
this.openFolderToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.contextMenuStrip1.SuspendLayout();
this.SuspendLayout();
//
// lblTitle
//
resources.ApplyResources(this.lblTitle, "lblTitle");
this.lblTitle.Name = "lblTitle";
//
// linkLabel1
//
resources.ApplyResources(this.linkLabel1, "linkLabel1");
this.linkLabel1.AutoEllipsis = true;
this.linkLabel1.Name = "linkLabel1";
this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel1_LinkClicked);
//
// btnClose
//
resources.ApplyResources(this.btnClose, "btnClose");
this.btnClose.Cursor = System.Windows.Forms.Cursors.Hand;
this.btnClose.FlatAppearance.BorderSize = 0;
this.btnClose.Image = global::NAPS2.Icons.close.ToBitmap();
this.btnClose.Name = "btnClose";
this.btnClose.UseVisualStyleBackColor = true;
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
//
// hideTimer
//
this.hideTimer.Interval = 5000;
this.hideTimer.Tick += new System.EventHandler(this.hideTimer_Tick);
//
// contextMenuStrip1
//
this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.openFolderToolStripMenuItem});
this.contextMenuStrip1.Name = "contextMenuStrip1";
resources.ApplyResources(this.contextMenuStrip1, "contextMenuStrip1");
//
// openFolderToolStripMenuItem
//
this.openFolderToolStripMenuItem.Name = "openFolderToolStripMenuItem";
resources.ApplyResources(this.openFolderToolStripMenuItem, "openFolderToolStripMenuItem");
this.openFolderToolStripMenuItem.Click += new System.EventHandler(this.openFolderToolStripMenuItem_Click);
//
// NotifyWidget
//
resources.ApplyResources(this, "$this");
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(234)))), ((int)(((byte)(234)))), ((int)(((byte)(234)))));
this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.Controls.Add(this.btnClose);
this.Controls.Add(this.linkLabel1);
this.Controls.Add(this.lblTitle);
this.Name = "NotifyWidget";
this.MouseEnter += new System.EventHandler(this.NotifyWidget_MouseEnter);
this.MouseLeave += new System.EventHandler(this.NotifyWidget_MouseLeave);
this.contextMenuStrip1.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
protected System.Windows.Forms.Label lblTitle;
private System.Windows.Forms.LinkLabel linkLabel1;
private System.Windows.Forms.Button btnClose;
protected System.Windows.Forms.Timer hideTimer;
private System.Windows.Forms.ContextMenuStrip contextMenuStrip1;
private System.Windows.Forms.ToolStripMenuItem openFolderToolStripMenuItem;
}
}

View File

@ -1,98 +0,0 @@
using System.Windows.Forms;
namespace NAPS2.WinForms;
public partial class NotifyWidget : NotifyWidgetBase
{
private readonly string? _linkTarget;
private readonly string? _folderTarget;
public NotifyWidget(string title, string linkLabel, string? linkTarget, string? folderTarget)
{
_linkTarget = linkTarget;
_folderTarget = folderTarget;
InitializeComponent();
lblTitle.Text = title;
linkLabel1.Text = linkLabel;
if (lblTitle.Width > Width - 35)
{
Width = lblTitle.Width + 35;
}
if (lblTitle.Height > Height - 35)
{
Height = lblTitle.Height + 35;
}
if (folderTarget == null)
{
contextMenuStrip1.Enabled = false;
}
openFolderToolStripMenuItem.Text = UiStrings.OpenFolder;
}
private void hideTimer_Tick(object sender, EventArgs e)
{
DoHideNotify();
}
protected void DoHideNotify()
{
InvokeHideNotify();
hideTimer.Stop();
}
private void btnClose_Click(object sender, EventArgs e)
{
DoHideNotify();
}
private void NotifyWidget_MouseEnter(object sender, EventArgs e)
{
hideTimer.Stop();
}
private void NotifyWidget_MouseLeave(object sender, EventArgs e)
{
hideTimer.Start();
}
public override void ShowNotify()
{
hideTimer.Start();
}
protected virtual void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
if (_linkTarget == null)
{
Log.Error("Link target should not be null");
return;
}
if (e.Button == MouseButtons.Right)
{
contextMenuStrip1.Show(linkLabel1, linkLabel1.Location);
}
else
{
Process.Start(new ProcessStartInfo
{
UseShellExecute = true,
FileName = _linkTarget,
Verb = "open"
});
}
}
private void openFolderToolStripMenuItem_Click(object sender, EventArgs e)
{
Process.Start(new ProcessStartInfo
{
UseShellExecute = true,
FileName = _folderTarget,
Verb = "open"
});
}
}

View File

@ -1,261 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="mscorlib" name="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="lblTitle.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="lblTitle.Font" type="System.Drawing.Font, System.Drawing">
<value>Microsoft Sans Serif, 8.25pt, style=Bold</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="lblTitle.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
<value>NoControl</value>
</data>
<data name="lblTitle.Location" type="System.Drawing.Point, System.Drawing">
<value>7, 8</value>
</data>
<data name="lblTitle.MaximumSize" type="System.Drawing.Size, System.Drawing">
<value>200, 0</value>
</data>
<data name="lblTitle.Size" type="System.Drawing.Size, System.Drawing">
<value>0, 13</value>
</data>
<data name="lblTitle.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="&gt;&gt;lblTitle.Name" xml:space="preserve">
<value>lblTitle</value>
</data>
<data name="&gt;&gt;lblTitle.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;lblTitle.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;lblTitle.ZOrder" xml:space="preserve">
<value>3</value>
</data>
<data name="linkLabel1.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Bottom, Left, Right</value>
</data>
<data name="linkLabel1.Location" type="System.Drawing.Point, System.Drawing">
<value>7, 27</value>
</data>
<data name="linkLabel1.Size" type="System.Drawing.Size, System.Drawing">
<value>150, 13</value>
</data>
<data name="linkLabel1.TabIndex" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="&gt;&gt;linkLabel1.Name" xml:space="preserve">
<value>linkLabel1</value>
</data>
<data name="&gt;&gt;linkLabel1.Type" xml:space="preserve">
<value>System.Windows.Forms.LinkLabel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;linkLabel1.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;linkLabel1.ZOrder" xml:space="preserve">
<value>2</value>
</data>
<data name="btnClose.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Right</value>
</data>
<data name="btnClose.FlatStyle" type="System.Windows.Forms.FlatStyle, System.Windows.Forms">
<value>Flat</value>
</data>
<data name="btnClose.Font" type="System.Drawing.Font, System.Drawing">
<value>Microsoft Sans Serif, 12pt</value>
</data>
<data name="btnClose.Location" type="System.Drawing.Point, System.Drawing">
<value>132, 4</value>
</data>
<data name="btnClose.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>0, 0, 1, 1</value>
</data>
<data name="btnClose.Size" type="System.Drawing.Size, System.Drawing">
<value>20, 20</value>
</data>
<data name="btnClose.TabIndex" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="&gt;&gt;btnClose.Name" xml:space="preserve">
<value>btnClose</value>
</data>
<data name="&gt;&gt;btnClose.Type" xml:space="preserve">
<value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;btnClose.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;btnClose.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<metadata name="hideTimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="contextMenuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>123, 17</value>
</metadata>
<data name="contextMenuStrip1.Size" type="System.Drawing.Size, System.Drawing">
<value>140, 26</value>
</data>
<data name="&gt;&gt;contextMenuStrip1.Name" xml:space="preserve">
<value>contextMenuStrip1</value>
</data>
<data name="&gt;&gt;contextMenuStrip1.Type" xml:space="preserve">
<value>System.Windows.Forms.ContextMenuStrip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="openFolderToolStripMenuItem.Size" type="System.Drawing.Size, System.Drawing">
<value>139, 22</value>
</data>
<data name="openFolderToolStripMenuItem.Text" xml:space="preserve">
<value>Open Folder</value>
</data>
<metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<data name="$this.AutoScaleDimensions" type="System.Drawing.SizeF, System.Drawing">
<value>6, 13</value>
</data>
<data name="$this.Size" type="System.Drawing.Size, System.Drawing">
<value>156, 46</value>
</data>
<data name="&gt;&gt;hideTimer.Name" xml:space="preserve">
<value>hideTimer</value>
</data>
<data name="&gt;&gt;hideTimer.Type" xml:space="preserve">
<value>System.Windows.Forms.Timer, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;openFolderToolStripMenuItem.Name" xml:space="preserve">
<value>openFolderToolStripMenuItem</value>
</data>
<data name="&gt;&gt;openFolderToolStripMenuItem.Type" xml:space="preserve">
<value>System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;$this.Name" xml:space="preserve">
<value>NotifyWidget</value>
</data>
<data name="&gt;&gt;$this.Type" xml:space="preserve">
<value>System.Windows.Forms.UserControl, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>

View File

@ -1,19 +0,0 @@
using System.Windows.Forms;
namespace NAPS2.WinForms;
public class NotifyWidgetBase : UserControl
{
public event EventHandler? HideNotify;
protected void InvokeHideNotify()
{
Invoker.Current.Invoke(() => HideNotify?.Invoke(this, EventArgs.Empty));
}
public virtual void ShowNotify()
{
}
public virtual NotifyWidgetBase Clone() => throw new NotImplementedException();
}

View File

@ -1,14 +0,0 @@
namespace NAPS2.WinForms;
public class OneImageSavedNotifyWidget : NotifyWidget
{
private readonly string _path;
public OneImageSavedNotifyWidget(string path)
: base(MiscResources.ImageSaved, Path.GetFileName(path), path, Path.GetDirectoryName(path))
{
_path = path;
}
public override NotifyWidgetBase Clone() => new OneImageSavedNotifyWidget(_path);
}

View File

@ -1,99 +0,0 @@
namespace NAPS2.WinForms
{
partial class OperationProgressNotifyWidget
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(OperationProgressNotifyWidget));
this.lblTitle = new System.Windows.Forms.Label();
this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components);
this.cancelToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.progressBar = new System.Windows.Forms.ProgressBar();
this.lblNumber = new System.Windows.Forms.Label();
this.contextMenuStrip1.SuspendLayout();
this.SuspendLayout();
//
// lblTitle
//
resources.ApplyResources(this.lblTitle, "lblTitle");
this.lblTitle.Name = "lblTitle";
this.lblTitle.MouseClick += new System.Windows.Forms.MouseEventHandler(this.OperationProgressNotifyWidget_Click);
//
// contextMenuStrip1
//
this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.cancelToolStripMenuItem});
this.contextMenuStrip1.Name = "contextMenuStrip1";
resources.ApplyResources(this.contextMenuStrip1, "contextMenuStrip1");
//
// cancelToolStripMenuItem
//
this.cancelToolStripMenuItem.Name = "cancelToolStripMenuItem";
resources.ApplyResources(this.cancelToolStripMenuItem, "cancelToolStripMenuItem");
this.cancelToolStripMenuItem.Click += new System.EventHandler(this.cancelToolStripMenuItem_Click);
//
// progressBar
//
resources.ApplyResources(this.progressBar, "progressBar");
this.progressBar.MarqueeAnimationSpeed = 50;
this.progressBar.Name = "progressBar";
this.progressBar.Style = System.Windows.Forms.ProgressBarStyle.Continuous;
this.progressBar.MouseClick += new System.Windows.Forms.MouseEventHandler(this.OperationProgressNotifyWidget_Click);
//
// lblNumber
//
resources.ApplyResources(this.lblNumber, "lblNumber");
this.lblNumber.Name = "lblNumber";
//
// OperationProgressNotifyWidget
//
resources.ApplyResources(this, "$this");
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(234)))), ((int)(((byte)(234)))), ((int)(((byte)(234)))));
this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.ContextMenuStrip = this.contextMenuStrip1;
this.Controls.Add(this.lblNumber);
this.Controls.Add(this.progressBar);
this.Controls.Add(this.lblTitle);
this.Name = "OperationProgressNotifyWidget";
this.MouseClick += new System.Windows.Forms.MouseEventHandler(this.OperationProgressNotifyWidget_Click);
this.contextMenuStrip1.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
protected System.Windows.Forms.Label lblTitle;
private System.Windows.Forms.ContextMenuStrip contextMenuStrip1;
private System.Windows.Forms.ToolStripMenuItem cancelToolStripMenuItem;
private System.Windows.Forms.ProgressBar progressBar;
private System.Windows.Forms.Label lblNumber;
}
}

View File

@ -1,74 +0,0 @@
using System.Windows.Forms;
namespace NAPS2.WinForms;
public partial class OperationProgressNotifyWidget : NotifyWidgetBase
{
private readonly OperationProgress _operationProgress;
private readonly IOperation _op;
public OperationProgressNotifyWidget(OperationProgress operationProgress, IOperation op)
{
InitializeComponent();
_operationProgress = operationProgress;
_op = op;
cancelToolStripMenuItem.Text = UiStrings.Cancel;
cancelToolStripMenuItem.Visible = op.AllowCancel;
op.StatusChanged += Op_StatusChanged;
op.Finished += Op_Finished;
}
public override void ShowNotify() => DisplayProgress();
public override NotifyWidgetBase Clone() => new OperationProgressNotifyWidget(_operationProgress, _op);
private void DisplayProgress()
{
var lblNumberRight = lblNumber.Right;
WinFormsOperationProgress.RenderStatus(_op, lblTitle, lblNumber, progressBar);
if (_op.Status?.IndeterminateProgress != true)
{
// Don't display the number if the progress bar is precise
// Otherwise, the widget will be too cluttered
// The number is only shown for OcrOperation at the moment
lblNumber.Text = "";
}
lblNumber.Left = lblNumberRight - lblNumber.Width;
Width = Math.Max(Width, lblTitle.Width + lblNumber.Width + 22);
Height = Math.Max(Height, lblTitle.Height + 35);
}
private void DoHideNotify()
{
_op.StatusChanged -= Op_StatusChanged;
_op.Finished -= Op_Finished;
InvokeHideNotify();
}
private void Op_StatusChanged(object? sender, EventArgs e)
{
Invoker.Current.Invoke(DisplayProgress);
}
private void Op_Finished(object? sender, EventArgs e)
{
DoHideNotify();
}
private void cancelToolStripMenuItem_Click(object? sender, EventArgs e)
{
_op.Cancel();
cancelToolStripMenuItem.Enabled = false;
}
private void OperationProgressNotifyWidget_Click(object? sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
DoHideNotify();
_operationProgress.ShowModalProgress(_op);
}
}
}

View File

@ -1,246 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="mscorlib" name="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="lblTitle.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="lblTitle.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
<value>NoControl</value>
</data>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="lblTitle.Location" type="System.Drawing.Point, System.Drawing">
<value>7, 8</value>
</data>
<data name="lblTitle.MaximumSize" type="System.Drawing.Size, System.Drawing">
<value>200, 0</value>
</data>
<data name="lblTitle.Size" type="System.Drawing.Size, System.Drawing">
<value>0, 13</value>
</data>
<data name="lblTitle.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="&gt;&gt;lblTitle.Name" xml:space="preserve">
<value>lblTitle</value>
</data>
<data name="&gt;&gt;lblTitle.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;lblTitle.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;lblTitle.ZOrder" xml:space="preserve">
<value>3</value>
</data>
<metadata name="contextMenuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>123, 17</value>
</metadata>
<data name="cancelToolStripMenuItem.Size" type="System.Drawing.Size, System.Drawing">
<value>110, 22</value>
</data>
<data name="cancelToolStripMenuItem.Text" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="contextMenuStrip1.Size" type="System.Drawing.Size, System.Drawing">
<value>111, 26</value>
</data>
<data name="&gt;&gt;contextMenuStrip1.Name" xml:space="preserve">
<value>contextMenuStrip1</value>
</data>
<data name="&gt;&gt;contextMenuStrip1.Type" xml:space="preserve">
<value>System.Windows.Forms.ContextMenuStrip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="progressBar.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Bottom, Left, Right</value>
</data>
<data name="progressBar.Location" type="System.Drawing.Point, System.Drawing">
<value>7, 26</value>
</data>
<data name="progressBar.Size" type="System.Drawing.Size, System.Drawing">
<value>138, 11</value>
</data>
<data name="progressBar.TabIndex" type="System.Int32, mscorlib">
<value>3</value>
</data>
<data name="&gt;&gt;progressBar.Name" xml:space="preserve">
<value>progressBar</value>
</data>
<data name="&gt;&gt;progressBar.Type" xml:space="preserve">
<value>System.Windows.Forms.ProgressBar, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;progressBar.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;progressBar.ZOrder" xml:space="preserve">
<value>2</value>
</data>
<data name="lblNumber.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Right</value>
</data>
<data name="lblNumber.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="lblNumber.Location" type="System.Drawing.Point, System.Drawing">
<value>145, 8</value>
</data>
<data name="lblNumber.Size" type="System.Drawing.Size, System.Drawing">
<value>0, 13</value>
</data>
<data name="lblNumber.TabIndex" type="System.Int32, mscorlib">
<value>4</value>
</data>
<data name="lblNumber.TextAlign" type="System.Drawing.ContentAlignment, System.Drawing">
<value>TopRight</value>
</data>
<data name="&gt;&gt;lblNumber.Name" xml:space="preserve">
<value>lblNumber</value>
</data>
<data name="&gt;&gt;lblNumber.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;lblNumber.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;lblNumber.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<data name="$this.AutoScaleDimensions" type="System.Drawing.SizeF, System.Drawing">
<value>6, 13</value>
</data>
<data name="$this.Size" type="System.Drawing.Size, System.Drawing">
<value>150, 40</value>
</data>
<data name="&gt;&gt;cancelToolStripMenuItem.Name" xml:space="preserve">
<value>cancelToolStripMenuItem</value>
</data>
<data name="&gt;&gt;cancelToolStripMenuItem.Type" xml:space="preserve">
<value>System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;$this.Name" xml:space="preserve">
<value>OperationProgressNotifyWidget</value>
</data>
<data name="&gt;&gt;$this.Type" xml:space="preserve">
<value>NAPS2.WinForms.NotifyWidgetBase, NAPS2.Core, Version=6.0.1.27018, Culture=neutral, PublicKeyToken=null</value>
</data>
</root>

View File

@ -1,14 +0,0 @@
namespace NAPS2.WinForms;
public class PdfSavedNotifyWidget : NotifyWidget
{
private readonly string _path;
public PdfSavedNotifyWidget(string path)
: base(MiscResources.PdfSaved, Path.GetFileName(path), path, Path.GetDirectoryName(path))
{
_path = path;
}
public override NotifyWidgetBase Clone() => new PdfSavedNotifyWidget(_path);
}

View File

@ -1,27 +0,0 @@
using System.Windows.Forms;
using NAPS2.Update;
namespace NAPS2.WinForms;
public class UpdateAvailableNotifyWidget : NotifyWidget
{
private readonly IUpdateChecker _updateChecker;
private readonly UpdateInfo _update;
public UpdateAvailableNotifyWidget(IUpdateChecker updateChecker, UpdateInfo update)
: base(MiscResources.UpdateAvailable, string.Format(MiscResources.Install, update.Name), null, null)
{
_updateChecker = updateChecker;
_update = update;
hideTimer.Interval = 60 * 1000;
}
public override NotifyWidgetBase Clone() => new UpdateAvailableNotifyWidget(_updateChecker, _update);
protected override void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
_updateChecker.StartUpdate(_update);
DoHideNotify();
}
}

View File

@ -1,132 +0,0 @@
using System.Collections.Immutable;
using System.Windows.Forms;
using NAPS2.EtoForms;
using NAPS2.EtoForms.Ui;
namespace NAPS2.WinForms;
public class WinFormsOperationProgress : OperationProgress
{
private readonly IFormFactory _formFactory;
private readonly INotificationManager _notificationManager;
private readonly Naps2Config _config;
private readonly HashSet<IOperation> _activeOperations = new HashSet<IOperation>();
public WinFormsOperationProgress(IFormFactory formFactory, INotificationManager notificationManager, Naps2Config config)
{
_formFactory = formFactory;
_notificationManager = notificationManager;
_config = config;
}
public override void Attach(IOperation op)
{
lock (this)
{
if (!_activeOperations.Contains(op))
{
_activeOperations.Add(op);
op.Finished += (sender, args) => _activeOperations.Remove(op);
if (op.IsFinished) _activeOperations.Remove(op);
}
}
}
public override void ShowProgress(IOperation op)
{
if (_config.Get(c => c.BackgroundOperations).Contains(op.GetType().Name))
{
ShowBackgroundProgress(op);
}
else
{
ShowModalProgress(op);
}
}
public override void ShowModalProgress(IOperation op)
{
Attach(op);
var bgOps = _config.Get(c => c.BackgroundOperations) ?? ImmutableHashSet<string>.Empty;
bgOps = bgOps.Remove(op.GetType().Name);
_config.User.Set(c => c.BackgroundOperations, bgOps);
if (!op.IsFinished)
{
var form = _formFactory.Create<ProgressForm>();
form.Operation = op;
form.ShowModal();
}
if (!op.IsFinished)
{
ShowBackgroundProgress(op);
}
}
public override void ShowBackgroundProgress(IOperation op)
{
Attach(op);
var bgOps = _config.Get(c => c.BackgroundOperations) ?? ImmutableHashSet<string>.Empty;
bgOps = bgOps.Add(op.GetType().Name);
_config.User.Set(c => c.BackgroundOperations, bgOps);
if (!op.IsFinished)
{
Invoker.Current.Invoke(() => _notificationManager.OperationProgress(this, op));
}
}
public static void RenderStatus(IOperation op, Label textLabel, Label numberLabel, ProgressBar progressBar)
{
var status = op.Status ?? new OperationStatus();
textLabel.Text = status.StatusText;
progressBar.Style = status.MaxProgress == 1 || status.IndeterminateProgress
? ProgressBarStyle.Marquee
: ProgressBarStyle.Continuous;
if (status.MaxProgress == 1 || status.ProgressType == OperationProgressType.None)
{
numberLabel.Text = "";
}
else if (status.MaxProgress == 0)
{
numberLabel.Text = "";
progressBar.Maximum = 1;
progressBar.Value = 0;
}
else if (status.ProgressType == OperationProgressType.BarOnly)
{
numberLabel.Text = "";
progressBar.Maximum = status.MaxProgress;
progressBar.Value = status.CurrentProgress;
}
else
{
numberLabel.Text = status.ProgressType == OperationProgressType.MB
? string.Format(MiscResources.SizeProgress, (status.CurrentProgress / 1000000.0).ToString("f1"), (status.MaxProgress / 1000000.0).ToString("f1"))
: string.Format(MiscResources.ProgressFormat, status.CurrentProgress, status.MaxProgress);
progressBar.Maximum = status.MaxProgress;
progressBar.Value = status.CurrentProgress;
}
// Force the progress bar to render immediately
if (progressBar.Value < progressBar.Maximum)
{
progressBar.Value += 1;
progressBar.Value -= 1;
}
}
public override List<IOperation> ActiveOperations
{
get
{
lock (_activeOperations)
{
return _activeOperations.ToList();
}
}
}
}

View File

@ -1,5 +1,7 @@
using System.Threading;
using Eto.Forms;
using NAPS2.EtoForms.Notifications;
using NAPS2.EtoForms.Widgets;
using NAPS2.ImportExport;
using NAPS2.ImportExport.Images;
using NAPS2.Platform.Windows;
@ -22,7 +24,7 @@ public class DesktopController
private readonly IOperationFactory _operationFactory;
private readonly StillImage _stillImage;
private readonly IUpdateChecker _updateChecker;
private readonly INotificationManager _notify;
private readonly Notify _notify;
private readonly ImageTransfer _imageTransfer;
private readonly ImageClipboard _imageClipboard;
private readonly ImageListActions _imageListActions;
@ -41,7 +43,7 @@ public class DesktopController
RecoveryStorageManager recoveryStorageManager, ThumbnailController thumbnailController,
OperationProgress operationProgress, Naps2Config config, IOperationFactory operationFactory,
StillImage stillImage,
IUpdateChecker updateChecker, INotificationManager notify, ImageTransfer imageTransfer,
IUpdateChecker updateChecker, Notify notify, ImageTransfer imageTransfer,
ImageClipboard imageClipboard, ImageListActions imageListActions,
DialogHelper dialogHelper,
DesktopImagesController desktopImagesController, IDesktopScanController desktopScanController,

View File

@ -1,3 +1,5 @@
using NAPS2.EtoForms.Notifications;
using NAPS2.EtoForms.Widgets;
using NAPS2.ImportExport;
namespace NAPS2.EtoForms.Desktop;
@ -10,11 +12,11 @@ public class ImageListActions
private readonly Naps2Config _config;
private readonly ThumbnailController _thumbnailController;
private readonly IExportController _exportController;
private readonly INotificationManager _notify;
private readonly Notify _notify;
public ImageListActions(UiImageList imageList, IOperationFactory operationFactory,
OperationProgress operationProgress, Naps2Config config, ThumbnailController thumbnailController,
IExportController exportController, INotificationManager notify)
IExportController exportController, Notify notify)
{
_imageList = imageList;
_operationFactory = operationFactory;

View File

@ -1,20 +1,22 @@
using Eto.Forms;
using NAPS2.EtoForms.Notifications;
using NAPS2.EtoForms.Ui;
using NAPS2.EtoForms.Widgets;
namespace NAPS2.EtoForms;
public class EtoOperationProgress : OperationProgress
{
private readonly IFormFactory _formFactory;
private readonly INotificationManager _notificationManager;
private readonly Notify _notify;
private readonly Naps2Config _config;
private readonly HashSet<IOperation> _activeOperations = new();
public EtoOperationProgress(IFormFactory formFactory, INotificationManager notificationManager, Naps2Config config)
public EtoOperationProgress(IFormFactory formFactory, Notify notify, Naps2Config config)
{
_formFactory = formFactory;
_notificationManager = notificationManager;
_notify = notify;
_config = config;
}
@ -78,7 +80,7 @@ public class EtoOperationProgress : OperationProgress
if (!op.IsFinished)
{
Invoker.Current.Invoke(() => _notificationManager.OperationProgress(this, op));
Invoker.Current.Invoke(() => _notify.OperationProgress(this, op));
}
}

View File

@ -1,11 +0,0 @@
using NAPS2.Update;
namespace NAPS2.EtoForms;
public interface INotificationManager : ISaveNotify
{
void DonatePrompt();
void OperationProgress(OperationProgress opModalProgress, IOperation op);
void UpdateAvailable(IUpdateChecker updateChecker, UpdateInfo update);
void Rebuild();
}

View File

@ -0,0 +1,60 @@
using Eto.Drawing;
using Eto.Forms;
namespace NAPS2.EtoForms.Notifications;
public class CloseButton : Drawable
{
private const int CLOSE_BUTTON_PADDING = 5;
private static readonly Pen CloseButtonPen = new(new Color(0.7f, 0.7f, 0.7f), 3);
private static readonly Color DefaultBackground = new(0.95f, 0.95f, 0.95f);
private static readonly Color ActiveBackground = new(0.8f, 0.8f, 0.8f);
private static readonly Color HoverBackground = new(0.87f, 0.87f, 0.87f);
private bool _hover;
private bool _active;
public CloseButton()
{
Cursor = Cursors.Pointer;
Paint += OnPaint;
MouseEnter += (_, _) =>
{
_hover = true;
Invalidate();
};
MouseLeave += (_, _) =>
{
_hover = false;
Invalidate();
};
MouseDown += (_, _) =>
{
_active = true;
Invalidate();
};
MouseUp += (_, _) =>
{
var actualHover = new Rectangle(0, 0, Width, Height).Contains(Point.Round(PointFromScreen(Mouse.Position)));
if (_active && _hover && actualHover)
{
Click?.Invoke(this, EventArgs.Empty);
}
_active = false;
Invalidate();
};
}
private void OnPaint(object? sender, PaintEventArgs e)
{
var clearColor = _active && _hover ? ActiveBackground : _hover ? HoverBackground : DefaultBackground;
e.Graphics.Clear(clearColor);
var w = e.ClipRectangle.Width;
var h = e.ClipRectangle.Height;
var p = CLOSE_BUTTON_PADDING;
e.Graphics.DrawLine(CloseButtonPen, p - 1, p - 1, w - p, h - p);
e.Graphics.DrawLine(CloseButtonPen, w - p, p - 1, p - 1, h - p);
}
public event EventHandler? Click;
}

View File

@ -0,0 +1,17 @@
namespace NAPS2.EtoForms.Notifications;
public class DonateNotification : LinkNotification
{
private const string DONATE_URL = "https://www.naps2.com/donate";
public DonateNotification() : base(MiscResources.DonatePrompt, MiscResources.Donate, DONATE_URL, null)
{
HideTimeout = HIDE_LONG;
}
protected override void LinkClick()
{
base.LinkClick();
Manager!.Hide(this);
}
}

View File

@ -0,0 +1,60 @@
using Eto.Drawing;
using Eto.Forms;
using NAPS2.EtoForms.Layout;
namespace NAPS2.EtoForms.Notifications;
public class LinkNotification : Notification
{
private readonly string? _linkTarget;
private readonly string? _folderTarget;
private readonly Label _label = new() { BackgroundColor = BackgroundColor };
private readonly LinkButton _link = new() { BackgroundColor = BackgroundColor };
private readonly ContextMenu _contextMenu = new();
protected LinkNotification(string title, string linkLabel, string? linkTarget, string? folderTarget)
{
_label.Text = title;
_label.Font = new Font(_label.Font.Family, _label.Font.Size, FontStyle.Bold);
_link.Text = linkLabel;
_linkTarget = linkTarget;
_folderTarget = folderTarget;
if (_folderTarget != null)
{
_contextMenu.Items.Add(new ActionCommand(OpenFolder) { Text = UiStrings.OpenFolder });
}
_link.Click += (_, _) => LinkClick();
_link.MouseUp += (_, args) =>
{
if (args.Buttons == MouseButtons.Alternate)
{
LinkRightClick();
}
};
}
protected override LayoutElement PrimaryContent => _label;
protected override LayoutElement SecondaryContent => _link;
protected virtual void LinkClick()
{
ProcessHelper.OpenFile(_linkTarget!);
}
protected virtual void LinkRightClick()
{
if (_folderTarget != null)
{
_contextMenu.Show(_link);
}
}
protected virtual void OpenFolder()
{
ProcessHelper.OpenFolder(_folderTarget!);
}
}

View File

@ -0,0 +1,121 @@
using System.Threading;
using Eto.Drawing;
using Eto.Forms;
using NAPS2.EtoForms.Layout;
namespace NAPS2.EtoForms.Notifications;
public abstract class Notification : IDisposable
{
// TODO: Get from color scheme
protected static readonly Color BackgroundColor = new(0.95f, 0.95f, 0.95f);
private static readonly Color BorderColor = new(0.7f, 0.7f, 0.7f);
private const int BORDER_RADIUS = 7;
private const int CLOSE_BUTTON_SIZE = 18;
protected const int HIDE_LONG = 60 * 1000;
protected const int HIDE_SHORT = 5 * 1000;
protected int HideTimeout { get; set; }
protected bool ShowClose { get; set; } = true;
public NotificationManager? Manager { get; set; }
protected abstract LayoutElement PrimaryContent { get; }
protected abstract LayoutElement SecondaryContent { get; }
public LayoutElement CreateView()
{
var drawable = new Drawable();
drawable.Paint += DrawableOnPaint;
var closeButton = new CloseButton();
closeButton.Click += (_, _) => Manager!.Hide(this);
drawable.MouseUp += (_, _) => NotificationClicked();
drawable.Load += (_, _) => SetUpHideTimeout(drawable);
return L.Overlay(
drawable,
L.Column(
L.Row(
PrimaryContent,
ShowClose ? C.Spacer().Width(CLOSE_BUTTON_SIZE) : C.None()),
SecondaryContent
).Padding(10, 8, 10, 8),
ShowClose
? L.Column(
L.Row(
C.Filler(),
closeButton.Size(CLOSE_BUTTON_SIZE, CLOSE_BUTTON_SIZE)
),
C.Filler()
).Padding(5, 5, 5, 5)
: C.None());
}
private void SetUpHideTimeout(Control control)
{
Timer CreateTimer()
{
return new Timer(_ =>
{
Manager!.Hide(this);
}, null, HideTimeout, -1);
}
if (HideTimeout > 0)
{
// Don't start the timer until the user is interacting with the window
void StartTimer(object? sender, EventArgs e)
{
Manager!.TimersStarting -= StartTimer;
control.ParentWindow.MouseMove -= StartTimer;
var timer = CreateTimer();
control.MouseEnter += (_, _) => timer.Dispose();
control.MouseLeave += (_, _) => timer = CreateTimer();
}
Manager!.TimersStarting += StartTimer;
}
}
protected virtual void NotificationClicked()
{
}
private static void DrawableOnPaint(object? sender, PaintEventArgs e)
{
var w = e.ClipRectangle.Width;
var h = e.ClipRectangle.Height;
var r = BORDER_RADIUS;
var d = r * 2;
var q = r / 2;
// TODO: Color scheme background
e.Graphics.Clear(Colors.White);
// Corners
e.Graphics.FillEllipse(BackgroundColor, -1, -1, d, d);
e.Graphics.FillEllipse(BackgroundColor, w - d, -1, d, d);
e.Graphics.FillEllipse(BackgroundColor, -1, h - d, d, d);
e.Graphics.FillEllipse(BackgroundColor, w - d, h - d, d, d);
// Corner borders
e.Graphics.DrawEllipse(BorderColor, -1, -1, d, d);
e.Graphics.DrawEllipse(BorderColor, w - d, -1, d, d);
e.Graphics.DrawEllipse(BorderColor, -1, h - d, d, d);
e.Graphics.DrawEllipse(BorderColor, w - d, h - d, d, d);
// Middle
e.Graphics.FillRectangle(BackgroundColor, r, r, w - d, h - d);
// Sides
e.Graphics.FillRectangle(BackgroundColor, 0, r, r, h - d);
e.Graphics.FillRectangle(BackgroundColor, r, 0, w - d, r);
e.Graphics.FillRectangle(BackgroundColor, w - r, r, r, h - d);
e.Graphics.FillRectangle(BackgroundColor, r, h - r, w - d, r);
// Side borders
e.Graphics.DrawLine(BorderColor, 0, q, 0, h - q);
e.Graphics.DrawLine(BorderColor, q, 0, w - q, 0);
e.Graphics.DrawLine(BorderColor, w - 1, q, w - 1, h - q);
e.Graphics.DrawLine(BorderColor, q, h - 1, w - q, h - 1);
}
public virtual void Dispose()
{
}
}

View File

@ -0,0 +1,36 @@
using NAPS2.EtoForms.Layout;
namespace NAPS2.EtoForms.Notifications;
public class NotificationManager
{
private readonly Dictionary<Notification, LayoutElement> _items = new();
public LayoutColumn Column { get; } = L.Column(C.Filler()).Spacing(20).Padding(15);
public event EventHandler? Updated;
public event EventHandler? TimersStarting;
public void Show(Notification notification)
{
notification.Manager = this;
var item = notification.CreateView();
_items[notification] = item;
Column.Children.Add(item);
Updated?.Invoke(this, EventArgs.Empty);
}
public void Hide(Notification notification)
{
if (!_items.ContainsKey(notification)) return;
Column.Children.Remove(_items[notification]);
Updated?.Invoke(this, EventArgs.Empty);
notification.Dispose();
}
public void StartTimers()
{
TimersStarting?.Invoke(this, EventArgs.Empty);
}
}

View File

@ -0,0 +1,41 @@
using NAPS2.Update;
namespace NAPS2.EtoForms.Notifications;
public class Notify : ISaveNotify
{
private readonly NotificationManager _notificationManager;
public Notify(NotificationManager notificationManager)
{
_notificationManager = notificationManager;
}
public void PdfSaved(string path)
{
_notificationManager.Show(new SaveNotification(MiscResources.PdfSaved, path));
}
public void ImagesSaved(int imageCount, string path)
{
var title = imageCount == 1
? MiscResources.ImageSaved
: string.Format(MiscResources.ImagesSaved, imageCount);
_notificationManager.Show(new SaveNotification(title, path));
}
public void DonatePrompt()
{
_notificationManager.Show(new DonateNotification());
}
public void OperationProgress(OperationProgress progress, IOperation op)
{
_notificationManager.Show(new ProgressNotification(progress, op));
}
public void UpdateAvailable(IUpdateChecker updateChecker, UpdateInfo update)
{
_notificationManager.Show(new UpdateNotification(updateChecker, update));
}
}

View File

@ -0,0 +1,72 @@
using Eto.Forms;
using NAPS2.EtoForms.Layout;
namespace NAPS2.EtoForms.Notifications;
public class ProgressNotification : Notification
{
private readonly OperationProgress _operationProgress;
private readonly IOperation _op;
private readonly Label _textLabel = new();
private readonly Label _numberLabel = new();
private readonly ProgressBar _progressBar = new();
private readonly LayoutVisibility _numberVis = new(true);
public ProgressNotification(OperationProgress operationProgress, IOperation op)
{
_operationProgress = operationProgress;
_op = op;
ShowClose = false;
op.StatusChanged += OnStatusChanged;
op.Finished += OnFinished;
if (op.IsFinished)
{
Invoker.Current.Invoke(() => Manager?.Hide(this));
}
_textLabel.MouseUp += (_, _) => NotificationClicked();
_numberLabel.MouseUp += (_, _) => NotificationClicked();
_progressBar.MouseUp += (_, _) => NotificationClicked();
UpdateStatus();
}
private void OnStatusChanged(object? sender, EventArgs eventArgs)
{
Invoker.Current.Invoke(UpdateStatus);
}
private void OnFinished(object? sender, EventArgs e)
{
Invoker.Current.Invoke(() => Manager?.Hide(this));
}
private void UpdateStatus()
{
EtoOperationProgress.RenderStatus(_op, _textLabel, _numberLabel, _progressBar);
// Don't display the number if the progress bar is precise
// Otherwise, the widget will be too cluttered
// The number is only shown for OcrOperation at the moment
_numberVis.IsVisible = _op.Status?.IndeterminateProgress == true;
}
protected override void NotificationClicked()
{
Manager!.Hide(this);
_operationProgress.ShowModalProgress(_op);
}
public override void Dispose()
{
base.Dispose();
_op.StatusChanged -= OnStatusChanged;
_op.Finished -= OnFinished;
}
protected override LayoutElement PrimaryContent => L.Row(
_textLabel,
C.Filler().Visible(_numberVis),
_numberLabel.Visible(_numberVis));
protected override LayoutElement SecondaryContent => _progressBar.MaxHeight(10);
}

View File

@ -0,0 +1,10 @@
namespace NAPS2.EtoForms.Notifications;
public class SaveNotification : LinkNotification
{
public SaveNotification(string message, string path)
: base(message, Path.GetFileName(path), path, Path.GetDirectoryName(path))
{
HideTimeout = HIDE_SHORT;
}
}

View File

@ -0,0 +1,26 @@
using NAPS2.Update;
namespace NAPS2.EtoForms.Notifications;
public class UpdateNotification : LinkNotification
{
private readonly IUpdateChecker _updateChecker;
private readonly UpdateInfo _update;
public UpdateNotification(IUpdateChecker updateChecker, UpdateInfo update)
: base(
MiscResources.UpdateAvailable,
string.Format(MiscResources.Install, update.Name),
null, null)
{
_updateChecker = updateChecker;
_update = update;
HideTimeout = HIDE_LONG;
}
protected override void LinkClick()
{
_updateChecker.StartUpdate(_update);
Manager!.Hide(this);
}
}

View File

@ -5,6 +5,7 @@ using Eto.Drawing;
using Eto.Forms;
using NAPS2.EtoForms.Desktop;
using NAPS2.EtoForms.Layout;
using NAPS2.EtoForms.Notifications;
using NAPS2.EtoForms.Widgets;
using NAPS2.ImportExport.Images;
using NAPS2.Scan;
@ -14,7 +15,7 @@ namespace NAPS2.EtoForms.Ui;
public abstract class DesktopForm : EtoFormBase
{
private readonly DesktopKeyboardShortcuts _keyboardShortcuts;
private readonly INotificationManager _notify;
private readonly NotificationManager _notificationManager;
private readonly CultureHelper _cultureHelper;
protected readonly ColorScheme _colorScheme;
private readonly IProfileManager _profileManager;
@ -37,7 +38,7 @@ public abstract class DesktopForm : EtoFormBase
public DesktopForm(
Naps2Config config,
DesktopKeyboardShortcuts keyboardShortcuts,
INotificationManager notify,
NotificationManager notificationManager,
CultureHelper cultureHelper,
ColorScheme colorScheme,
IProfileManager profileManager,
@ -54,7 +55,7 @@ public abstract class DesktopForm : EtoFormBase
DesktopCommands commands) : base(config)
{
_keyboardShortcuts = keyboardShortcuts;
_notify = notify;
_notificationManager = notificationManager;
_cultureHelper = cultureHelper;
_colorScheme = colorScheme;
_profileManager = profileManager;
@ -95,6 +96,7 @@ public abstract class DesktopForm : EtoFormBase
KeyDown += OnKeyDown;
_listView.Control.KeyDown += OnKeyDown;
_listView.Control.MouseWheel += ListViewMouseWheel;
_listView.Control.MouseMove += ListViewMouseMove;
//
// Shown += FDesktop_Shown;
@ -107,6 +109,7 @@ public abstract class DesktopForm : EtoFormBase
ImageList.SelectionChanged += ImageList_SelectionChanged;
ImageList.ImagesUpdated += ImageList_ImagesUpdated;
_profileManager.ProfilesUpdated += ProfileManager_ProfilesUpdated;
_notificationManager.Updated += (_, _) => LayoutController.Invalidate();
}
protected override void BuildLayout()
@ -121,7 +124,10 @@ public abstract class DesktopForm : EtoFormBase
GetMainContent(),
L.Column(
C.Filler(),
L.Row(GetZoomButtons(), C.Filler())
L.Row(
GetZoomButtons(),
C.Filler(),
_notificationManager.Column)
).Padding(10)
);
@ -382,7 +388,7 @@ public abstract class DesktopForm : EtoFormBase
EtoPlatform.Current.ConfigureZoomButton(zoomIn);
var zoomOut = C.ImageButton(Commands.ZoomOut);
EtoPlatform.Current.ConfigureZoomButton(zoomOut);
return L.Row(zoomOut, zoomIn).Spacing(-1);
return L.Row(zoomOut.AlignTrailing(), zoomIn.AlignTrailing()).Spacing(-1);
}
private void InitLanguageDropdown()
@ -486,6 +492,11 @@ public abstract class DesktopForm : EtoFormBase
}
}
private void ListViewMouseMove(object? sender, MouseEventArgs e)
{
_notificationManager.StartTimers();
}
protected virtual void SetThumbnailSpacing(int thumbnailSize)
{
}

View File

@ -1,6 +1,8 @@
using Autofac;
using NAPS2.EtoForms;
using NAPS2.EtoForms.Desktop;
using NAPS2.EtoForms.Notifications;
using NAPS2.EtoForms.Widgets;
using NAPS2.ImportExport;
using NAPS2.Pdf;
using NAPS2.Scan;
@ -20,7 +22,8 @@ public class GuiModule : Module
builder.RegisterType<EtoDialogHelper>().As<DialogHelper>();
builder.RegisterType<EtoDevicePrompt>().As<IDevicePrompt>();
builder.RegisterType<EtoPdfPasswordProvider>().As<IPdfPasswordProvider>();
builder.Register<ISaveNotify>(ctx => ctx.Resolve<INotificationManager>());
builder.RegisterType<NotificationManager>().AsSelf().SingleInstance();
builder.Register<ISaveNotify>(ctx => ctx.Resolve<Notify>());
builder.RegisterType<DesktopController>().AsSelf().SingleInstance();
builder.RegisterType<UpdateChecker>().As<IUpdateChecker>();
builder.RegisterType<ExportController>().As<IExportController>();

View File

@ -4,6 +4,18 @@ public static class ProcessHelper
{
public static void OpenUrl(string url) => Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
public static void OpenFile(string file)
{
Process.Start(new ProcessStartInfo
{
UseShellExecute = true,
FileName = file,
Verb = "open"
});
}
public static void OpenFolder(string folder) => OpenFile(folder);
public static bool IsSuccessful(string command, string args, int timeoutMs)
{
try