2025-06-28 17:40:42 +02:00
|
|
|
/*
|
|
|
|
|
* File: XInsaneWidget.cpp
|
|
|
|
|
* Author: Saleem Edah-Tally - nmset@yandex.com
|
|
|
|
|
* License : CeCILL-C
|
|
|
|
|
* Copyright Saleem Edah-Tally - © 2025
|
|
|
|
|
*
|
|
|
|
|
* Created on 15 06 2025, 21:24
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "XInsaneWidget.h"
|
|
|
|
|
#include <PopupTransientWindow.h>
|
|
|
|
|
#include <MiscTools.h>
|
|
|
|
|
#include "PixelToImageWriter.h"
|
|
|
|
|
#include "PixelToPdfWriter.h"
|
2025-07-01 22:38:07 +02:00
|
|
|
#include <DefsInsaneWidget.h>
|
|
|
|
|
#include <DefsStampWidget.h>
|
|
|
|
|
#include <XStampWidget.h>
|
|
|
|
|
#include <StampWorker.h>
|
2025-06-28 17:40:42 +02:00
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
|
|
// When device discovery in a detached thread completes.
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
class BackgroundScannerDiscoveryEVH : public BackgroundScannerDiscoveryEvent
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
BackgroundScannerDiscoveryEVH(XInsaneWidget * owner)
|
|
|
|
|
{
|
|
|
|
|
m_owner = owner;
|
|
|
|
|
}
|
|
|
|
|
virtual ~BackgroundScannerDiscoveryEVH()
|
|
|
|
|
{}
|
|
|
|
|
void OnDone() override
|
|
|
|
|
{
|
|
|
|
|
if (!m_owner)
|
|
|
|
|
return;
|
|
|
|
|
m_owner->CallAfter(&XInsaneWidget::EnableScanButton, true);
|
|
|
|
|
}
|
|
|
|
|
private:
|
|
|
|
|
wxWeakRef<XInsaneWidget> m_owner = nullptr;
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-11 21:10:59 +02:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
class BackgroundScan : public wxThread
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
BackgroundScan(XInsaneWidget * insaneWidget, InsaneWorker * insaneWorker,
|
|
|
|
|
const wxString& dir, const wxString& basename,
|
|
|
|
|
int start = 0, int padWidth = 2, int increment = 1)
|
|
|
|
|
{
|
|
|
|
|
m_insaneWidget = insaneWidget;
|
|
|
|
|
m_insaneWorker = insaneWorker;
|
|
|
|
|
m_dir = dir;
|
|
|
|
|
m_basename = basename;
|
|
|
|
|
m_start = start;
|
|
|
|
|
m_padWidth = padWidth;
|
|
|
|
|
m_increment = increment;
|
|
|
|
|
}
|
|
|
|
|
ExitCode Entry() override
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* We refrain from using a mutex/critical section.
|
|
|
|
|
* If 2 instances of XInsaneWidget in a single application scan on the same
|
|
|
|
|
* device simultaneously, the first one that starts scanning prevails and
|
|
|
|
|
* the second one triggers a "Device busy" error.
|
|
|
|
|
* If 2 instances of XInsaneWidget in a single application scan on different
|
|
|
|
|
* devices simultaneously, both jobs succeed.
|
|
|
|
|
* Since there cannot be concurrency leading to a crash ('Device busy'), we
|
|
|
|
|
* do not block the next line. It may limit an application that needs to
|
|
|
|
|
* scan from multiple devices.
|
|
|
|
|
*/
|
|
|
|
|
m_insaneWorker->Scan(m_dir.ToStdString(), m_basename.ToStdString(), m_start, m_padWidth, m_increment);
|
|
|
|
|
return (ExitCode) 0;
|
|
|
|
|
}
|
|
|
|
|
private:
|
|
|
|
|
wxWeakRef<XInsaneWidget> m_insaneWidget = nullptr;
|
|
|
|
|
InsaneWorker * m_insaneWorker = nullptr;
|
|
|
|
|
wxString m_dir, m_basename;
|
|
|
|
|
int m_start = 0, m_padWidth = 2, m_increment = 1;
|
|
|
|
|
};
|
|
|
|
|
|
2025-06-28 17:40:42 +02:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
class ScanProjectHandler : public InsaneWorkerEvent
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
ScanProjectHandler(XInsaneWidget * owner, TimeredStatusBar * sb = nullptr)
|
|
|
|
|
: InsaneWorkerEvent(), m_owner(owner), m_sb (sb)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
/*
|
2025-07-01 22:38:07 +02:00
|
|
|
* mode, resolution, outputType, adf, doubleSided, total, PaperSize:
|
2025-06-28 17:40:42 +02:00
|
|
|
* These are not updated here if there are pending jobs.
|
|
|
|
|
* However,
|
|
|
|
|
* deviceId, source, sourceIndex, mode, resolution, paperSize
|
|
|
|
|
* can be changed if there are pending jobs in ConfigureDevice() since it is
|
|
|
|
|
* called before these setters. They won't change on their own but by user
|
|
|
|
|
* interaction.
|
2025-07-01 22:38:07 +02:00
|
|
|
* The 'resolution' variable is used downstream to distinguish between v4l
|
|
|
|
|
* and real scanners. v4l devices do not have a source and a resolution
|
|
|
|
|
* tunables.
|
2025-06-28 17:40:42 +02:00
|
|
|
*/
|
|
|
|
|
void SetMode(const string& mode)
|
|
|
|
|
{
|
|
|
|
|
if (m_pixelFiles.size())
|
|
|
|
|
return;
|
|
|
|
|
m_mode = mode;
|
|
|
|
|
}
|
2025-07-01 22:38:07 +02:00
|
|
|
void SetResolution(int resolution)
|
|
|
|
|
{
|
|
|
|
|
if (m_pixelFiles.size())
|
|
|
|
|
return;
|
|
|
|
|
m_resolution = resolution;
|
|
|
|
|
}
|
2025-06-28 17:40:42 +02:00
|
|
|
void SetOutputType(int outputType)
|
|
|
|
|
{
|
|
|
|
|
if (m_pixelFiles.size())
|
|
|
|
|
return;
|
|
|
|
|
m_outputType = outputType;
|
|
|
|
|
}
|
|
|
|
|
void SetADF(bool adf)
|
|
|
|
|
{
|
|
|
|
|
if (m_pixelFiles.size())
|
|
|
|
|
return;
|
|
|
|
|
m_adf = adf;
|
|
|
|
|
if (m_adf && m_doubleSided)
|
|
|
|
|
m_startPageIndex = -2;
|
|
|
|
|
else
|
|
|
|
|
m_startPageIndex = -1;
|
|
|
|
|
}
|
|
|
|
|
void SetDoubleSided(bool doubleSided)
|
|
|
|
|
{
|
|
|
|
|
if (m_pixelFiles.size())
|
|
|
|
|
return;
|
|
|
|
|
m_doubleSided = doubleSided;
|
|
|
|
|
if (m_adf && m_doubleSided)
|
|
|
|
|
m_startPageIndex = -2;
|
|
|
|
|
else
|
|
|
|
|
m_startPageIndex = -1;
|
|
|
|
|
}
|
|
|
|
|
void SetTotalNumberOfSides(uint total)
|
|
|
|
|
{
|
|
|
|
|
if (m_pixelFiles.size())
|
|
|
|
|
return;
|
|
|
|
|
m_total = total;
|
|
|
|
|
m_totalEven = total;
|
|
|
|
|
if (total % 2)
|
|
|
|
|
m_totalEven = total + 1;
|
|
|
|
|
}
|
|
|
|
|
void SetPaperSize(const wxString& paperSize)
|
|
|
|
|
{
|
|
|
|
|
if (m_pixelFiles.size())
|
|
|
|
|
return;
|
|
|
|
|
m_paperSize = paperSize;
|
|
|
|
|
}
|
2025-07-01 22:38:07 +02:00
|
|
|
void SetStampDescriptors(vector<StampDescriptor*> * descriptors)
|
|
|
|
|
{
|
|
|
|
|
m_stampDescriptors = descriptors;
|
|
|
|
|
}
|
2025-07-11 21:10:59 +02:00
|
|
|
// WIP means InsaneWorker is busy, not the position in a scan project.
|
|
|
|
|
void SetWip(bool wip)
|
|
|
|
|
{
|
|
|
|
|
m_wip = wip;
|
|
|
|
|
}
|
|
|
|
|
bool GetWip() const
|
|
|
|
|
{
|
|
|
|
|
return m_wip;
|
|
|
|
|
}
|
|
|
|
|
/* Enable/disable the label and destination file controls per scanning and
|
|
|
|
|
* idle mode.
|
|
|
|
|
* Do not include in Reset() because the status of the scan button is
|
|
|
|
|
* independent of the rest.
|
|
|
|
|
*/
|
|
|
|
|
void UpdateControlsStatus(bool enable)
|
|
|
|
|
{
|
|
|
|
|
wxTheApp->CallAfter([this, enable] ()
|
|
|
|
|
{
|
|
|
|
|
m_owner->lblNewDoc->Enable(enable);
|
|
|
|
|
m_owner->txtNewDoc->Enable(enable);
|
|
|
|
|
if (enable)
|
|
|
|
|
{
|
|
|
|
|
m_owner->btnScan->Enable(enable);
|
|
|
|
|
m_owner->btnScan->SetLabelText(_("Scan"));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/*Only XInsaneWidget::Setup() and XInsaneWidget::CancelScanning()
|
|
|
|
|
* disable the scan button.
|
|
|
|
|
*/
|
|
|
|
|
m_owner->btnScan->SetLabelText(_("Cancel"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-06-28 17:40:42 +02:00
|
|
|
std::pair<int, int> GetStartAndIncrement(InsaneWorker * insaneWorker)
|
|
|
|
|
{
|
|
|
|
|
wxASSERT_MSG(insaneWorker != nullptr, "insaneWorker is NULL.");
|
|
|
|
|
std::pair<int, int> startAndIncrement = insaneWorker->UpdateStartAndIncrement(m_startPageIndex, m_increment,
|
|
|
|
|
m_adf, m_doubleSided, m_total);
|
|
|
|
|
m_startPageIndex = startAndIncrement.first;
|
|
|
|
|
m_increment = startAndIncrement.second;
|
|
|
|
|
return startAndIncrement;
|
|
|
|
|
}
|
|
|
|
|
void OnInsaneError ( const std::string& message ) override
|
|
|
|
|
{
|
|
|
|
|
cerr << message << endl;
|
|
|
|
|
Reset();
|
2025-07-11 21:10:59 +02:00
|
|
|
UpdateControlsStatus(true);
|
|
|
|
|
const wxString msg = _("A scan library error occurred.");
|
|
|
|
|
MiscTools::AsyncMessageBox(msg, true);
|
2025-06-28 17:40:42 +02:00
|
|
|
}
|
|
|
|
|
void OnError ( const std::string& message ) override
|
|
|
|
|
{
|
|
|
|
|
cerr << message << endl;
|
|
|
|
|
Reset();
|
2025-07-11 21:10:59 +02:00
|
|
|
UpdateControlsStatus(true);
|
|
|
|
|
const wxString msg = _("A general error occurred.");
|
|
|
|
|
MiscTools::AsyncMessageBox(msg, true);
|
2025-06-28 17:40:42 +02:00
|
|
|
}
|
|
|
|
|
void OnSessionReadError(const std::string & filePath) override
|
|
|
|
|
{
|
|
|
|
|
const wxString msg = _("A session read error occurred.");
|
|
|
|
|
cerr << msg << endl;
|
|
|
|
|
Reset();
|
2025-07-11 21:10:59 +02:00
|
|
|
UpdateControlsStatus(true);
|
|
|
|
|
MiscTools::AsyncMessageBox(msg, true);
|
2025-06-28 17:40:42 +02:00
|
|
|
}
|
|
|
|
|
void OnSessionCancelled(const std::string & filePath) override
|
|
|
|
|
{
|
2025-07-11 21:10:59 +02:00
|
|
|
if (wxFileExists(filePath))
|
|
|
|
|
wxRemoveFile(filePath);
|
2025-06-28 17:40:42 +02:00
|
|
|
Reset();
|
2025-07-11 21:10:59 +02:00
|
|
|
UpdateControlsStatus(true);
|
|
|
|
|
const wxString msg = _("Session cancelled.");
|
|
|
|
|
MiscTools::AsyncMessageBox(msg, true);
|
|
|
|
|
m_sb->CallAfter(&TimeredStatusBar::SetTransientText, msg, 3000);
|
2025-06-28 17:40:42 +02:00
|
|
|
}
|
|
|
|
|
// Every time a page is fully scanned.
|
|
|
|
|
void OnPageEndScan(const std::string & filePath, uint pageIndex,
|
|
|
|
|
const ImageAttributes& imageAttributes) override
|
|
|
|
|
{
|
|
|
|
|
m_startPageIndex = pageIndex;
|
|
|
|
|
m_pixelFiles[pageIndex] = {filePath, imageAttributes};
|
|
|
|
|
|
2025-07-11 21:10:59 +02:00
|
|
|
auto informProgress = [this] ()
|
2025-06-28 17:40:42 +02:00
|
|
|
{
|
|
|
|
|
if (!m_sb || (m_total == 1))
|
|
|
|
|
return;
|
|
|
|
|
int max = (m_adf && m_doubleSided) ? m_totalEven : m_total;
|
|
|
|
|
wxString progress = to_string(m_pixelFiles.size()) + "/" + to_string(max);
|
|
|
|
|
wxString info = _("Scanning: ");
|
|
|
|
|
if (m_increment == 2)
|
|
|
|
|
info = _("Front face: ");
|
|
|
|
|
else if (m_increment == -2)
|
|
|
|
|
info = _("Back face: ");
|
|
|
|
|
wxString msg = info + progress;
|
|
|
|
|
if (m_increment == 2 && ((max / 2) == (m_pixelFiles.size())))
|
|
|
|
|
{
|
|
|
|
|
wxString upperBoundMessage = _(". Turn the whole stack of pages.");
|
|
|
|
|
msg += upperBoundMessage;
|
|
|
|
|
}
|
2025-07-11 21:10:59 +02:00
|
|
|
m_sb->CallAfter(&wxStatusBar::SetStatusText, msg, 0);
|
2025-06-28 17:40:42 +02:00
|
|
|
};
|
2025-07-01 22:38:07 +02:00
|
|
|
|
2025-06-28 17:40:42 +02:00
|
|
|
if (m_outputType != PDF)
|
|
|
|
|
{
|
2025-07-11 21:10:59 +02:00
|
|
|
// Convert pixel file to PNG using wxImage.
|
2025-07-01 22:38:07 +02:00
|
|
|
if (!PixelToImageWriter::Convert(filePath, imageAttributes.width, imageAttributes.height,
|
|
|
|
|
m_stampDescriptors, m_outputType))
|
2025-06-28 17:40:42 +02:00
|
|
|
{
|
|
|
|
|
const wxString msg = _("Failed to create output image.");
|
|
|
|
|
cerr << msg << endl;
|
|
|
|
|
if (m_sb)
|
2025-07-11 21:10:59 +02:00
|
|
|
m_sb->CallAfter(&TimeredStatusBar::SetTransientText, msg, 3000);
|
2025-06-28 17:40:42 +02:00
|
|
|
}
|
|
|
|
|
wxRemoveFile(filePath);
|
|
|
|
|
informProgress();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Append or insert PDF page from pixel file using PoDoFo.
|
|
|
|
|
if (!m_pixelToPdfWriter.get())
|
|
|
|
|
m_pixelToPdfWriter = std::unique_ptr<PixelToPdfWriter> (new PixelToPdfWriter());
|
|
|
|
|
uint index = (m_increment > 0) // 1 or 2
|
|
|
|
|
? m_pixelToPdfWriter->GetNumberOfPages() // Append.
|
|
|
|
|
: m_totalEven - m_pixelToPdfWriter->GetNumberOfPages(); // Insert.
|
|
|
|
|
PoDoFo::PdfPageSize pageSize = m_pixelToPdfWriter->GetPageSize(m_paperSize.ToStdString());
|
|
|
|
|
if (pageSize == PoDoFo::PdfPageSize::Unknown)
|
|
|
|
|
{
|
|
|
|
|
const wxString msg = _("Wrong paper size: ") + m_paperSize + _("; using A4.");
|
|
|
|
|
cerr << msg << endl;
|
|
|
|
|
pageSize = PoDoFo::PdfPageSize::A4;
|
|
|
|
|
}
|
2025-07-01 22:38:07 +02:00
|
|
|
if (!m_pixelToPdfWriter->AddPageAt(filePath, imageAttributes.width, imageAttributes.height, index,
|
|
|
|
|
m_stampDescriptors, pageSize))
|
2025-06-28 17:40:42 +02:00
|
|
|
{
|
|
|
|
|
const wxString msg = _("Failed to add page to PDF document.");
|
|
|
|
|
cerr << msg << endl;
|
|
|
|
|
if (m_sb)
|
2025-07-11 21:10:59 +02:00
|
|
|
m_sb->CallAfter(&TimeredStatusBar::SetTransientText, msg, 3000);
|
2025-06-28 17:40:42 +02:00
|
|
|
}
|
|
|
|
|
wxRemoveFile(filePath);
|
|
|
|
|
informProgress();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
void OnEndScanSession(uint pageIndex, const ImageAttributes & imageAttributes) override
|
|
|
|
|
{
|
|
|
|
|
int max = (m_adf && m_doubleSided) ? m_totalEven : m_total;
|
|
|
|
|
// A special case is made for (total == 1): double-sided does not have meaning.
|
|
|
|
|
if (m_total == 1)
|
|
|
|
|
max = m_total;
|
|
|
|
|
if (m_pixelFiles.size() >= max)
|
|
|
|
|
{
|
|
|
|
|
// All pages have been scanned, accounting for ADF and double-sided.
|
|
|
|
|
if (m_outputType != PDF)
|
|
|
|
|
{
|
|
|
|
|
// Remove the last image if an odd number of pages was requested.
|
|
|
|
|
if (m_increment != 1 && (m_total != m_totalEven))
|
|
|
|
|
{
|
|
|
|
|
const string filePath = std::get<0> (m_pixelFiles.rbegin()->second) + "." + Extensions[m_outputType];
|
|
|
|
|
wxRemoveFile(filePath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Remove the last page if an odd number of pages was requested.
|
|
|
|
|
if (m_increment != 1 && (m_total != m_totalEven))
|
|
|
|
|
{
|
|
|
|
|
uint lastIndex = m_pixelFiles.end()->first;
|
|
|
|
|
m_pixelToPdfWriter->RemovePageAt(lastIndex - 1);
|
|
|
|
|
}
|
|
|
|
|
const string filePath = m_owner->txtNewDoc->GetValue().ToStdString() + "." + Extensions[m_outputType];
|
|
|
|
|
m_pixelToPdfWriter->Save(filePath);
|
|
|
|
|
m_pixelToPdfWriter.reset((new PixelToPdfWriter())); // For next scan project.
|
|
|
|
|
}
|
|
|
|
|
Reset();
|
2025-07-11 21:10:59 +02:00
|
|
|
m_owner->txtNewDoc->CallAfter(&wxTextCtrl::SetValue, wxString());
|
2025-06-28 17:40:42 +02:00
|
|
|
if (m_sb)
|
|
|
|
|
{
|
|
|
|
|
const wxString msg = _("Finished.");
|
2025-07-11 21:10:59 +02:00
|
|
|
m_sb->CallAfter(&TimeredStatusBar::SetTransientText, msg, 3000);
|
2025-06-28 17:40:42 +02:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-11 21:10:59 +02:00
|
|
|
UpdateControlsStatus(true);
|
|
|
|
|
m_wip = false;
|
2025-06-28 17:40:42 +02:00
|
|
|
}
|
2025-07-01 22:38:07 +02:00
|
|
|
void OnStartScanSession(uint pageIndex, const ImageAttributes& imageAttributes) override
|
|
|
|
|
{
|
2025-07-11 21:10:59 +02:00
|
|
|
// Dealing with stamps only.
|
2025-07-01 22:38:07 +02:00
|
|
|
if (!m_stampDescriptors)
|
|
|
|
|
return;
|
|
|
|
|
for (StampDescriptor * descriptor : *m_stampDescriptors)
|
|
|
|
|
{
|
|
|
|
|
if (descriptor->text.IsEmpty())
|
|
|
|
|
continue;
|
|
|
|
|
descriptor->image = StampWorker::CreateStamp(descriptor, m_resolution);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-28 17:40:42 +02:00
|
|
|
void Reset()
|
|
|
|
|
{
|
|
|
|
|
// Don't reset calculated variables that depend on widgets.
|
|
|
|
|
m_startPageIndex = 0;
|
|
|
|
|
m_increment = 1;
|
|
|
|
|
m_totalEven = 0;
|
|
|
|
|
m_pixelFiles.clear();
|
2025-07-11 21:10:59 +02:00
|
|
|
m_wip = false;
|
2025-06-28 17:40:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
wxWeakRef<XInsaneWidget> m_owner = nullptr;
|
|
|
|
|
wxWeakRef<TimeredStatusBar> m_sb = nullptr;
|
|
|
|
|
std::unique_ptr<PixelToPdfWriter> m_pixelToPdfWriter;
|
|
|
|
|
PixelFilesMap m_pixelFiles;
|
2025-07-01 22:38:07 +02:00
|
|
|
vector<StampDescriptor*> * m_stampDescriptors = nullptr;
|
2025-06-28 17:40:42 +02:00
|
|
|
string m_mode = "Color";
|
2025-07-01 22:38:07 +02:00
|
|
|
int m_resolution = -1;
|
2025-06-28 17:40:42 +02:00
|
|
|
uint m_outputType = PDF;
|
|
|
|
|
wxString m_paperSize = _T("A4");
|
|
|
|
|
bool m_adf = false;
|
|
|
|
|
bool m_doubleSided = false;
|
|
|
|
|
int m_total = 0;
|
|
|
|
|
int m_totalEven = 0;
|
|
|
|
|
int m_startPageIndex = 0;
|
|
|
|
|
int m_increment = 1;
|
2025-07-11 21:10:59 +02:00
|
|
|
bool m_wip = false; // Is InsaneWorker busy?
|
2025-06-28 17:40:42 +02:00
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
2025-07-01 22:38:07 +02:00
|
|
|
IMPLEMENT_CLASS( XInsaneWidget, InsaneWidget )
|
|
|
|
|
|
2025-07-13 16:31:46 +02:00
|
|
|
XInsaneWidget::XInsaneWidget(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
|
2025-06-28 17:40:42 +02:00
|
|
|
: InsaneWidget(parent, id, pos, size, style)
|
2025-07-13 16:31:46 +02:00
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
XInsaneWidget::~XInsaneWidget() = default; // Important for mixing unique_ptr and PIMPL.
|
|
|
|
|
|
|
|
|
|
void XInsaneWidget::Setup(wxConfig * config, TimeredStatusBar * sb)
|
2025-06-28 17:40:42 +02:00
|
|
|
{
|
|
|
|
|
UpdateExtensionsMap();
|
|
|
|
|
m_config = config;
|
|
|
|
|
m_sb = sb;
|
2025-07-01 22:38:07 +02:00
|
|
|
|
2025-07-13 16:10:15 +02:00
|
|
|
// Restore known last values.
|
|
|
|
|
m_doubleSided = m_config->ReadBool("/Scanner/DoubleSided", false);
|
|
|
|
|
m_total = m_config->ReadLong("/Scanner/Total", 1);
|
|
|
|
|
|
2025-06-28 17:40:42 +02:00
|
|
|
lblNewDoc->Bind ( wxEVT_RIGHT_UP, &XInsaneWidget::OnLblNewDocRightClick, this );
|
|
|
|
|
txtNewDoc->Bind ( wxEVT_KEY_UP, &XInsaneWidget::OnTxtNewDocKeyPressed, this );
|
|
|
|
|
|
2025-07-11 21:10:59 +02:00
|
|
|
/*
|
|
|
|
|
* Don't use GetTopWindow() as parent. If it goes away in an application with
|
|
|
|
|
* multiples instances of XInsaneWidget, any call to the scanner widget leads
|
|
|
|
|
* to a crash.
|
|
|
|
|
*/
|
|
|
|
|
m_ptwScannerWidget = std::make_unique<wxPopupTransientWindow> (GetParent());
|
2025-06-28 17:40:42 +02:00
|
|
|
m_ptwScannerWidget->Show ( false );
|
|
|
|
|
m_scanProject = std::make_unique<ScanProjectHandler>(this, m_sb);
|
|
|
|
|
m_insaneWorker = std::make_unique<InsaneWorker>(m_scanProject.get());
|
2025-07-13 16:31:46 +02:00
|
|
|
m_scannerWidget = std::make_unique<XScannerWidget> (m_ptwScannerWidget.get());
|
|
|
|
|
m_scannerWidget->Setup ( m_config, m_insaneWorker.get(), m_sb );
|
2025-07-01 22:38:07 +02:00
|
|
|
|
2025-06-28 17:40:42 +02:00
|
|
|
btnScan->Enable(false);
|
|
|
|
|
btnScan->Bind ( wxEVT_RIGHT_UP, &XInsaneWidget::OnBtnScanRightClick, this );
|
|
|
|
|
btnScan->Bind ( wxEVT_LEFT_UP, &XInsaneWidget::OnBtnScanClick, this );
|
2025-07-13 20:24:50 +02:00
|
|
|
m_backgroundDiscovery = new BackgroundScannerDiscovery ( m_scannerWidget.get(),
|
|
|
|
|
new BackgroundScannerDiscoveryEVH(this)); // Takes ownership.
|
|
|
|
|
m_backgroundDiscovery->Run();
|
2025-06-28 17:40:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Show a popup to specifiy the number of pages to scan and back-sided scanning.
|
|
|
|
|
void XInsaneWidget::OnLblNewDocRightClick ( wxMouseEvent& evt )
|
|
|
|
|
{
|
2025-07-11 21:10:59 +02:00
|
|
|
if (!lblNewDoc->IsEnabled())
|
|
|
|
|
{
|
|
|
|
|
evt.Skip();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-06-28 17:40:42 +02:00
|
|
|
/*
|
|
|
|
|
* The previous ConfigEditorPopup is deleted here, which commits the
|
|
|
|
|
* parameters to the config file. At any time, the current parameter values
|
|
|
|
|
* can be lost if a crash occurs.
|
|
|
|
|
*/
|
2025-07-11 21:10:59 +02:00
|
|
|
m_pageStack.reset(new ConfigEditorPopup(GetParent(), m_config));
|
2025-06-28 17:40:42 +02:00
|
|
|
PopupTransientWindow * ptw = m_pageStack->CreatePopup();
|
|
|
|
|
if ( !ptw )
|
|
|
|
|
{
|
|
|
|
|
evt.Skip();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-07-13 16:10:15 +02:00
|
|
|
wxCheckBox * cb = m_pageStack->AddCheckBox (_("Double sided:"),_T("/Scanner/DoubleSided"), &m_doubleSided );
|
2025-06-28 17:40:42 +02:00
|
|
|
cb->SetToolTip (_("Scan all front faces first, then all back faces in reverse order.") );
|
2025-07-13 16:10:15 +02:00
|
|
|
wxSpinCtrl * spn = m_pageStack->AddSpinCtrl (_("Total:"),_T("/Scanner/Total"), &m_total );
|
2025-06-28 17:40:42 +02:00
|
|
|
spn->SetRange ( 1, 50 );
|
|
|
|
|
spn->SetToolTip (_("Total number of sides to scan (not total number of sheets).") );
|
|
|
|
|
m_pageStack->ShowPopup();
|
|
|
|
|
evt.Skip();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void XInsaneWidget::OnTxtNewDocKeyPressed ( wxKeyEvent& evt )
|
|
|
|
|
{
|
|
|
|
|
if ( evt.GetKeyCode() == WXK_BACK )
|
|
|
|
|
{
|
|
|
|
|
txtNewDoc->Clear();
|
|
|
|
|
}
|
|
|
|
|
evt.Skip();
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-01 22:38:07 +02:00
|
|
|
// Show the scanner widget or the stamp widgets.
|
2025-06-28 17:40:42 +02:00
|
|
|
void XInsaneWidget::OnBtnScanRightClick ( wxMouseEvent& evt )
|
|
|
|
|
{
|
2025-07-01 22:38:07 +02:00
|
|
|
if (evt.ControlDown())
|
2025-06-28 17:40:42 +02:00
|
|
|
{
|
2025-07-01 22:38:07 +02:00
|
|
|
if (!m_dlgStampWidgets)
|
|
|
|
|
{
|
|
|
|
|
m_dlgStampWidgets = std::make_unique<wxDialog>
|
|
|
|
|
(nullptr, wxID_ANY, _("Stamps"),
|
|
|
|
|
wxDefaultPosition, wxDefaultSize,
|
|
|
|
|
wxCAPTION | wxRESIZE_BORDER | wxCLOSE_BOX);
|
|
|
|
|
m_dlgStampWidgets->SetSize(600, 500);
|
|
|
|
|
m_dlgStampWidgets->SetSizer(new wxBoxSizer(wxVERTICAL));
|
|
|
|
|
m_dlgStampWidgets->Show ( false );
|
|
|
|
|
m_stampWidgets = std::make_unique<XStampWidgets> ( m_dlgStampWidgets.get());
|
|
|
|
|
m_stampWidgets->Setup(m_config);
|
|
|
|
|
m_dlgStampWidgets->GetSizer()->Add(m_stampWidgets.get(), 1, wxGROW | wxALL, 5);
|
|
|
|
|
}
|
|
|
|
|
m_dlgStampWidgets->Show();
|
2025-06-28 17:40:42 +02:00
|
|
|
}
|
2025-07-01 22:38:07 +02:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if ( m_scannerWidget->cmbDevices->GetCount() )
|
|
|
|
|
{
|
|
|
|
|
const wxSize current = m_scannerWidget->GetSize();
|
|
|
|
|
m_scannerWidget->SetSize ( wxSize ( 500, current.GetHeight() ) );
|
|
|
|
|
}
|
|
|
|
|
MiscTools::ShowTransientPopup ( m_ptwScannerWidget.get(), m_scannerWidget.get() );
|
|
|
|
|
}
|
|
|
|
|
// +++ Prevent OnLblNewDocRightClick() from being called.
|
|
|
|
|
// Why is OnBtnScanRightClick() not called the other way round?
|
|
|
|
|
evt.Skip(false);
|
2025-06-28 17:40:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start scanning.
|
|
|
|
|
void XInsaneWidget::OnBtnScanClick ( wxMouseEvent& evt )
|
|
|
|
|
{
|
2025-07-11 21:10:59 +02:00
|
|
|
if (m_scanProject->GetWip())
|
|
|
|
|
{
|
|
|
|
|
CancelScanning();
|
|
|
|
|
evt.Skip();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-06-28 17:40:42 +02:00
|
|
|
const wxString dest = txtNewDoc->GetValue();
|
|
|
|
|
if (dest.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
MiscTools::MessageBox(_("Destination file missing."), true);
|
|
|
|
|
evt.Skip();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const uint outputType = m_scannerWidget->GetScannerOutputType();
|
|
|
|
|
bool adf = false;
|
2025-07-13 16:10:15 +02:00
|
|
|
wxString paperSize = m_scannerWidget->GetPaperSize();
|
2025-06-28 17:40:42 +02:00
|
|
|
|
|
|
|
|
wxFileName destFile(dest);
|
|
|
|
|
const string deviceId = m_scannerWidget->GetCurrentDeviceId().ToStdString();
|
|
|
|
|
const std::pair<int, wxString> sourceAttributes = m_scannerWidget->GetScannerSource();
|
|
|
|
|
const string source = sourceAttributes.second.ToStdString();
|
|
|
|
|
const string mode = m_scannerWidget->GetScannerMode().ToStdString();
|
2025-07-01 22:38:07 +02:00
|
|
|
int resolution = -1; // No resolution with v4l devices.
|
2025-06-28 17:40:42 +02:00
|
|
|
if ( !m_scannerWidget->GetScannerResolution().IsEmpty() )
|
|
|
|
|
{
|
|
|
|
|
resolution = std::stoi ( m_scannerWidget->GetScannerResolution().ToStdString() );
|
|
|
|
|
}
|
|
|
|
|
int sourceIndex = sourceAttributes.first == wxNOT_FOUND
|
|
|
|
|
? 0 : sourceAttributes.first;
|
|
|
|
|
adf = m_insaneWorker->IsADF(deviceId, sourceIndex);
|
|
|
|
|
if (m_insaneWorker->ConfigureDevice(deviceId, source, sourceIndex, mode, resolution, paperSize.ToStdString()))
|
|
|
|
|
{
|
|
|
|
|
m_scanProject->SetADF(adf);
|
|
|
|
|
m_scanProject->SetMode(mode);
|
2025-07-01 22:38:07 +02:00
|
|
|
m_scanProject->SetResolution(resolution);
|
2025-06-28 17:40:42 +02:00
|
|
|
m_scanProject->SetOutputType(outputType);
|
|
|
|
|
m_scanProject->SetPaperSize(paperSize);
|
2025-07-13 16:10:15 +02:00
|
|
|
m_scanProject->SetDoubleSided(m_doubleSided);
|
|
|
|
|
m_scanProject->SetTotalNumberOfSides((uint) m_total);
|
2025-07-09 23:08:33 +02:00
|
|
|
if (m_stampWidgets)
|
|
|
|
|
{
|
|
|
|
|
m_stampDescriptors = m_stampWidgets->GetStampDescriptors();
|
|
|
|
|
m_scanProject->SetStampDescriptors(m_stampDescriptors);
|
|
|
|
|
}
|
2025-06-28 17:40:42 +02:00
|
|
|
|
|
|
|
|
std::pair<int, int> startAndIncrement = m_scanProject->GetStartAndIncrement(m_insaneWorker.get());
|
|
|
|
|
|
|
|
|
|
const int padWidth = ( ushort ) m_config->Read (_T("/Scanner/Counter/Length"), 2 );
|
2025-07-11 21:10:59 +02:00
|
|
|
m_scanProject->UpdateControlsStatus(false);
|
|
|
|
|
BackgroundScan * bgScan = new BackgroundScan(this, m_insaneWorker.get(),
|
|
|
|
|
destFile.GetPath().ToStdString(),
|
|
|
|
|
destFile.GetName().ToStdString(),
|
|
|
|
|
startAndIncrement.first, padWidth,
|
|
|
|
|
startAndIncrement.second);
|
|
|
|
|
m_scanProject->SetWip(true);
|
|
|
|
|
bgScan->Run();
|
2025-06-28 17:40:42 +02:00
|
|
|
}
|
|
|
|
|
evt.Skip();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void XInsaneWidget::ResetScanProject()
|
|
|
|
|
{
|
|
|
|
|
if (m_scanProject)
|
|
|
|
|
m_scanProject->Reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void XInsaneWidget::CancelScanning()
|
|
|
|
|
{
|
2025-07-11 21:10:59 +02:00
|
|
|
if (m_insaneWorker && m_scanProject->GetWip())
|
|
|
|
|
{
|
2025-06-28 17:40:42 +02:00
|
|
|
m_insaneWorker->Cancel();
|
2025-07-11 21:10:59 +02:00
|
|
|
btnScan->Enable(false);
|
|
|
|
|
}
|
2025-06-28 17:40:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void XInsaneWidget::EnableScanButton(bool enable)
|
|
|
|
|
{
|
2025-07-13 20:24:50 +02:00
|
|
|
// Called from BackgroundScannerDiscoveryEVH::Done() only;
|
2025-06-28 17:40:42 +02:00
|
|
|
btnScan->Enable(enable);
|
2025-07-13 20:24:50 +02:00
|
|
|
m_backgroundDiscovery = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool XInsaneWidget::IsScannerDiscoveryRunning()
|
|
|
|
|
{
|
|
|
|
|
if (!m_backgroundDiscovery)
|
|
|
|
|
return false;
|
|
|
|
|
return m_backgroundDiscovery->IsRunning();
|
2025-06-28 17:40:42 +02:00
|
|
|
}
|