Compare commits

15 Commits
v1 ... master

Author SHA1 Message Date
Saleem Edah-Tally
5f5cec01c0 Update inline comments. 2025-07-15 20:36:38 +02:00
Saleem Edah-Tally
518371e5c1 Add missing space in translation. 2025-07-15 19:27:58 +02:00
Saleem Edah-Tally
81cf250862 Improve identification of stdout messages.
Add a prefix '[InsaneWorker]' for this class for cerr logging.
2025-07-15 19:25:34 +02:00
Saleem Edah-Tally
4f64fdc7d5 Reparent the Stamps dialog to the calling widget. 2025-07-14 13:31:06 +02:00
Saleem Edah-Tally
b1f4f2d15f Use wxAboutBox dialog. 2025-07-14 13:31:01 +02:00
Saleem Edah-Tally
0e517799c8 Cancel scanning if window closure is requested.
The job is cancelled but the window remains open and functional.
At least, the application survives; a crash would happen without this patch.
2025-07-13 22:36:09 +02:00
Saleem Edah-Tally
2b9290e4d8 Report if a scan job has started. 2025-07-13 22:35:09 +02:00
Saleem Edah-Tally
24f58fa13d Allow using multiple instances of the application window.
All instances share the same config file.
Each instance can independently
 - work on a device
 - define its own stamps.
They can work simultaneously.
2025-07-13 21:26:00 +02:00
Saleem Edah-Tally
c10aac922d Veto window closure during scanner discovery in the background. 2025-07-13 20:24:50 +02:00
Saleem Edah-Tally
0f286efe4c Prefer two-step construction of UI classes for consistency. 2025-07-13 17:44:46 +02:00
Saleem Edah-Tally
7d3c61c91d Use the saved config values or application values.
ConfigEditorPopup

On creating the popup, config values were always read and used. Do this if the
application does not provide its own variables. The values are still committed
to the config file in the usual way (on control destruction). The application
may restore them to its own variables during setup.
2025-07-13 16:13:56 +02:00
Saleem Edah-Tally
059b16f08a Process command line and configuration file in the application class. 2025-07-13 14:36:02 +02:00
Saleem Edah-Tally
4b23b1f3de Allow cancelling a scan session.
- perform scanning in a thread
 - change the label of the scan button
 - trigger cancel with the same button and with the ESC key
 - process all GUI updates in asynchronous mode.

Do not use a top window as parent of popups:
 - If a top window goes away in an application with multiple instances of
 XInsaneWidget, any call to a scanner widget leads to a crash.

Minor changes.
2025-07-12 23:03:32 +02:00
Saleem Edah-Tally
3350f86ddf Null pointer check. 2025-07-09 23:08:33 +02:00
Saleem Edah-Tally
a2045aa1f6 Add a 'Stamp' widget.
Place one or multiple stamps on scanned pages in defined locations.

A stamp is understood here as
 - a transparent text in a transparent frame with no borders
 - an opaque text on an opaque background with no borders.

Stamp parameters:
 - text
 - font
 - foreground colour
 - background colour
 - angle of rotation
 - transparency.

Locations:
 - centre
 - cardinal directions
 - inter-cardinal directions.
2025-07-09 21:12:02 +02:00
47 changed files with 3717 additions and 293 deletions

View File

@@ -13,9 +13,13 @@ include_directories(${CMAKE_CURRENT_LIST_DIR}
Resources/InsaneWidget
Resources/Utilities
Resources/InsaneWidget/UI
Resources/StampWidget
Resources/StampWidget/UI
Resources/UI/S7)
add_subdirectory(Resources/Utilities)
add_subdirectory(Resources/StampWidget)
add_subdirectory(Resources/InsaneWidget)
add_executable(s7
@@ -25,6 +29,6 @@ add_executable(s7
install(TARGETS s7 RUNTIME DESTINATION bin)
target_link_libraries(s7 minutils insanewidget
target_link_libraries(s7 insanewidget
${wxWidgets_LIBRARIES}
)

View File

@@ -5,9 +5,10 @@ This is a simple scanning application with these goals:
- full page scan
- known number of pages to scan
- double-sided handling
- multiple output file format: PNG, JPEG, TIFF, PNM and PDF.
- multiple output file format: PNG, JPEG, TIFF, PNM and PDF
- apply an optional stamp on each page.
It is based on [libinsane](https://gitlab.gnome.org/World/OpenPaperwork/libinsane) and written with [wxWidgets](https://wxwidgets.org).
It is based on [libinsane](https://gitlab.gnome.org/World/OpenPaperwork/libinsane) and [wxWidgets](https://wxwidgets.org).
![S7_01](S7_01.png)
@@ -22,6 +23,7 @@ Inputs:
- the scanner and its minimal parameters (source, mode, resolution)
- page size
- output file format.
- an optional stamp text
Right click on the 'New' label to specify the number of faces and whether double-sided scanning is needed.
@@ -29,6 +31,14 @@ Right click on the 'New' label to specify the number of faces and whether double
Right click on the 'Scan' button to set the device properties.
'Ctrl - right click' on the 'Scan' button to define stamps.
Press 'Esc' to cancel and reset a scan project.
Press 'Ctrl + N' to open a new window.
Press 'Ctrl + Q' to close a window.
Outputs:
- a single PDF file with the number of requested pages

View File

@@ -9,25 +9,25 @@ find_package(LibInsane REQUIRED)
find_package(PoDoFo REQUIRED)
find_package(Paper REQUIRED)
include_directories(${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/UI
${CMAKE_CURRENT_LIST_DIR}/../StampWidget
${CMAKE_CURRENT_LIST_DIR}/../StampWidget/UI
${CMAKE_CURRENT_LIST_DIR}/../Utilities)
add_library(insanewidget STATIC
UI/InsaneWidget.cpp
UI/ScannerWidget.cpp
Common.h
DefsInsaneWidget.h
XInsaneWidget.cpp
XScannerWidget.cpp
InsaneWorker.cpp
PixelToImageWriter.cpp
PixelToPdfWriter.cpp)
target_link_libraries(insanewidget minutils
target_link_libraries(insanewidget minutils stampwidget
${wxWidgets_LIBRARIES}
${LIBINSANE_LIBRARIES}
${PODOFO_LIBRARIES}
${NETPBM_LIBRARIES}
${PAPER_LIBRARIES})

View File

@@ -1,14 +1,14 @@
// /*
// * File: Common.h
// * File: DefsInsaneWidget.h
// * Author: Saleem Edah-Tally - nmset@yandex.com
// * License : CeCILL-C
// * Copyright Saleem Edah-Tally - © 2025
// *
// * Created on 27 06 2025, 20:34
// * Created on 08 07 2025, 20:44
// */
#ifndef COMMON_H
#define COMMON_H
#ifndef DEFSINSANEWIDGET_H
#define DEFSINSANEWIDGET_H
#include <string>
#include <map>
@@ -27,4 +27,4 @@ static void UpdateExtensionsMap()
Extensions[PNM] = "pnm";
}
#endif // COMMON_H
#endif // DEFSINSANEWIDGET_H

View File

@@ -13,7 +13,6 @@
#include <sstream>
#include <fstream>
#include <paper.h>
#include <libinsane/log.h>
#include <libinsane/safebet.h>
#include <libinsane/error.h>
#include <libinsane/util.h>
@@ -22,8 +21,7 @@
using namespace std;
#define IERR(e) ("[" + to_string(e) + "] " + lis_strerror(e))
static bool gs_cancelRequested = false;
#define IERR(e) ("[InsaneWorker] - [" + to_string(e) + "] " + lis_strerror(e))
InsaneWorker::InsaneWorker ( InsaneWorkerEvent * evh )
{
@@ -273,7 +271,8 @@ bool InsaneWorker::ConfigureDevice(const std::string& deviceId,
* Sounds weird but needed.
*/
lis_set_option(m_sourceItem, OPT_NAME_SOURCE, source.c_str());
lis_set_option(m_sourceItem, OPT_NAME_RESOLUTION, to_string(resolution).c_str());
if (resolution > 0) // No resolution with v4l devices.
lis_set_option(m_sourceItem, OPT_NAME_RESOLUTION, to_string(resolution).c_str());
pair<double, double> br;
if (GetBottomRight(br))
{
@@ -306,6 +305,8 @@ bool InsaneWorker::Scan(const std::string& dir, const std::string& basename,
return false;
}
}
if (m_cancelRequested)
return false;
auto makeFileName = [&] (int index)
{
// std:format is not friendly with a variable padwidth; requires a literal format.
@@ -375,12 +376,13 @@ bool InsaneWorker::Scan(const std::string& dir, const std::string& basename,
m_evh->OnPageStartScan(filePath, pageIndex, imageAttributes);
do
{
if (gs_cancelRequested)
if (m_cancelRequested)
{
session->cancel(session);
m_rootSourceItem->close(m_rootSourceItem);
if (m_evh)
m_evh->OnSessionCancelled(filePath);
gs_cancelRequested = false;
m_cancelRequested = false;
return false;
}
try
@@ -410,7 +412,7 @@ bool InsaneWorker::Scan(const std::string& dir, const std::string& basename,
catch (std::bad_alloc& e)
{
m_rootSourceItem->close(m_rootSourceItem);
cout << "ABORT: " << e.what() << " - could not allocate " << bytesPerRow << " bytes." << endl;
cerr << "[InsaneWorker] - ABORT: " << e.what() << " - could not allocate " << bytesPerRow << " bytes." << endl;
if (m_evh)
m_evh->OnError("Insufficient system RAM.");
return false;
@@ -451,7 +453,7 @@ bool InsaneWorker::GetBottomRight(std::pair<double, double>& br)
if (res != PAPER_OK)
{
const string msg = "Could not initialise the paper library.";
cerr << msg << endl;
cerr << "[InsaneWorker] - " << msg << endl;
if (m_evh)
m_evh->OnError(msg);
return false;
@@ -461,14 +463,14 @@ bool InsaneWorker::GetBottomRight(std::pair<double, double>& br)
if (!p)
{
string msg = "Failed to find the requested paper; attempt to use a default paper size.";
cerr << msg << endl;
cerr << "[InsaneWorker] - " << msg << endl;
if (m_evh)
m_evh->OnError(msg);
p = defaultpaper();
if (!p)
{
msg = "Failed to find a default paper size; using the default scanner sizes.";
cerr << msg << endl;
cerr << "[InsaneWorker] - " << msg << endl;
if (m_evh)
m_evh->OnError(msg);
return false;
@@ -484,7 +486,7 @@ bool InsaneWorker::GetBottomRight(std::pair<double, double>& br)
else
{
const string msg = "The measurement unit of the paper size is not handled; using the default scanner sizes.";
cerr << msg << endl;
cerr << "[InsaneWorker] - " << msg << endl;
if (m_evh)
m_evh->OnError(msg);
return false;
@@ -497,7 +499,7 @@ bool InsaneWorker::GetBottomRight(std::pair<double, double>& br)
if (res != PAPER_OK)
{
const string msg = "Could not cleanly end the paper library.";
cerr << msg << endl;
cerr << "[InsaneWorker] - " << msg << endl;
if (m_evh)
m_evh->OnError(msg);
}
@@ -516,7 +518,7 @@ std::string InsaneWorker::ToLower(const std::string& input)
void InsaneWorker::Cancel()
{
gs_cancelRequested = true;
m_cancelRequested = true;
}
std::pair<int, int> InsaneWorker::UpdateStartAndIncrement(const int startPageIndex, const int increment,

View File

@@ -88,6 +88,7 @@ private:
lis_item * m_sourceItem = nullptr;
InsaneWorkerEvent * m_evh = nullptr;
bool m_cancelRequested = false;
std::vector<DeviceDescriptor> m_devices;
std::string m_deviceId;
std::string m_source = "FlatBed";

View File

@@ -8,14 +8,17 @@
// */
#include "PixelToImageWriter.h"
#include <Common.h>
#include <StampWorker.h>
#include <fstream>
#include <DefsInsaneWidget.h>
#include <DefsStampWidget.h>
using namespace std;
bool PixelToImageWriter::Convert(const std::string& pixelFilePath,
int imageWidth, int imageHeight,
int outputFormat, wxImage * image)
int imageWidth, int imageHeight,
std::vector<StampDescriptor*> * descriptors,
int outputFormat, wxImage * image)
{
UpdateExtensionsMap();
wxImage * outImage = image;
@@ -35,6 +38,16 @@ bool PixelToImageWriter::Convert(const std::string& pixelFilePath,
raw.assign(istreambuf_iterator<char>(ifs), istreambuf_iterator<char>());
outImage->SetData((unsigned char*) raw.data(), imageWidth, imageHeight, true); // true +++
if (descriptors)
{
for (StampDescriptor * descriptor : *descriptors)
{
if (descriptor)
{
StampWorker::StampBackground(*outImage, descriptor->image, descriptor->location);
}
}
}
switch (outputFormat)
{

View File

@@ -10,19 +10,26 @@
#ifndef PIXELTOIMAGEWRITER_H
#define PIXELTOIMAGEWRITER_H
#include "Common.h"
#include "DefsInsaneWidget.h"
#include <string>
#include <vector>
#include <wx/wx.h>
struct StampDescriptor;
/**
* Create an image file from a raw scanned file.\n
* Optionally,\n
* - use an wxImage object from the application\n
* - blend a stamp image on the converted image.\n
*/
class PixelToImageWriter
{
DECLARE_DYNAMIC_CLASS( PixelToImageWriter )
public:
static bool Convert(const std::string& pixelFilePath,
int imageWidth, int imageHeight, int outputFormat = PNG,
wxImage * image = nullptr);
int imageWidth, int imageHeight, std::vector<StampDescriptor*> * descriptors,
int outputFormat = PNG, wxImage * image = nullptr);
};
#endif // PIXELTOIMAGEWRITER_H

View File

@@ -9,7 +9,11 @@
#include "PixelToPdfWriter.h"
#include <iostream>
#include <sstream>
#include <memory>
#include <StampWorker.h>
#include <DefsInsaneWidget.h>
#include <DefsStampWidget.h>
using namespace std;
using namespace PoDoFo;
@@ -29,8 +33,9 @@ PixelToPdfWriter::PixelToPdfWriter()
}
bool PixelToPdfWriter::AddPageAt(const std::string& pixelFile, uint width, uint height, uint index,
PoDoFo::PdfPageSize pageSize, PoDoFo::PdfColorSpace)
bool PixelToPdfWriter::AddPageAt(const std::string& pixelFile, uint width, uint height, uint index,
std::vector<StampDescriptor*> * descriptors,
PoDoFo::PdfPageSize pageSize, PoDoFo::PdfColorSpace)
{
try
{
@@ -46,6 +51,23 @@ bool PixelToPdfWriter::AddPageAt(const std::string& pixelFile, uint width, uint
ifstream ifs(pixelFile, ios::binary);
string content;
content.assign(istreambuf_iterator<char>(ifs), istreambuf_iterator<char>());
if (descriptors)
{
for (StampDescriptor * descriptor : *descriptors)
{
if (!descriptor)
continue;
wxImage background;
background.SetData(reinterpret_cast<unsigned char*> (content.data()), width, height, true);
StampWorker::StampBackground(background, descriptor->image, descriptor->location);
stringstream ssStamped;
ssStamped.write((const char*) (background.GetData()), width * height * 3);
ssStamped.flush();
content.clear();
content = ssStamped.str();
}
}
bufferview bv(content);
const uint pageNumber = m_doc.GetPages().GetCount();

View File

@@ -12,13 +12,18 @@
#include <string>
#include <map>
#include <vector>
#include <podofo/podofo.h>
#include <wx/wx.h>
struct StampDescriptor;
/**
* Create a PDF document, append or insert pages from raw scanned files.\n
* Each image is a full page scan; it is scaled in the PDF document to the full
* page dimensions.\n
* Account for page size.
* Account for page size.\n
* Optionally, a stamp image may be blended on the page.
*/
class PixelToPdfWriter
{
@@ -27,6 +32,7 @@ public:
PixelToPdfWriter();
bool AddPageAt(const std::string& pixelFile, uint width, uint height, uint index,
std::vector<StampDescriptor*> * descriptors,
PoDoFo::PdfPageSize pageSize = PoDoFo::PdfPageSize::A4,
PoDoFo::PdfColorSpace = PoDoFo::PdfColorSpace::DeviceRGB /*Unused*/);
void Save(const std::string& pdfFile);

View File

@@ -103,6 +103,7 @@ InsaneWidget::~InsaneWidget()
void InsaneWidget::Init()
{
////@begin InsaneWidget member initialisation
szInsaneWidgetMain = NULL;
lblNewDoc = NULL;
txtNewDoc = NULL;
btnScan = NULL;
@@ -119,11 +120,11 @@ void InsaneWidget::CreateControls()
////@begin InsaneWidget content construction
InsaneWidget* itemPanel1 = this;
wxBoxSizer* itemBoxSizer2 = new wxBoxSizer(wxVERTICAL);
itemPanel1->SetSizer(itemBoxSizer2);
szInsaneWidgetMain = new wxBoxSizer(wxVERTICAL);
itemPanel1->SetSizer(szInsaneWidgetMain);
wxBoxSizer* itemBoxSizer1 = new wxBoxSizer(wxHORIZONTAL);
itemBoxSizer2->Add(itemBoxSizer1, 0, wxGROW|wxALL, 5);
szInsaneWidgetMain->Add(itemBoxSizer1, 0, wxGROW|wxALL, 5);
lblNewDoc = new wxStaticText( itemPanel1, ID_NewDoc_LBL, _("New"), wxDefaultPosition, wxDefaultSize, 0 );
if (InsaneWidget::ShowToolTips())
@@ -137,7 +138,7 @@ void InsaneWidget::CreateControls()
btnScan = new wxButton( itemPanel1, ID_Scan_BTN, _("Scan"), wxDefaultPosition, wxDefaultSize, 0 );
if (InsaneWidget::ShowToolTips())
btnScan->SetToolTip(_("'Left click' to start the scan project.\n'Right click' to show the scanner widget."));
btnScan->SetToolTip(_("'Left click' to start the scan project.\n'Right click' to show the scanner widget.\n'Ctrl + Right click' to show the Stamp dialog."));
itemBoxSizer1->Add(btnScan, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
////@end InsaneWidget content construction

View File

@@ -25,6 +25,7 @@
*/
////@begin forward declarations
class wxBoxSizer;
////@end forward declarations
/*!
@@ -39,7 +40,7 @@
#define SYMBOL_INSANEWIDGET_STYLE wxTAB_TRAVERSAL
#define SYMBOL_INSANEWIDGET_TITLE _("InsaneWidget")
#define SYMBOL_INSANEWIDGET_IDNAME ID_INSANEWIDGET
#define SYMBOL_INSANEWIDGET_SIZE wxSize(400, 300)
#define SYMBOL_INSANEWIDGET_SIZE wxDefaultSize
#define SYMBOL_INSANEWIDGET_POSITION wxDefaultPosition
////@end control identifiers
@@ -87,6 +88,7 @@ public:
static bool ShowToolTips();
////@begin InsaneWidget member variables
wxBoxSizer* szInsaneWidgetMain;
wxStaticText* lblNewDoc;
wxTextCtrl* txtNewDoc;
wxButton* btnScan;

View File

@@ -245,7 +245,7 @@
<string name="proxy-Font">""</string>
<string name="proxy-Foreground colour">""</string>
<string name="proxy-Header filename">"InsaneWidget.h"</string>
<long name="proxy-Height">300</long>
<long name="proxy-Height">-1</long>
<string name="proxy-Help text">""</string>
<bool name="proxy-Hidden">0</bool>
<string name="proxy-Icon">""</string>
@@ -257,7 +257,7 @@
<string name="proxy-Texture style">"Tiled"</string>
<string name="proxy-Title">"InsaneWidget"</string>
<string name="proxy-Tooltip text">""</string>
<long name="proxy-Width">400</long>
<long name="proxy-Width">-1</long>
<string name="proxy-Window kind">"wxPanel"</string>
<bool name="proxy-wxBORDER_THEME">0</bool>
<bool name="proxy-wxCAPTION">0</bool>
@@ -303,13 +303,21 @@
<long name="is-transient">0</long>
<long name="locked">0</long>
<long name="owns-file">1</long>
<string name="proxy-Member variable name">""</string>
<string name="proxy-AlignH">"Centre"</string>
<string name="proxy-AlignV">"Centre"</string>
<long name="proxy-Border">5</long>
<string name="proxy-Member variable name">"szInsaneWidgetMain"</string>
<string name="proxy-Orientation">"Vertical"</string>
<string name="proxy-Platform">"&lt;Any platform&gt;"</string>
<long name="proxy-Stretch factor">0</long>
<bool name="proxy-wxADJUST_MINSIZE">0</bool>
<bool name="proxy-wxBOTTOM">1</bool>
<bool name="proxy-wxFIXED_MINSIZE">0</bool>
<bool name="proxy-wxLEFT">1</bool>
<bool name="proxy-wxRESERVE_SPACE_EVEN_IF_HIDDEN">0</bool>
<bool name="proxy-wxRIGHT">1</bool>
<bool name="proxy-wxSHAPED">0</bool>
<bool name="proxy-wxTOP">1</bool>
<string name="title">"wxBoxSizer V"</string>
<long name="title-mode">0</long>
<string name="type">"dialog-control-document"</string>
@@ -538,7 +546,8 @@
<bool name="proxy-Separate files">0</bool>
<long name="proxy-Stretch factor">0</long>
<string name="proxy-Tooltip text">"'Left click' to start the scan project.
'Right click' to show the scanner widget."</string>
'Right click' to show the scanner widget.
'Ctrl + Right click' to show the Stamp dialog."</string>
<long name="proxy-Width">-1</long>
<bool name="proxy-wxADJUST_MINSIZE">0</bool>
<bool name="proxy-wxBOTTOM">1</bool>
@@ -654,13 +663,21 @@
<long name="is-transient">0</long>
<long name="locked">0</long>
<long name="owns-file">1</long>
<string name="proxy-AlignH">"Centre"</string>
<string name="proxy-AlignV">"Centre"</string>
<long name="proxy-Border">5</long>
<string name="proxy-Member variable name">""</string>
<string name="proxy-Orientation">"Vertical"</string>
<string name="proxy-Platform">"&lt;Any platform&gt;"</string>
<long name="proxy-Stretch factor">0</long>
<bool name="proxy-wxADJUST_MINSIZE">0</bool>
<bool name="proxy-wxBOTTOM">1</bool>
<bool name="proxy-wxFIXED_MINSIZE">0</bool>
<bool name="proxy-wxLEFT">1</bool>
<bool name="proxy-wxRESERVE_SPACE_EVEN_IF_HIDDEN">0</bool>
<bool name="proxy-wxRIGHT">1</bool>
<bool name="proxy-wxSHAPED">0</bool>
<bool name="proxy-wxTOP">1</bool>
<string name="title">"wxBoxSizer V"</string>
<long name="title-mode">0</long>
<string name="type">"dialog-control-document"</string>

View File

@@ -12,7 +12,10 @@
#include <MiscTools.h>
#include "PixelToImageWriter.h"
#include "PixelToPdfWriter.h"
#include <Common.h>
#include <DefsInsaneWidget.h>
#include <DefsStampWidget.h>
#include <XStampWidget.h>
#include <StampWorker.h>
using namespace std;
@@ -37,6 +40,45 @@ private:
wxWeakRef<XInsaneWidget> m_owner = nullptr;
};
// ----------------------------------------------------------------------------
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;
};
// ----------------------------------------------------------------------------
class ScanProjectHandler : public InsaneWorkerEvent
{
@@ -46,13 +88,16 @@ public:
{
}
/*
* mode, outputType, adf, doubleSided, total, PaperSize:
* mode, resolution, outputType, adf, doubleSided, total, PaperSize:
* 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.
* The 'resolution' variable is used downstream to distinguish between v4l
* and real scanners. v4l devices do not have a source and a resolution
* tunables.
*/
void SetMode(const string& mode)
{
@@ -60,6 +105,12 @@ public:
return;
m_mode = mode;
}
void SetResolution(int resolution)
{
if (m_pixelFiles.size())
return;
m_resolution = resolution;
}
void SetOutputType(int outputType)
{
if (m_pixelFiles.size())
@@ -101,6 +152,45 @@ public:
return;
m_paperSize = paperSize;
}
void SetStampDescriptors(vector<StampDescriptor*> * descriptors)
{
m_stampDescriptors = descriptors;
}
// 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"));
}
}
);
}
std::pair<int, int> GetStartAndIncrement(InsaneWorker * insaneWorker)
{
wxASSERT_MSG(insaneWorker != nullptr, "insaneWorker is NULL.");
@@ -114,26 +204,35 @@ public:
{
cerr << message << endl;
Reset();
MiscTools::MessageBox(_("A scan library error occurred."), true);
UpdateControlsStatus(true);
const wxString msg = _("A scan library error occurred.");
MiscTools::AsyncMessageBox(msg, true);
}
void OnError ( const std::string& message ) override
{
cerr << message << endl;
Reset();
MiscTools::MessageBox(_("A general error occurred."), true);
UpdateControlsStatus(true);
const wxString msg = _("A general error occurred.");
MiscTools::AsyncMessageBox(msg, true);
}
void OnSessionReadError(const std::string & filePath) override
{
const wxString msg = _("A session read error occurred.");
cerr << msg << endl;
Reset();
MiscTools::MessageBox(msg, true);
UpdateControlsStatus(true);
MiscTools::AsyncMessageBox(msg, true);
}
void OnSessionCancelled(const std::string & filePath) override
{
const wxString msg = _("Session cancelled.");
if (wxFileExists(filePath))
wxRemoveFile(filePath);
Reset();
MiscTools::MessageBox(msg, true);
UpdateControlsStatus(true);
const wxString msg = _("Session cancelled.");
MiscTools::AsyncMessageBox(msg, true);
m_sb->CallAfter(&TimeredStatusBar::SetTransientText, msg, 3000);
}
// Every time a page is fully scanned.
void OnPageEndScan(const std::string & filePath, uint pageIndex,
@@ -142,7 +241,7 @@ public:
m_startPageIndex = pageIndex;
m_pixelFiles[pageIndex] = {filePath, imageAttributes};
auto informProgress = [&] ()
auto informProgress = [this] ()
{
if (!m_sb || (m_total == 1))
return;
@@ -159,19 +258,19 @@ public:
wxString upperBoundMessage = _(". Turn the whole stack of pages.");
msg += upperBoundMessage;
}
m_sb->SetStatusText(msg);
m_sb->CallAfter(&wxStatusBar::SetStatusText, msg, 0);
};
if (m_outputType != PDF)
{
// Convert pixel file to PNG using netpbm.
if (!PixelToImageWriter::Convert(filePath, imageAttributes.width, imageAttributes.height, m_outputType))
// Convert pixel file to PNG using wxImage.
if (!PixelToImageWriter::Convert(filePath, imageAttributes.width, imageAttributes.height,
m_stampDescriptors, m_outputType))
{
const wxString msg = _("Failed to create output image.");
cerr << msg << endl;
if (m_sb)
m_sb->SetTransientText(msg);
m_sb->CallAfter(&TimeredStatusBar::SetTransientText, msg, 3000);
}
wxRemoveFile(filePath);
informProgress();
@@ -191,12 +290,13 @@ public:
cerr << msg << endl;
pageSize = PoDoFo::PdfPageSize::A4;
}
if (!m_pixelToPdfWriter->AddPageAt(filePath, imageAttributes.width, imageAttributes.height, index, pageSize))
if (!m_pixelToPdfWriter->AddPageAt(filePath, imageAttributes.width, imageAttributes.height, index,
m_stampDescriptors, pageSize))
{
const wxString msg = _("Failed to add page to PDF document.");
cerr << msg << endl;
if (m_sb)
m_sb->SetTransientText(msg);
m_sb->CallAfter(&TimeredStatusBar::SetTransientText, msg, 3000);
}
wxRemoveFile(filePath);
informProgress();
@@ -233,13 +333,27 @@ public:
m_pixelToPdfWriter.reset((new PixelToPdfWriter())); // For next scan project.
}
Reset();
m_owner->txtNewDoc->Clear();
m_owner->txtNewDoc->CallAfter(&wxTextCtrl::SetValue, wxString());
if (m_sb)
{
const wxString msg = _("Finished.");
m_sb->SetTransientText(msg);
m_sb->CallAfter(&TimeredStatusBar::SetTransientText, msg, 3000);
}
}
UpdateControlsStatus(true);
m_wip = false;
}
void OnStartScanSession(uint pageIndex, const ImageAttributes& imageAttributes) override
{
// Dealing with stamps only.
if (!m_stampDescriptors)
return;
for (StampDescriptor * descriptor : *m_stampDescriptors)
{
if (descriptor->text.IsEmpty())
continue;
descriptor->image = StampWorker::CreateStamp(descriptor, m_resolution);
}
}
void Reset()
{
@@ -248,6 +362,7 @@ public:
m_increment = 1;
m_totalEven = 0;
m_pixelFiles.clear();
m_wip = false;
}
private:
@@ -255,7 +370,9 @@ private:
wxWeakRef<TimeredStatusBar> m_sb = nullptr;
std::unique_ptr<PixelToPdfWriter> m_pixelToPdfWriter;
PixelFilesMap m_pixelFiles;
vector<StampDescriptor*> * m_stampDescriptors = nullptr;
string m_mode = "Color";
int m_resolution = -1;
uint m_outputType = PDF;
wxString m_paperSize = _T("A4");
bool m_adf = false;
@@ -264,55 +381,76 @@ private:
int m_totalEven = 0;
int m_startPageIndex = 0;
int m_increment = 1;
bool m_wip = false; // Is InsaneWorker busy?
};
// ----------------------------------------------------------------------------
XInsaneWidget::XInsaneWidget(wxWindow* parent, TimeredStatusBar * sb, wxConfig * config, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
IMPLEMENT_CLASS( XInsaneWidget, InsaneWidget )
XInsaneWidget::XInsaneWidget(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: InsaneWidget(parent, id, pos, size, style)
{}
XInsaneWidget::~XInsaneWidget() = default; // Important for mixing unique_ptr and PIMPL.
void XInsaneWidget::Setup(wxConfig * config, TimeredStatusBar * sb)
{
UpdateExtensionsMap();
m_config = config;
m_sb = sb;
// Restore known last values.
m_doubleSided = m_config->ReadBool("/Scanner/DoubleSided", false);
m_total = m_config->ReadLong("/Scanner/Total", 1);
lblNewDoc->Bind ( wxEVT_RIGHT_UP, &XInsaneWidget::OnLblNewDocRightClick, this );
txtNewDoc->Bind ( wxEVT_KEY_UP, &XInsaneWidget::OnTxtNewDocKeyPressed, this );
m_ptwScannerWidget = std::unique_ptr<wxPopupTransientWindow> (new wxPopupTransientWindow ( wxApp::GetGUIInstance()->GetTopWindow() ));
/*
* 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());
m_ptwScannerWidget->Show ( false );
m_scanProject = std::make_unique<ScanProjectHandler>(this, m_sb);
m_insaneWorker = std::make_unique<InsaneWorker>(m_scanProject.get());
m_scannerWidget = std::make_unique<XScannerWidget> ( m_ptwScannerWidget.get(), m_sb, m_insaneWorker.get() );
m_scannerWidget->SetConfig ( m_config );
m_scannerWidget = std::make_unique<XScannerWidget> (m_ptwScannerWidget.get());
m_scannerWidget->Setup ( m_config, m_insaneWorker.get(), m_sb );
btnScan->Enable(false);
btnScan->Bind ( wxEVT_RIGHT_UP, &XInsaneWidget::OnBtnScanRightClick, this );
btnScan->Bind ( wxEVT_LEFT_UP, &XInsaneWidget::OnBtnScanClick, this );
m_backgroundScannerDiscoveryEvh = std::make_unique<BackgroundScannerDiscoveryEVH>(this);
BackgroundScannerDiscovery * backgroundDiscovery = new BackgroundScannerDiscovery ( m_scannerWidget.get(),
m_backgroundScannerDiscoveryEvh.get());
backgroundDiscovery->Run();
m_backgroundDiscovery = new BackgroundScannerDiscovery ( m_scannerWidget.get(),
new BackgroundScannerDiscoveryEVH(this)); // Takes ownership.
m_backgroundDiscovery->Run();
}
XInsaneWidget::~XInsaneWidget() = default; // Important for mixing unique_ptr and PIMPL.
// Show a popup to specifiy the number of pages to scan and back-sided scanning.
void XInsaneWidget::OnLblNewDocRightClick ( wxMouseEvent& evt )
{
if (!lblNewDoc->IsEnabled())
{
evt.Skip();
return;
}
/*
* 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.
*/
m_pageStack.reset(new ConfigEditorPopup(wxApp::GetGUIInstance()->GetTopWindow(), m_config));
m_pageStack.reset(new ConfigEditorPopup(GetParent(), m_config));
PopupTransientWindow * ptw = m_pageStack->CreatePopup();
if ( !ptw )
{
evt.Skip();
return;
}
wxCheckBox * cb = m_pageStack->AddCheckBox (_("Double sided:"),_T("/Scanner/DoubleSided") );
wxCheckBox * cb = m_pageStack->AddCheckBox (_("Double sided:"),_T("/Scanner/DoubleSided"), &m_doubleSided );
cb->SetToolTip (_("Scan all front faces first, then all back faces in reverse order.") );
wxSpinCtrl * spn = m_pageStack->AddSpinCtrl (_("Total:"),_T("/Scanner/Total") );
wxSpinCtrl * spn = m_pageStack->AddSpinCtrl (_("Total:"),_T("/Scanner/Total"), &m_total );
spn->SetRange ( 1, 50 );
spn->SetToolTip (_("Total number of sides to scan (not total number of sheets).") );
m_pageStack->ShowPopup();
@@ -328,21 +466,49 @@ void XInsaneWidget::OnTxtNewDocKeyPressed ( wxKeyEvent& evt )
evt.Skip();
}
// Show the scanner widget.
// Show the scanner widget or the stamp widgets.
void XInsaneWidget::OnBtnScanRightClick ( wxMouseEvent& evt )
{
if ( m_scannerWidget->cmbDevices->GetCount() )
if (evt.ControlDown())
{
const wxSize current = m_scannerWidget->GetSize();
m_scannerWidget->SetSize ( wxSize ( 500, current.GetHeight() ) );
if (!m_dlgStampWidgets)
{
m_dlgStampWidgets = std::make_unique<wxDialog>
(this, 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();
}
MiscTools::ShowTransientPopup ( m_ptwScannerWidget.get(), m_scannerWidget.get() );
evt.Skip();
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);
}
// Start scanning.
void XInsaneWidget::OnBtnScanClick ( wxMouseEvent& evt )
{
if (m_scanProject->GetWip())
{
CancelScanning();
evt.Skip();
return;
}
const wxString dest = txtNewDoc->GetValue();
if (dest.IsEmpty())
{
@@ -352,22 +518,14 @@ void XInsaneWidget::OnBtnScanClick ( wxMouseEvent& evt )
}
const uint outputType = m_scannerWidget->GetScannerOutputType();
bool adf = false;
bool doubleSided = false;
uint total = 1;
wxString paperSize = _T("A4");
if (m_config)
{
doubleSided = m_config->ReadBool("/Scanner/DoubleSided", false);
total = m_config->ReadLong("/Scanner/Total", 1);
m_config->Read("/Scanner/Last/PaperSize", &paperSize, "A4");
}
wxString paperSize = m_scannerWidget->GetPaperSize();
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();
int resolution = 300;
int resolution = -1; // No resolution with v4l devices.
if ( !m_scannerWidget->GetScannerResolution().IsEmpty() )
{
resolution = std::stoi ( m_scannerWidget->GetScannerResolution().ToStdString() );
@@ -379,17 +537,28 @@ void XInsaneWidget::OnBtnScanClick ( wxMouseEvent& evt )
{
m_scanProject->SetADF(adf);
m_scanProject->SetMode(mode);
m_scanProject->SetResolution(resolution);
m_scanProject->SetOutputType(outputType);
m_scanProject->SetPaperSize(paperSize);
m_scanProject->SetDoubleSided(doubleSided);
m_scanProject->SetTotalNumberOfSides(total);
m_scanProject->SetDoubleSided(m_doubleSided);
m_scanProject->SetTotalNumberOfSides((uint) m_total);
if (m_stampWidgets)
{
m_stampDescriptors = m_stampWidgets->GetStampDescriptors();
m_scanProject->SetStampDescriptors(m_stampDescriptors);
}
std::pair<int, int> startAndIncrement = m_scanProject->GetStartAndIncrement(m_insaneWorker.get());
const int padWidth = ( ushort ) m_config->Read (_T("/Scanner/Counter/Length"), 2 );
bool res = m_insaneWorker->Scan(destFile.GetPath().ToStdString(),
destFile.GetName().ToStdString(),
startAndIncrement.first, padWidth, startAndIncrement.second);
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();
}
evt.Skip();
}
@@ -402,11 +571,30 @@ void XInsaneWidget::ResetScanProject()
void XInsaneWidget::CancelScanning()
{
if (m_insaneWorker)
if (m_insaneWorker && m_scanProject->GetWip())
{
m_insaneWorker->Cancel();
btnScan->Enable(false);
}
}
void XInsaneWidget::EnableScanButton(bool enable)
{
// Called from BackgroundScannerDiscoveryEVH::Done() only;
btnScan->Enable(enable);
m_backgroundDiscovery = nullptr;
}
bool XInsaneWidget::IsScannerDiscoveryRunning()
{
if (!m_backgroundDiscovery)
return false;
return m_backgroundDiscovery->IsRunning();
}
bool XInsaneWidget::IsScanning()
{
if (!m_scanProject)
return false;
return m_scanProject->GetWip();
}

View File

@@ -16,18 +16,23 @@
#include <InsaneWidget.h>
#include <InsaneWorker.h>
#include <XScannerWidget.h>
#include <XStampWidget.h>
#include <XStampWidgets.h>
#include <TimeredStatusBar.h>
#include <ConfigEditorPopup.h>
#include <memory>
#include <vector>
class BackgroundScannerDiscoveryEVH;
class BackgroundScannerDiscovery; // Is a wxThread.
class ScanProjectHandler; // An event handler extending InsaneWorkerEvent.
class XStampWidget;
struct StampDescriptor;
// Page index (not page number), {pixel file path, {pixel count, image width, image height}}.
typedef std::map<uint, std::tuple<std::string, InsaneWorkerEvent::ImageAttributes>> PixelFilesMap;
/**
* Shows a label, a disabled text box and a button.
* Shows a label, a disabled text box, and a button.
*
* Label:
* - Right click: define the number of pages to scan and double-sided scanning.
@@ -42,35 +47,47 @@ typedef std::map<uint, std::tuple<std::string, InsaneWorkerEvent::ImageAttribute
*
* Text box:
* - shows the path and the basename of the files to create. This must be set
* by the application in any conveniant manner.
* by the application in any convenient manner.
* Pressing the backspace key clears the text box.
*
* Button:
* - Right click: shows the scanner widget.
* - Left click: starts scanning.
* - Left click: starts or cancels scanning.
* - Ctrl + Right click: show the stamp widgets dialog.
*/
class XInsaneWidget : public InsaneWidget
{
DECLARE_DYNAMIC_CLASS( XInsaneWidget )
public:
virtual ~XInsaneWidget();
XInsaneWidget( wxWindow* parent, TimeredStatusBar * sb, wxConfig * config, wxWindowID id = SYMBOL_INSANEWIDGET_IDNAME, const wxPoint& pos = SYMBOL_INSANEWIDGET_POSITION, const wxSize& size = SYMBOL_INSANEWIDGET_SIZE, long style = SYMBOL_INSANEWIDGET_STYLE );
XInsaneWidget( wxWindow* parent, wxWindowID id = SYMBOL_INSANEWIDGET_IDNAME, const wxPoint& pos = SYMBOL_INSANEWIDGET_POSITION, const wxSize& size = SYMBOL_INSANEWIDGET_SIZE, long style = SYMBOL_INSANEWIDGET_STYLE );
void ResetScanProject();
void CancelScanning(); // Not tested, probably doesn't work as intended.
void CancelScanning();
void EnableScanButton(bool enable); // For CallAfter.
void Setup(wxConfig * config, TimeredStatusBar * sb);
bool IsScannerDiscoveryRunning();
bool IsScanning();
private:
wxConfig * m_config;
wxWeakRef<TimeredStatusBar> m_sb;
std::vector<StampDescriptor*> * m_stampDescriptors;
// Contains a popup to define the number of pages and double-sided scanning.
std::unique_ptr<ConfigEditorPopup> m_pageStack;
// Contains the scanner widget.
std::unique_ptr<wxPopupTransientWindow> m_ptwScannerWidget;
// Contains the stamp widgets.
std::unique_ptr<wxDialog> m_dlgStampWidgets;
std::unique_ptr<XStampWidgets> m_stampWidgets;
// Available devices and minimal options.
std::unique_ptr<XScannerWidget> m_scannerWidget;
std::unique_ptr<InsaneWorker> m_insaneWorker;
std::unique_ptr<BackgroundScannerDiscoveryEVH> m_backgroundScannerDiscoveryEvh;
BackgroundScannerDiscovery * m_backgroundDiscovery;
std::unique_ptr<ScanProjectHandler> m_scanProject;
bool m_doubleSided = false;
int m_total = 1;
void OnLblNewDocRightClick ( wxMouseEvent& evt );
void OnTxtNewDocKeyPressed ( wxKeyEvent& evt );

View File

@@ -9,26 +9,39 @@
#include "XScannerWidget.h"
#include "XClientData.hpp"
#include "Common.h"
#include "DefsInsaneWidget.h"
#include <libinsane/constants.h>
#include <fstream>
#include <vector>
using namespace std;
IMPLEMENT_CLASS( XScannerWidget, ScannerWidget )
XScannerWidget::XScannerWidget() : ScannerWidget()
{}
XScannerWidget::~XScannerWidget()
{}
XScannerWidget::XScannerWidget ( wxWindow* parent, TimeredStatusBar * sb,
InsaneWorker * insaneWorker,
wxWindowID id, const wxPoint& pos, const wxSize& size, long int style )
: ScannerWidget ( parent, id, pos, size, style )
XScannerWidget::XScannerWidget ( wxWindow* parent, wxWindowID id,
const wxPoint& pos, const wxSize& size, long int style )
: ScannerWidget ( parent, id, pos, size, style )
{}
void XScannerWidget::Setup ( wxConfig* config, InsaneWorker * insaneWorker,
TimeredStatusBar * sb)
{
UpdateExtensionsMap();
wxASSERT_MSG ( ( config != nullptr ),_T("config IS nullptr") );
wxASSERT_MSG ( ( insaneWorker != nullptr ),_T("insaneWorker IS nullptr") );
m_config = config;
m_sb = sb;
m_insaneWorker = insaneWorker;
UpdateExtensionsMap();
if (m_insaneWorker)
m_insaneWorkerEvh = m_insaneWorker->GetEventHandler();
cmbOutputType->Append (Extensions[PDF]); // Use the file extension and the enum index without client data.
#if wxUSE_LIBPNG
cmbOutputType->Append (Extensions[PNG]);
@@ -44,7 +57,7 @@ XScannerWidget::XScannerWidget ( wxWindow* parent, TimeredStatusBar * sb,
#endif
// Paper sizes handled by podofo.
cmbPaperSize->Append(wxArrayString({"A0", "A1", "A2", "A3", "A4", "A5", "A6",
"Letter", "Legal", "Tabloid"}));
"Letter", "Legal", "Tabloid"}));
btnRefreshDevices->Bind ( wxEVT_COMMAND_BUTTON_CLICKED, &XScannerWidget::OnButtonRefreshDevices, this );
cmbDevices->Bind ( wxEVT_COMMAND_COMBOBOX_SELECTED, &XScannerWidget::OnDeviceSelected, this );
@@ -54,6 +67,20 @@ XScannerWidget::XScannerWidget ( wxWindow* parent, TimeredStatusBar * sb,
cmbResolution->Bind ( wxEVT_COMMAND_COMBOBOX_SELECTED, &XScannerWidget::OnResolutionSelected, this );
cmbPaperSize->Bind ( wxEVT_COMMAND_COMBOBOX_SELECTED, &XScannerWidget::OnPaperSizeSelected, this );
GetParent()->Bind ( wxEVT_SHOW, &XScannerWidget::OnActivated, this );
wxString lastImageType;
m_config->Read (_T("/Scanner/Last/OutputType"), &lastImageType );
if ( cmbOutputType->FindString ( lastImageType, true ) != wxNOT_FOUND )
cmbOutputType->SetStringSelection ( lastImageType );
else
cmbOutputType->Select(0);
wxString lastPaperSize;
m_config->Read (_T("/Scanner/Last/PaperSize"), &lastPaperSize );
if ( cmbPaperSize->FindString ( lastPaperSize, true ) != wxNOT_FOUND )
cmbPaperSize->SetStringSelection ( lastPaperSize );
else
cmbPaperSize->Select(4);
}
bool XScannerWidget::FindDevices ( bool async )
@@ -113,28 +140,6 @@ void XScannerWidget::OnButtonRefreshDevices ( wxCommandEvent& evt )
btnRefreshDevices->Enable ( true );
}
void XScannerWidget::SetConfig ( wxFileConfig* config )
{
m_config = config;
if ( !m_config )
return;
wxString lastImageType;
m_config->Read (_T("/Scanner/Last/OutputType"), &lastImageType );
if ( cmbOutputType->FindString ( lastImageType, true ) != wxNOT_FOUND )
cmbOutputType->SetStringSelection ( lastImageType );
else
cmbOutputType->Select(0);
wxString lastPaperSize;
m_config->Read (_T("/Scanner/Last/PaperSize"), &lastPaperSize );
if ( cmbPaperSize->FindString ( lastPaperSize, true ) != wxNOT_FOUND )
cmbPaperSize->SetStringSelection ( lastPaperSize );
else
cmbPaperSize->Select(4);
}
void XScannerWidget::OnDeviceSelected ( wxCommandEvent& evt )
{
if ( !m_config || ( cmbDevices->GetSelection() == wxNOT_FOUND ) )
@@ -306,7 +311,10 @@ wxThread::ExitCode BackgroundScannerDiscovery::Entry()
evt.SetShow ( true );
m_owner->OnActivated ( evt );
if (m_evh)
{
m_evh->OnDone();
wxDELETE(m_evh);
}
}
return ( wxThread::ExitCode ) 0;
}
@@ -316,3 +324,5 @@ wxThread::ExitCode BackgroundScannerDiscovery::Entry()
void BackgroundScannerDiscoveryEvent::OnDone()
{}
BackgroundScannerDiscoveryEvent::~BackgroundScannerDiscoveryEvent() = default;

View File

@@ -16,7 +16,6 @@
#include <ScannerWidget.h>
#include "TimeredStatusBar.h"
#include "InsaneWorker.h"
#include "Common.h"
#include <map>
class BackgroundScannerDiscoveryEvent;
@@ -33,20 +32,19 @@ class BackgroundScannerDiscoveryEvent;
class XScannerWidget : public ScannerWidget
{
DECLARE_DYNAMIC_CLASS( XScannerWidget )
friend class BackgroundScannerDiscovery;
public:
XScannerWidget() {};
~XScannerWidget();
XScannerWidget();
virtual ~XScannerWidget();
XScannerWidget ( wxWindow* parent, TimeredStatusBar * sb,
InsaneWorker * insaneWorker,
wxWindowID id = wxID_ANY,
XScannerWidget ( wxWindow* parent, wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = SYMBOL_SCANNERWIDGET_SIZE,
long int style = SYMBOL_SCANNERWIDGET_STYLE );
void SetConfig ( wxConfig * config );
void Setup ( wxConfig* config, InsaneWorker * insaneWorker,
TimeredStatusBar * sb = nullptr );
bool FindDevices ( bool async = false );
wxString GetCurrentDeviceId() const;
wxString GetScannerMode() const
@@ -101,7 +99,7 @@ public:
BackgroundScannerDiscovery ( XScannerWidget * owner, BackgroundScannerDiscoveryEvent * evh )
{
m_owner = owner;
m_evh = evh;
m_evh = evh; // Owned here.
}
virtual ~BackgroundScannerDiscovery() {};
virtual ExitCode Entry();
@@ -114,6 +112,7 @@ class BackgroundScannerDiscoveryEvent
{
public:
virtual void OnDone();
virtual ~BackgroundScannerDiscoveryEvent();
};

View File

@@ -15,6 +15,8 @@ DEST=$DOMAIN/${COMPONENT_NAME}.po
[ -f $DEST ] && cp $DEST $DEST.bak-$(date +%F-%T)
[ ! -f $DEST ] && touch $DEST
# Memo: use '--no-location' transiently to get rid of obsolete file locations.
xgettext --keyword=_ -d $DOMAIN $JOIN -o $DEST --c++ --from-code=UTF-8 $(find $SRC -type f -name "*.cpp")
xgettext --keyword=_ -d $DOMAIN -j -o $DEST --c++ --from-code=UTF-8 $(find $SRC -type f -name "*.h")

View File

@@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-06-26 22:47+0200\n"
"PO-Revision-Date: 2025-06-26 22:47+0200\n"
"POT-Creation-Date: 2025-07-14 13:28+0200\n"
"PO-Revision-Date: 2025-07-15 19:27+0200\n"
"Last-Translator: Saleem EDAH-TALLY <set@nmset.info>\n"
"Language-Team: French <kde-francophone@kde.org>\n"
"Language: fr_FR\n"
@@ -15,9 +15,9 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Lokalize 25.04.2\n"
"X-Generator: Lokalize 25.04.3\n"
#: ../../Resources/UI/S7/s7.cpp:126 ../..//Resources/UI/S7/s7.cpp:126
#: ../../Resources/UI/S7/s7.cpp:126
msgid ""
"Select a destination directory.\n"
"Double-click to go to the selected directory."
@@ -25,11 +25,11 @@ msgstr ""
"Choisissez un dossier destination\n"
"Double-clic pour aller au dossier."
#: ../../Resources/UI/S7/s7.cpp:130 ../..//Resources/UI/S7/s7.cpp:130
#: ../../Resources/UI/S7/s7.cpp:130
msgid "Basename"
msgstr "Nom de base"
#: ../../Resources/UI/S7/s7.cpp:132 ../..//Resources/UI/S7/s7.cpp:132
#: ../../Resources/UI/S7/s7.cpp:132
msgid ""
"Specify a destination file basename (without extension).\n"
"\n"
@@ -39,15 +39,15 @@ msgstr ""
"\n"
"'CTRL + clic' : à propos"
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:128
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:129
msgid "New"
msgstr "Nouveau"
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:130
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:131
msgid "'Right' click to define a scan project."
msgstr "Clic droit pour définir un projet de numérisation"
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:135
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:136
msgid ""
"Full path to destination file without the extension; it is determined by the "
"output type."
@@ -55,11 +55,11 @@ msgstr ""
"Chemin complet du fichier de destination sans l'extension; elle est "
"déterminée par le format de sortie."
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:138
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:139
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:182
msgid "Scan"
msgstr "Numériser"
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:140
msgid ""
"'Left click' to start the scan project.\n"
"'Right click' to show the scanner widget."
@@ -91,139 +91,113 @@ msgstr "Mode de numérisation"
msgid "Scan resolution"
msgstr "Résolution de la numérisation"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:128
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:107
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:208
msgid "A scan library error occurred."
msgstr "Une erreur de bibliothèque est survenue."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:134
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:113
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:216
msgid "A general error occurred."
msgstr "Une erreur générale est survenue."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:138
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:117
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:221
msgid "A session read error occurred."
msgstr "Une erreur de lecture est survenue."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:145
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:124
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:233
msgid "Session cancelled."
msgstr "Session annulée."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:162
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:141
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:250
msgid "Scanning: "
msgstr "Numérisation :"
msgstr "Numérisation : "
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:164
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:143
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:252
msgid "Front face: "
msgstr "Recto :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:166
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:145
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:254
msgid "Back face: "
msgstr "Verso :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:170
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:149
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:258
msgid ". Turn the whole stack of pages."
msgstr ". Retournez toute la pile de pages."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:182
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:161
msgid "Failed to create PNG image."
msgstr "Échec de création d'image PNG."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:201
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:180
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:289
msgid "Wrong paper size: "
msgstr "Mauvaise taille de papier :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:201
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:180
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:289
msgid "; using A4."
msgstr "; utilisation du format A4."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:207
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:186
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:296
msgid "Failed to add page to PDF document."
msgstr "Échec d'ajout de page au document PDF."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:217
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:255
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:196
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:234
msgid "Unhandled output file format."
msgstr "Format de fichier de sortie non pris en charge."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:264
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:243
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:339
msgid "Finished."
msgstr "Terminé."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:333
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:317
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:452
msgid "Scan all front faces first, then all back faces in reverse order."
msgstr ""
"Numériser tous les faces recto, puis toutes les faces verso en ordre inverse."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:336
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:320
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:455
msgid "Total number of sides to scan (not total number of sheets)."
msgstr ""
"Nombre total de faces à numériser (et non pas le nombre total de feuilles)."
#: ../../Resources/InsaneWidget/lib/TimeredStatusBar.cpp:44
#: ../../Resources/Utilities/TimeredStatusBar.cpp:44
#: ../..//Resources/Utilities/TimeredStatusBar.cpp:44
msgid "NUM"
msgstr "NUM"
#: ../../Resources/InsaneWidget/lib/TimeredStatusBar.cpp:52
#: ../../Resources/Utilities/TimeredStatusBar.cpp:52
#: ../..//Resources/Utilities/TimeredStatusBar.cpp:52
msgid "CAPS"
msgstr "CAPS"
#: ../../Resources/InsaneWidget/XScannerWidget.cpp:50
#: ../../Resources/InsaneWidget/XScannerWidget.cpp:89
msgid "Searching for devices..."
msgstr "Recherche de périphériques..."
#: ../../Resources/InsaneWidget/XScannerWidget.cpp:53
#: ../../Resources/InsaneWidget/XScannerWidget.cpp:92
msgid "Could not initialise insane api."
msgstr "Échec d'initialisation de la bibliothèque 'insane'."
#: ../../XS7.cpp:46
msgid "'Shift + left' click'' to generate a new destination file name."
msgstr "'Maj + clic gauche' pour générer un nouveau fichier de sortie."
#: ../../XS7.cpp:69 ../../XS7.cpp:104
#: ../../XS7.cpp:102
msgid "Could not launch default file manager"
msgstr "Échec de lancement du gestionnaire de fichier par défaut."
#: ../../XS7.cpp:93 ../../XS7.cpp:128
#: ../../XS7.cpp:143
msgid "Invalid folder name."
msgstr "Nom de dossier invalide."
#: ../../XS7.cpp:101 ../../XS7.cpp:136
#: ../../XS7.cpp:151
msgid "Invalid file basename."
msgstr "Nom de base de fichier invalide."
#: ../../XS7.cpp:123 ../../XS7.cpp:158
msgid "Copyright: Saleem Edah-Tally [Surgeon] [Hobbyist developer]\n"
msgstr "Copyright: Saleem Edah-Tally [Chirurgien] [Développeur par hobby]\n"
#: ../../XS7.cpp:124 ../../XS7.cpp:159
#: ../../XS7.cpp:174
msgid "License: CeCILL/CeCILL-C per file header."
msgstr "Licence : CeCILL/CeCILL-C selon les entêtes de fichier."
#: ../../Resources/UI/S7/s7.h:44 ../..//Resources/UI/S7/s7.h:44
#: ../../Resources/UI/S7/s7.h:44
msgid "S7"
msgstr "S7"
#: ../../Resources/InsaneWidget/UI/InsaneWidget.h:40
#: ../../Resources/InsaneWidget/UI/InsaneWidget.h:41
msgid "InsaneWidget"
msgstr "InsaneWidget"
@@ -255,25 +229,22 @@ msgstr "Résolution :"
msgid "Paper size:"
msgstr "Format de page :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:332
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:316
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:451
msgid "Double sided:"
msgstr "Recto-verso :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:334
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:318
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:453
msgid "Total:"
msgstr "Total :"
#: ../../XS7.cpp:46 ../../XS7.cpp:81
#: ../../XS7.cpp:50
msgid "'Shift + left' click to generate a new destination file name."
msgstr "'Maj + clic gauche' pour générer un nouveau fichier de sortie."
#: ../../XS7.cpp:121 ../../XS7.cpp:45 ../../XS7.cpp:156
#: ../../Resources/UI/S7/s7app.cpp:174
msgid " - version "
msgstr " - version "
#: ../../XS7.cpp:122 ../../XS7.cpp:157
msgid ""
", using InsaneWidget.\n"
"\n"
@@ -281,22 +252,191 @@ msgstr ""
", utilisant InsaneWidget.\n"
"\n"
#: ../../Resources/InsaneWidget/XScannerWidget.cpp:73
#: ../../Resources/InsaneWidget/XScannerWidget.cpp:112
msgid " device(s) found."
msgstr " périphériques trouvé(s)."
#: ../../XS7.cpp:29
#: ../../Resources/UI/S7/s7app.cpp:158
msgid "Config file tag."
msgstr "Suffixe du fichier de configuration."
#: ../../XS7.cpp:30
#: ../../Resources/UI/S7/s7app.cpp:159
msgid "Show version and quit."
msgstr "Afficher la version et quitter."
#: ../../XS7.cpp:31
#: ../../Resources/UI/S7/s7app.cpp:160
msgid "Show help and quit."
msgstr "Afficher l'aide et quitter."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:352
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:515
msgid "Destination file missing."
msgstr "Fichier de destination manquant."
#: ../../Resources/StampWidget/UI/StampWidget.cpp:140
msgid ""
"Create a stamp with this text, which can be multiline.\n"
"\n"
"CTRL + S: save the current text.\n"
"CTRL + R: restore the saved text."
msgstr ""
"Créer un tampon utilisant ce texte qui peut être multi-ligne.\n"
"\n"
"CTRL + S : sauvegarder le texte actuel.\n"
"CTRL + R : restaurer le texte sauvegardé."
#: ../../Resources/StampWidget/UI/StampWidget.cpp:188
msgid "Select the font of the stamp text."
msgstr "Sélectionnez la police de caractère du texte."
msgid "Select the colour of the stamp text."
msgstr "Sélectionnez la couleur du texte."
#: ../../Resources/StampWidget/UI/StampWidget.cpp:199
msgid "Select the rotation angle of the stamp text."
msgstr "Sélectionnez l'angle de rotation du texte."
msgid "Stamp"
msgstr "Tampon"
#: ../../Resources/InsaneWidget/PixelToImageWriter.cpp:35
msgid "Failed to read raw file."
msgstr "Échec de lecture du fichier brut."
#: ../../Resources/InsaneWidget/PixelToImageWriter.cpp:69
msgid "Unhandled output image format."
msgstr "Format de fichier d'image en sortie non pris en charge."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:270
msgid "Failed to create output image."
msgstr "Échec de création de l'image de sortie."
#: ../../Resources/StampWidget/UI/StampWidget.h:57
msgid "StampWidget"
msgstr ""
#: ../../Resources/StampWidget/UI/StampWidget.cpp:171
msgid "Location of the stamp on the output."
msgstr "Place du tampon dans la sortie."
#: ../../Resources/StampWidget/UI/StampWidgets.cpp:127
msgid "+"
msgstr ""
#: ../../Resources/StampWidget/UI/StampWidgets.cpp:129
msgid "Add a stamp widget."
msgstr "Ajouter un widget 'Tampon'."
#: ../../Resources/StampWidget/UI/StampWidgets.cpp:132
msgid "-"
msgstr ""
#: ../../Resources/StampWidget/UI/StampWidgets.cpp:134
msgid "Remove the selected stamp widget."
msgstr "Supprimer le widget 'Tampon' sélectionné."
#: ../../Resources/StampWidget/XStampWidget.cpp:76
msgid "Centre"
msgstr "Centre"
#: ../../Resources/StampWidget/XStampWidget.cpp:77
msgid "North"
msgstr "Nord"
#: ../../Resources/StampWidget/XStampWidget.cpp:77
msgid "South"
msgstr "Sud"
#: ../../Resources/StampWidget/XStampWidget.cpp:77
msgid "West"
msgstr "Ouest"
#: ../../Resources/StampWidget/XStampWidget.cpp:78
msgid "North-east"
msgstr "Nord-est"
#: ../../Resources/StampWidget/XStampWidget.cpp:78
msgid "North-west"
msgstr "Nord-ouest"
#: ../../Resources/StampWidget/XStampWidget.cpp:79
msgid "South-east"
msgstr "Sud-est"
#: ../../Resources/StampWidget/XStampWidget.cpp:79
msgid "South-west"
msgstr "Sud-ouest"
#: ../../Resources/StampWidget/XStampWidget.cpp:77
msgid "East"
msgstr "Est"
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:141
msgid ""
"'Left click' to start the scan project.\n"
"'Right click' to show the scanner widget.\n"
"'Ctrl + Right click' to show the Stamp dialog."
msgstr ""
"'Clic gauche' pour démarrer la numérisation.\n"
"'Clic droit' pour afficher les options du numériseur.\n"
"'Ctrl + Clic droit' pour définir un tampon."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:477
msgid "Stamps"
msgstr "Tampons"
#: ../../Resources/StampWidget/UI/StampWidgets.h:44
msgid "StampWidgets"
msgstr ""
#: ../../Resources/StampWidget/UI/StampWidget.cpp:149
msgid "Foreground:"
msgstr "Avant-plan :"
#: ../../Resources/StampWidget/UI/StampWidget.cpp:154
msgid "Foreground colour of the text."
msgstr "Couleur du texte."
#: ../../Resources/StampWidget/UI/StampWidget.cpp:157
msgid "Background:"
msgstr "Arrière-plan :"
#: ../../Resources/StampWidget/UI/StampWidget.cpp:162
msgid "Background colour of the text."
msgstr "Couleur de l'arrière-plan."
#: ../../Resources/StampWidget/UI/StampWidget.cpp:165
msgid "Location:"
msgstr "Position :"
#: ../../Resources/StampWidget/UI/StampWidget.cpp:177
msgid "Click to update the preview."
msgstr "Cliquez pour actualiser l'aperçu."
#: ../../Resources/StampWidget/UI/StampWidget.cpp:191
msgid "Transparent"
msgstr "Transparent"
#: ../../Resources/StampWidget/UI/StampWidget.cpp:194
msgid ""
"Check for a completely transparent background and for a transparent text."
msgstr "Activez la transparence du texte et de l'arrière-plan. "
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:189
msgid "Cancel"
msgstr "Annuler"
#: ../../XS7.cpp:75
msgid "Veto: scanner discovery is running."
msgstr "Veto : recherche de périphériques en cours."
#: ../../XS7.cpp:173
msgid "Copyright: Saleem Edah-Tally [Surgeon] [Hobbyist developer]"
msgstr "Copyright: Saleem Edah-Tally [Chirurgien] [Développeur par hobby]"
#: ../../XS7.cpp:175
msgid "version "
msgstr "version "
#: ../../XS7.cpp:175
msgid ", using InsaneWidget."
msgstr ", utilisant InsaneWidget."

View File

@@ -1,7 +1,7 @@
#Simple PKGBUILD that suits my need.
pkgname=(s7)
pkgver=1
pkgver=2
pkgrel=0
arch=('x86_64' 'i686' 'armv7h' 'aarch64')
url='http://github.com/nmset/s7/'

View File

@@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.5)
project(StampWidget)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMake")
find_package(wxWidgets COMPONENTS base core CONFIG REQUIRED)
include_directories(${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/UI)
add_library(stampwidget STATIC
DefsStampWidget.h
UI/StampWidget.cpp
UI/StampWidgets.cpp
XStampWidget.cpp
XStampWidgets.cpp
StampWorker.cpp)
target_link_libraries(stampwidget
${wxWidgets_LIBRARIES})

View File

@@ -0,0 +1,30 @@
// /*
// * File: Common.h
// * Author: Saleem Edah-Tally - nmset@yandex.com
// * License : CeCILL-C
// * Copyright Saleem Edah-Tally - © 2025
// *
// * Created on 27 06 2025, 20:34
// */
#ifndef COMMON_H
#define COMMON_H
#include <wx/wx.h>
// ----------------------------------------------------------------------------
enum {CENTRE = 0, NORTH, SOUTH, EAST, WEST,
NORTH_EAST, NORTH_WEST, SOUTH_EAST, SOUTH_WEST};
struct StampDescriptor
{
wxImage image;
wxString text;
wxFont font = wxNullFont;
wxColour foregroundColour = wxNullColour;
wxColour backgroundColour = wxNullColour;
int rotationAngle = 45;
int location = 0;
bool transparent = true;
};
#endif // COMMON_H

View File

@@ -0,0 +1,121 @@
// /*
// * File: StampWorker.cpp
// * Author: Saleem Edah-Tally - nmset@yandex.com
// * License : CeCILL-C
// * Copyright Saleem Edah-Tally - © 2025
// *
// * Created on 02 07 2025, 21:23
// */
#include "StampWorker.h"
#include <math.h> // M_PI
wxImage StampWorker::CreateStamp(StampDescriptor * descriptor, int scanResolution)
{
/*
* Rescale the font point size of the stamp according to the scan resolution.
* Exclude rescaling on v4l that does not have a resolution parameter.
*/
wxFont dcFont(descriptor->font);
double scale = (scanResolution > 0)
? (double) scanResolution / 72.0
:1.0;
wxCoord extentWidth, extentHeight, textLineHeight;
{
wxMemoryDC dc;
if (scale != 1.0)
dcFont.SetFractionalPointSize(descriptor->font.GetFractionalPointSize() * scale);
dc.SetFont(dcFont); // Mandatory despite set below.
dc.GetMultiLineTextExtent(descriptor->text, &extentWidth, &extentHeight, &textLineHeight, &dcFont);
}
wxBitmap bmp;
// bmp.UseAlpha(true); // Not mandatory.
bmp.Create(extentWidth, extentHeight); // Memo: See CreateWithDIPSize
wxImage img0, img;
wxMemoryDC dc(bmp);
dc.SetFont(dcFont);
if (descriptor->transparent)
{
// +++, Use the red channel only: used by ConvertColourToAlpha().
// The chosen value is inversely proportional to the transparency of the text itself.
dc.SetTextForeground(wxColour(128, 0, 0)); // A good mean is reasonable.
dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
}
else
{
dc.SetTextForeground(descriptor->foregroundColour);
dc.SetTextBackground(descriptor->backgroundColour);
dc.SetBackgroundMode(wxBRUSHSTYLE_SOLID);
}
dc.DrawText(descriptor->text, wxPoint(0, 0));
img0 = bmp.ConvertToImage();
if (!descriptor->transparent)
img0.InitAlpha(); // So that there is no dark surrounding if it is rotated.
img = img0.Rotate((M_PI / 180.0) * descriptor->rotationAngle, wxPoint(0, 0));
if (descriptor->transparent)
{
img.ConvertColourToAlpha(descriptor->foregroundColour.GetRed(),
descriptor->foregroundColour.GetGreen(),
descriptor->foregroundColour.GetBlue()); // The target colour is passed in.
img.ChangeSaturation(-0.7); // Slightly better looking.
}
return img;
}
void StampWorker::StampBackground(wxImage& background,
const wxImage& stamp, int location)
{
// Simply pasting the stamp on the background, i.e., the scanned page.
wxPoint bgCentre(background.GetWidth() / 2, background.GetHeight() / 2);
wxPoint stampCentre(stamp.GetWidth() / 2, stamp.GetHeight() / 2);
int x = 0, y = 0;
switch (location)
{
case NORTH:
x = bgCentre.x - stampCentre.x;
y = 0;
break;
case SOUTH:
x = bgCentre.x - stampCentre.x;
y = background.GetHeight() - stamp.GetHeight();
break;
case EAST:
x = background.GetWidth() - stamp.GetWidth();
y = bgCentre.y - stampCentre.y;
break;
case WEST:
x = 0;
y = bgCentre.y - stampCentre.y;
break;
case NORTH_EAST:
x = background.GetWidth() - stamp.GetWidth();
y = 0;
break;
case NORTH_WEST:
x = 0;
y = 0;
break;
case SOUTH_EAST:
x = background.GetWidth() - stamp.GetWidth();
y = background.GetHeight() - stamp.GetHeight();
break;
case SOUTH_WEST:
x = 0;
y = background.GetHeight() - stamp.GetHeight();
break;
default: // CENTRE
x = bgCentre.x - stampCentre.x;
y = bgCentre.y - stampCentre.y;
break;
}
wxPoint stampLocation(x, y);
background.Paste(stamp, stampLocation.x, stampLocation.y, wxIMAGE_ALPHA_BLEND_COMPOSE);
}

View File

@@ -0,0 +1,38 @@
// /*
// * File: StampWorker.h
// * Author: Saleem Edah-Tally - nmset@yandex.com
// * License : CeCILL-C
// * Copyright Saleem Edah-Tally - © 2025
// *
// * Created on 02 07 2025, 21:23
// */
#ifndef STAMPWORKER_H
#define STAMPWORKER_H
#include <wx/wx.h>
#include "DefsStampWidget.h"
struct StampDescriptor;
/**
* A stamp is understood here as\n
* - a transparent text in a transparent frame with no borders\n
* - an opaque text on an opaque background with no borders.\n
*
* The text may be rotated. Actually, an initial image with the text is rotated
* and its new orthogonal bounds accepted (fortunately, we don't have to compute
* that).\n
* The font point size is rescaled to match the scan resolution. If the
* scanResolution parameter is invalid (<=0), the font is not rescaled.
*/
class StampWorker
{
DECLARE_DYNAMIC_CLASS( StampWorker )
public:
static wxImage CreateStamp(StampDescriptor * descriptor, int scanResolution = -1);
static void StampBackground(wxImage& background, const wxImage& stamp, int location = CENTRE);
};
#endif // STAMPWORKER_H

View File

@@ -0,0 +1,239 @@
/////////////////////////////////////////////////////////////////////////////
// Name: StampWidget.cpp
// Purpose:
// Author: Saleem EDAH-TALLY
// Modified by:
// Created: mar. 01 juil. 2025 19:14:05
// RCS-ID:
// Copyright: Copyright Saleem EDAH-TALLY. All rights reserved.
// Licence:
/////////////////////////////////////////////////////////////////////////////
// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
////@begin includes
////@end includes
#include "StampWidget.h"
////@begin XPM images
////@end XPM images
/*
* StampWidget type definition
*/
IMPLEMENT_DYNAMIC_CLASS( StampWidget, wxPanel )
/*
* StampWidget event table definition
*/
BEGIN_EVENT_TABLE( StampWidget, wxPanel )
////@begin StampWidget event table entries
////@end StampWidget event table entries
END_EVENT_TABLE()
/*
* StampWidget constructors
*/
StampWidget::StampWidget()
{
Init();
}
StampWidget::StampWidget( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style )
{
Init();
Create(parent, id, pos, size, style);
}
/*
* StampWidget creator
*/
bool StampWidget::Create( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style )
{
////@begin StampWidget creation
SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY);
wxPanel::Create( parent, id, pos, size, style );
CreateControls();
if (GetSizer())
{
GetSizer()->SetSizeHints(this);
}
Centre();
////@end StampWidget creation
return true;
}
/*
* StampWidget destructor
*/
StampWidget::~StampWidget()
{
////@begin StampWidget destruction
////@end StampWidget destruction
}
/*
* Member initialisation
*/
void StampWidget::Init()
{
////@begin StampWidget member initialisation
szStampWidgetMain = NULL;
txtStamp = NULL;
szStampWidgetH0 = NULL;
szStampWidgetFlexGrid = NULL;
lblForegroundColour = NULL;
cpkForegroundStamp = NULL;
lblBackgroundColour = NULL;
cpkBackgroundStamp = NULL;
lblLocation = NULL;
cmbStampLocation = NULL;
panBitmapPreview = NULL;
szBitmapPreviewInPanel = NULL;
szStampWidgetH1 = NULL;
fpkStamp = NULL;
tglTransparent = NULL;
sldTextRotationAngle = NULL;
////@end StampWidget member initialisation
}
/*
* Control creation for StampWidget
*/
void StampWidget::CreateControls()
{
////@begin StampWidget content construction
StampWidget* itemPanel1 = this;
szStampWidgetMain = new wxBoxSizer(wxVERTICAL);
itemPanel1->SetSizer(szStampWidgetMain);
txtStamp = new wxTextCtrl( itemPanel1, ID_TEXTCTRL, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE );
if (StampWidget::ShowToolTips())
txtStamp->SetToolTip(_("Create a stamp with this text, which can be multiline.\n\nCTRL + S: save the current text.\nCTRL + R: restore the saved text."));
szStampWidgetMain->Add(txtStamp, 0, wxGROW|wxALL, 5);
szStampWidgetH0 = new wxBoxSizer(wxHORIZONTAL);
szStampWidgetMain->Add(szStampWidgetH0, 0, wxALIGN_LEFT|wxALL, 5);
szStampWidgetFlexGrid = new wxFlexGridSizer(0, 2, 0, 0);
szStampWidgetH0->Add(szStampWidgetFlexGrid, 1, wxGROW|wxALL, 5);
lblForegroundColour = new wxStaticText( itemPanel1, ID_STATIC_FOREGROUND_COLOUR, _("Foreground:"), wxDefaultPosition, wxDefaultSize, 0 );
szStampWidgetFlexGrid->Add(lblForegroundColour, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
cpkForegroundStamp = new wxColourPickerCtrl( itemPanel1, ID_COLOURCTRL_FOREGROUND, wxColour(), wxDefaultPosition, wxDefaultSize, wxCLRP_DEFAULT_STYLE );
if (StampWidget::ShowToolTips())
cpkForegroundStamp->SetToolTip(_("Foreground colour of the text."));
szStampWidgetFlexGrid->Add(cpkForegroundStamp, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
lblBackgroundColour = new wxStaticText( itemPanel1, ID_STATIC_BACKGROUND_COLOUR, _("Background:"), wxDefaultPosition, wxDefaultSize, 0 );
szStampWidgetFlexGrid->Add(lblBackgroundColour, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
cpkBackgroundStamp = new wxColourPickerCtrl( itemPanel1, ID_COLOURCTRL_BACKGROUND, wxColour(255, 255, 255), wxDefaultPosition, wxDefaultSize, wxCLRP_DEFAULT_STYLE );
if (StampWidget::ShowToolTips())
cpkBackgroundStamp->SetToolTip(_("Background colour of the text."));
szStampWidgetFlexGrid->Add(cpkBackgroundStamp, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
lblLocation = new wxStaticText( itemPanel1, ID_STATIC_LOCATION, _("Location:"), wxDefaultPosition, wxDefaultSize, 0 );
szStampWidgetFlexGrid->Add(lblLocation, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
wxArrayString cmbStampLocationStrings;
cmbStampLocation = new wxComboBox( itemPanel1, ID_COMBOBOX, wxEmptyString, wxDefaultPosition, wxDefaultSize, cmbStampLocationStrings, wxCB_READONLY );
if (StampWidget::ShowToolTips())
cmbStampLocation->SetToolTip(_("Location of the stamp on the output."));
szStampWidgetFlexGrid->Add(cmbStampLocation, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5);
panBitmapPreview = new wxPanel( itemPanel1, ID_PANEL_BITMAP_PREVIEW, wxDefaultPosition, wxSize(200, 200), wxSUNKEN_BORDER|wxTAB_TRAVERSAL );
panBitmapPreview->SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY);
if (StampWidget::ShowToolTips())
panBitmapPreview->SetToolTip(_("Click to update the preview."));
szStampWidgetH0->Add(panBitmapPreview, 0, wxALIGN_TOP|wxALL, 5);
szBitmapPreviewInPanel = new wxBoxSizer(wxVERTICAL);
panBitmapPreview->SetSizer(szBitmapPreviewInPanel);
szStampWidgetH1 = new wxBoxSizer(wxHORIZONTAL);
szStampWidgetMain->Add(szStampWidgetH1, 0, wxGROW|wxALL, 5);
fpkStamp = new wxFontPickerCtrl( itemPanel1, ID_FONTCTRL, wxFont(), wxDefaultPosition, wxDefaultSize, wxFNTP_FONTDESC_AS_LABEL );
if (StampWidget::ShowToolTips())
fpkStamp->SetToolTip(_("Select the font of the stamp text."));
szStampWidgetH1->Add(fpkStamp, 1, wxGROW|wxALL, 5);
tglTransparent = new wxToggleButton( itemPanel1, ID_TOGGLE_TRANSPARENT, _("Transparent"), wxDefaultPosition, wxDefaultSize, 0 );
tglTransparent->SetValue(true);
if (StampWidget::ShowToolTips())
tglTransparent->SetToolTip(_("Check for a completely transparent background and for a transparent text."));
szStampWidgetH1->Add(tglTransparent, 0, wxGROW|wxALL, 5);
sldTextRotationAngle = new wxSlider( itemPanel1, ID_SLIDER, 45, -180, 180, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL|wxSL_AUTOTICKS|wxSL_LABELS );
if (StampWidget::ShowToolTips())
sldTextRotationAngle->SetToolTip(_("Select the rotation angle of the stamp text."));
szStampWidgetMain->Add(sldTextRotationAngle, 0, wxGROW|wxALL, 5);
////@end StampWidget content construction
}
/*
* Should we show tooltips?
*/
bool StampWidget::ShowToolTips()
{
return true;
}
/*
* Get bitmap resources
*/
wxBitmap StampWidget::GetBitmapResource( const wxString& name )
{
// Bitmap retrieval
////@begin StampWidget bitmap retrieval
wxUnusedVar(name);
return wxNullBitmap;
////@end StampWidget bitmap retrieval
}
/*
* Get icon resources
*/
wxIcon StampWidget::GetIconResource( const wxString& name )
{
// Icon retrieval
////@begin StampWidget icon retrieval
wxUnusedVar(name);
return wxNullIcon;
////@end StampWidget icon retrieval
}

View File

@@ -0,0 +1,129 @@
/////////////////////////////////////////////////////////////////////////////
// Name: StampWidget.h
// Purpose:
// Author: Saleem EDAH-TALLY
// Modified by:
// Created: mar. 01 juil. 2025 19:14:05
// RCS-ID:
// Copyright: Copyright Saleem EDAH-TALLY. All rights reserved.
// Licence:
/////////////////////////////////////////////////////////////////////////////
#ifndef _STAMPWIDGET_H_
#define _STAMPWIDGET_H_
/*!
* Includes
*/
////@begin includes
#include "wx/clrpicker.h"
#include "wx/fontpicker.h"
#include "wx/tglbtn.h"
////@end includes
#include "wx/combobox.h"
#include "wx/stattext.h"
/*!
* Forward declarations
*/
////@begin forward declarations
class wxBoxSizer;
class wxFlexGridSizer;
class wxColourPickerCtrl;
class wxFontPickerCtrl;
class wxToggleButton;
////@end forward declarations
/*!
* Control identifiers
*/
////@begin control identifiers
#define ID_STAMPWIDGET 10000
#define ID_TEXTCTRL 10001
#define ID_STATIC_FOREGROUND_COLOUR 10008
#define ID_COLOURCTRL_FOREGROUND 10003
#define ID_STATIC_BACKGROUND_COLOUR 10009
#define ID_COLOURCTRL_BACKGROUND 10006
#define ID_STATIC_LOCATION 10010
#define ID_COMBOBOX 10005
#define ID_PANEL_BITMAP_PREVIEW 10011
#define ID_FONTCTRL 10002
#define ID_TOGGLE_TRANSPARENT 10007
#define ID_SLIDER 10004
#define SYMBOL_STAMPWIDGET_STYLE wxTAB_TRAVERSAL
#define SYMBOL_STAMPWIDGET_TITLE _("StampWidget")
#define SYMBOL_STAMPWIDGET_IDNAME ID_STAMPWIDGET
#define SYMBOL_STAMPWIDGET_SIZE wxSize(400, 300)
#define SYMBOL_STAMPWIDGET_POSITION wxDefaultPosition
////@end control identifiers
#include <wx/panel.h>
#include <wx/slider.h>
/*!
* StampWidget class declaration
*/
class StampWidget: public wxPanel
{
DECLARE_DYNAMIC_CLASS( StampWidget )
DECLARE_EVENT_TABLE()
public:
/// Constructors
StampWidget();
StampWidget( wxWindow* parent, wxWindowID id = SYMBOL_STAMPWIDGET_IDNAME, const wxPoint& pos = SYMBOL_STAMPWIDGET_POSITION, const wxSize& size = SYMBOL_STAMPWIDGET_SIZE, long style = SYMBOL_STAMPWIDGET_STYLE );
/// Creation
bool Create( wxWindow* parent, wxWindowID id = SYMBOL_STAMPWIDGET_IDNAME, const wxPoint& pos = SYMBOL_STAMPWIDGET_POSITION, const wxSize& size = SYMBOL_STAMPWIDGET_SIZE, long style = SYMBOL_STAMPWIDGET_STYLE );
/// Destructor
~StampWidget();
/// Initialises member variables
void Init();
/// Creates the controls and sizers
void CreateControls();
////@begin StampWidget event handler declarations
////@end StampWidget event handler declarations
////@begin StampWidget member function declarations
/// Retrieves bitmap resources
wxBitmap GetBitmapResource( const wxString& name );
/// Retrieves icon resources
wxIcon GetIconResource( const wxString& name );
////@end StampWidget member function declarations
/// Should we show tooltips?
static bool ShowToolTips();
////@begin StampWidget member variables
wxBoxSizer* szStampWidgetMain;
wxTextCtrl* txtStamp;
wxBoxSizer* szStampWidgetH0;
wxFlexGridSizer* szStampWidgetFlexGrid;
wxStaticText* lblForegroundColour;
wxColourPickerCtrl* cpkForegroundStamp;
wxStaticText* lblBackgroundColour;
wxColourPickerCtrl* cpkBackgroundStamp;
wxStaticText* lblLocation;
wxComboBox* cmbStampLocation;
wxPanel* panBitmapPreview;
wxBoxSizer* szBitmapPreviewInPanel;
wxBoxSizer* szStampWidgetH1;
wxFontPickerCtrl* fpkStamp;
wxToggleButton* tglTransparent;
wxSlider* sldTextRotationAngle;
////@end StampWidget member variables
};
#endif
// _STAMPWIDGET_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
#include "wx/msw/wx.rc"

View File

@@ -0,0 +1,178 @@
/////////////////////////////////////////////////////////////////////////////
// Name: StampWidgets.cpp
// Purpose:
// Author: Saleem EDAH-TALLY
// Modified by:
// Created: dim. 06 juil. 2025 22:33:34
// RCS-ID:
// Copyright: Copyright Saleem EDAH-TALLY. All rights reserved.
// Licence:
/////////////////////////////////////////////////////////////////////////////
// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
////@begin includes
#include "wx/imaglist.h"
////@end includes
#include "StampWidgets.h"
////@begin XPM images
////@end XPM images
/*
* StampWidgets type definition
*/
IMPLEMENT_DYNAMIC_CLASS( StampWidgets, wxPanel )
/*
* StampWidgets event table definition
*/
BEGIN_EVENT_TABLE( StampWidgets, wxPanel )
////@begin StampWidgets event table entries
////@end StampWidgets event table entries
END_EVENT_TABLE()
/*
* StampWidgets constructors
*/
StampWidgets::StampWidgets()
{
Init();
}
StampWidgets::StampWidgets( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style )
{
Init();
Create(parent, id, pos, size, style);
}
/*
* StampWidgets creator
*/
bool StampWidgets::Create( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style )
{
////@begin StampWidgets creation
SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY);
wxPanel::Create( parent, id, pos, size, style );
CreateControls();
Centre();
////@end StampWidgets creation
return true;
}
/*
* StampWidgets destructor
*/
StampWidgets::~StampWidgets()
{
////@begin StampWidgets destruction
////@end StampWidgets destruction
}
/*
* Member initialisation
*/
void StampWidgets::Init()
{
////@begin StampWidgets member initialisation
szNoteBookMain = NULL;
szNoteBookButtons = NULL;
btnAddStampWidget = NULL;
btnDeleteStampWidget = NULL;
nbStampWidgets = NULL;
////@end StampWidgets member initialisation
}
/*
* Control creation for StampWidgets
*/
void StampWidgets::CreateControls()
{
////@begin StampWidgets content construction
StampWidgets* itemPanel1 = this;
szNoteBookMain = new wxBoxSizer(wxHORIZONTAL);
itemPanel1->SetSizer(szNoteBookMain);
szNoteBookButtons = new wxBoxSizer(wxHORIZONTAL);
szNoteBookMain->Add(szNoteBookButtons, 0, wxALIGN_TOP, 5);
btnAddStampWidget = new wxButton( itemPanel1, ID_BUTTON_NB, _("+"), wxDefaultPosition, wxSize(30, -1), 0 );
if (StampWidgets::ShowToolTips())
btnAddStampWidget->SetToolTip(_("Add a stamp widget."));
szNoteBookButtons->Add(btnAddStampWidget, 0, wxALIGN_CENTER_VERTICAL|wxFIXED_MINSIZE, 1);
btnDeleteStampWidget = new wxButton( itemPanel1, ID_BUTTON_NB1, _("-"), wxDefaultPosition, wxSize(30, -1), 0 );
if (StampWidgets::ShowToolTips())
btnDeleteStampWidget->SetToolTip(_("Remove the selected stamp widget."));
szNoteBookButtons->Add(btnDeleteStampWidget, 0, wxALIGN_CENTER_VERTICAL, 1);
nbStampWidgets = new wxNotebook( itemPanel1, ID_NOTEBOOK_STAMPWIDGETS_, wxDefaultPosition, wxDefaultSize, wxBK_DEFAULT );
szNoteBookMain->Add(nbStampWidgets, 1, wxGROW|wxALL, 5);
////@end StampWidgets content construction
}
/*
* Should we show tooltips?
*/
bool StampWidgets::ShowToolTips()
{
return true;
}
/*
* Get bitmap resources
*/
wxBitmap StampWidgets::GetBitmapResource( const wxString& name )
{
// Bitmap retrieval
////@begin StampWidgets bitmap retrieval
wxUnusedVar(name);
return wxNullBitmap;
////@end StampWidgets bitmap retrieval
}
/*
* Get icon resources
*/
wxIcon StampWidgets::GetIconResource( const wxString& name )
{
// Icon retrieval
////@begin StampWidgets icon retrieval
wxUnusedVar(name);
return wxNullIcon;
////@end StampWidgets icon retrieval
}

View File

@@ -0,0 +1,103 @@
/////////////////////////////////////////////////////////////////////////////
// Name: StampWidgets.h
// Purpose:
// Author: Saleem EDAH-TALLY
// Modified by:
// Created: dim. 06 juil. 2025 22:33:34
// RCS-ID:
// Copyright: Copyright Saleem EDAH-TALLY. All rights reserved.
// Licence:
/////////////////////////////////////////////////////////////////////////////
#ifndef _STAMPWIDGETS_H_
#define _STAMPWIDGETS_H_
/*!
* Includes
*/
////@begin includes
#include "wx/notebook.h"
////@end includes
/*!
* Forward declarations
*/
////@begin forward declarations
class wxBoxSizer;
class wxNotebook;
////@end forward declarations
#include <wx/panel.h>
#include <wx/button.h>
/*!
* Control identifiers
*/
////@begin control identifiers
#define ID_STAMPWIDGETS 10000
#define ID_BUTTON_NB 10002
#define ID_BUTTON_NB1 10003
#define ID_NOTEBOOK_STAMPWIDGETS_ 10001
#define SYMBOL_STAMPWIDGETS_STYLE wxTAB_TRAVERSAL
#define SYMBOL_STAMPWIDGETS_TITLE _("StampWidgets")
#define SYMBOL_STAMPWIDGETS_IDNAME ID_STAMPWIDGETS
#define SYMBOL_STAMPWIDGETS_SIZE wxSize(400, 300)
#define SYMBOL_STAMPWIDGETS_POSITION wxDefaultPosition
////@end control identifiers
/*!
* StampWidgets class declaration
*/
class StampWidgets: public wxPanel
{
DECLARE_DYNAMIC_CLASS( StampWidgets )
DECLARE_EVENT_TABLE()
public:
/// Constructors
StampWidgets();
StampWidgets( wxWindow* parent, wxWindowID id = SYMBOL_STAMPWIDGETS_IDNAME, const wxPoint& pos = SYMBOL_STAMPWIDGETS_POSITION, const wxSize& size = SYMBOL_STAMPWIDGETS_SIZE, long style = SYMBOL_STAMPWIDGETS_STYLE );
/// Creation
bool Create( wxWindow* parent, wxWindowID id = SYMBOL_STAMPWIDGETS_IDNAME, const wxPoint& pos = SYMBOL_STAMPWIDGETS_POSITION, const wxSize& size = SYMBOL_STAMPWIDGETS_SIZE, long style = SYMBOL_STAMPWIDGETS_STYLE );
/// Destructor
~StampWidgets();
/// Initialises member variables
void Init();
/// Creates the controls and sizers
void CreateControls();
////@begin StampWidgets event handler declarations
////@end StampWidgets event handler declarations
////@begin StampWidgets member function declarations
/// Retrieves bitmap resources
wxBitmap GetBitmapResource( const wxString& name );
/// Retrieves icon resources
wxIcon GetIconResource( const wxString& name );
////@end StampWidgets member function declarations
/// Should we show tooltips?
static bool ShowToolTips();
////@begin StampWidgets member variables
wxBoxSizer* szNoteBookMain;
wxBoxSizer* szNoteBookButtons;
wxButton* btnAddStampWidget;
wxButton* btnDeleteStampWidget;
wxNotebook* nbStampWidgets;
////@end StampWidgets member variables
};
#endif
// _STAMPWIDGETS_H_

View File

@@ -0,0 +1,230 @@
// /*
// * File: XStampWidget.cpp
// * Author: Saleem Edah-Tally - nmset@yandex.com
// * License : CeCILL-C
// * Copyright Saleem Edah-Tally - © 2025
// *
// * Created on 01 07 2025, 20:35
// */
#include "XStampWidget.h"
#include "DefsStampWidget.h"
#include "StampWorker.h"
#include <wx/bmpbndl.h>
using namespace std;
IMPLEMENT_CLASS( XStampWidget, StampWidget )
XStampWidget::XStampWidget(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: StampWidget(parent, id, pos, size, style)
{}
bool XStampWidget::Setup(wxConfig * config)
{
m_config = config;
if (!m_config)
return false;
const wxString fontDesc = m_config->Read("/Stamp/FontDesc", "");
if (!fontDesc.IsEmpty())
{
wxFont font = wxNullFont;
font.SetNativeFontInfo(fontDesc);
if (font.IsOk())
{
fpkStamp->SetSelectedFont(font);
fpkStamp->Update();
}
}
long rgb = m_config->Read("/Stamp/ForegroundRGB", wxNOT_FOUND);
if (rgb > wxNOT_FOUND)
{
wxColour foregroundColour = wxNullColour;
foregroundColour.SetRGB((wxUint32) rgb);
if (foregroundColour.IsOk())
{
cpkForegroundStamp->SetColour(foregroundColour);
}
}
rgb = m_config->Read("/Stamp/BackgroundRGB", wxNOT_FOUND);
if (rgb > wxNOT_FOUND)
{
wxColour backgroundColour = wxNullColour;
backgroundColour.SetRGB((wxUint32) rgb);
if (backgroundColour.IsOk())
{
cpkBackgroundStamp->SetColour(backgroundColour);
}
}
int textRotationAngle = m_config->Read("/Stamp/RotationAngle", 45);
sldTextRotationAngle->SetValue(textRotationAngle);
bool transparency = m_config->ReadBool("/Stamp/Transparency", true);
tglTransparent->SetValue(transparency);
wxCommandEvent evt;
evt.SetInt(transparency);
OnTransparencyToggled(evt);
fpkStamp->Bind(wxEVT_FONTPICKER_CHANGED, &XStampWidget::OnFontChanged, this);
cpkForegroundStamp->Bind(wxEVT_COLOURPICKER_CHANGED, &XStampWidget::OnForegroundColourChanged, this);
cpkBackgroundStamp->Bind(wxEVT_COLOURPICKER_CHANGED, &XStampWidget::OnBackgroundColourChanged, this);
// *End* of any mouse and any keyboard interaction.
sldTextRotationAngle->Bind(wxEVT_SCROLL_CHANGED, &XStampWidget::OnAngleSliderChanged, this);
txtStamp->Bind(wxEVT_KEY_UP, &XStampWidget::OnTxtKeyPressed, this);
tglTransparent->Bind(wxEVT_TOGGLEBUTTON, &XStampWidget::OnTransparencyToggled, this);
wxArrayString stampLocations ({_("Centre"),
_("North"), _("South"), _("East"), _("West"),
_("North-east"), _("North-west"),
_("South-east"), _("South-west")});
cmbStampLocation->Append(stampLocations);
int location = m_config->Read("/Stamp/Location", wxNOT_FOUND);
cmbStampLocation->Select((location >= cmbStampLocation->GetCount() || location < 0) ? 0 : location);
cmbStampLocation->Bind(wxEVT_COMMAND_COMBOBOX_SELECTED, &XStampWidget::OnLocationChanged, this);
// A panel container is used to have its borders.
m_sbmpPreview = new wxGenericStaticBitmap(panBitmapPreview, wxID_ANY, wxBitmapBundle(wxNullBitmap), wxDefaultPosition, panBitmapPreview->GetSize());
szBitmapPreviewInPanel->Add(m_sbmpPreview, 0, wxGROW | wxALL, 5);
m_sbmpPreview->SetScaleMode(wxStaticBitmapBase::Scale_AspectFit);
m_sbmpPreview->Bind(wxEVT_LEFT_UP, &XStampWidget::OnBitmapPreview, this);
return true;
}
void XStampWidget::OnFontChanged(wxFontPickerEvent& evt)
{
if (!m_config)
{
evt.Skip();
return;
}
const wxString desc = evt.GetFont().GetNativeFontInfoDesc();
m_config->Write("/Stamp/FontDesc", desc);
m_config->Flush();
evt.Skip();
}
void XStampWidget::OnForegroundColourChanged(wxColourPickerEvent& evt)
{
if (!m_config)
{
evt.Skip();
return;
}
const wxUint32 rgb = evt.GetColour().GetRGB();
m_config->Write("/Stamp/ForegroundRGB", (long) rgb);
m_config->Flush();
evt.Skip();
}
void XStampWidget::OnBackgroundColourChanged(wxColourPickerEvent& evt)
{
if (!m_config)
{
evt.Skip();
return;
}
const wxUint32 rgb = evt.GetColour().GetRGB();
m_config->Write("/Stamp/BackgroundRGB", (long) rgb);
m_config->Flush();
evt.Skip();
}
void XStampWidget::OnAngleSliderChanged(wxScrollEvent& evt)
{
if (!m_config)
{
evt.Skip();
return;
}
m_config->Write("/Stamp/RotationAngle", evt.GetInt());
m_config->Flush();
evt.Skip();
}
void XStampWidget::OnTxtKeyPressed(wxKeyEvent& evt)
{
if (!m_config || !evt.ControlDown())
{
evt.Skip();
return;
}
if (evt.GetKeyCode() == 'S')
{
if (txtStamp->IsEmpty())
{
evt.Skip();
return;
}
m_config->Write("/Stamp/Text", txtStamp->GetValue());
m_config->Flush();
}
else if (evt.GetKeyCode() == 'R')
{
wxString last;
if (m_config->Read("/Stamp/Text", &last))
{
txtStamp->SetValue(last);
txtStamp->SetSelection(last.Len(), last.Len());
}
}
evt.Skip();
}
void XStampWidget::OnLocationChanged(wxCommandEvent& evt)
{
if (!m_config)
{
evt.Skip();
return;
}
m_config->Write("/Stamp/Location", evt.GetSelection());
m_config->Flush();
evt.Skip();
}
void XStampWidget::OnTransparencyToggled(wxCommandEvent& evt)
{
lblBackgroundColour->Show(evt.GetInt() == 0);
cpkBackgroundStamp->Show(evt.GetInt() == 0);
if (GetSizer())
GetSizer()->Layout();
if (!m_config)
{
evt.Skip();
return;
}
m_config->Write("/Stamp/Transparency", evt.GetInt());
m_config->Flush();
evt.Skip();
}
StampDescriptor * XStampWidget::GetStampDescriptor()
{
m_descriptor.reset(nullptr);
m_descriptor = std::make_unique<StampDescriptor> ();
m_descriptor->text = txtStamp->GetValue();
m_descriptor->font = fpkStamp->GetSelectedFont();
m_descriptor->foregroundColour = cpkForegroundStamp->GetColour();
m_descriptor->backgroundColour = cpkBackgroundStamp->GetColour();
m_descriptor->rotationAngle = sldTextRotationAngle->GetValue();
m_descriptor->location = cmbStampLocation->GetSelection();
m_descriptor->transparent = tglTransparent->GetValue();
// Not setting image, it is to be created.
return m_descriptor.get();
}
void XStampWidget::OnBitmapPreview(wxMouseEvent& evt)
{
StampDescriptor * descriptor = GetStampDescriptor();
wxImage preview = StampWorker::CreateStamp(descriptor, 72);
m_sbmpPreview->SetBitmap(wxBitmapBundle(preview));
GetSizer()->Layout();
evt.Skip();
}

View File

@@ -0,0 +1,52 @@
// /*
// * File: XStampWidget.h
// * Author: Saleem Edah-Tally - nmset@yandex.com
// * License : CeCILL-C
// * Copyright Saleem Edah-Tally - © 2025
// *
// * Created on 01 07 2025, 20:35
// */
#ifndef XSTAMPWIDGET_H
#define XSTAMPWIDGET_H
#include "StampWidget.h"
#include <wx/wx.h>
#include <wx/config.h>
#include <wx/generic/statbmpg.h>
struct StampDescriptor;
/**
* This widget collects inputs for the creation of a stamp on scan pages.\n
* - text
* - font
* - foreground colour
* - background colour
* - angle of rotation
* - transparency.
*/
class XStampWidget : public StampWidget
{
DECLARE_DYNAMIC_CLASS( XStampWidget )
public:
XStampWidget( wxWindow* parent, wxWindowID id = SYMBOL_STAMPWIDGET_IDNAME, const wxPoint& pos = SYMBOL_STAMPWIDGET_POSITION, const wxSize& size = SYMBOL_STAMPWIDGET_SIZE, long style = SYMBOL_STAMPWIDGET_STYLE );
bool Setup(wxConfig * config);
StampDescriptor * GetStampDescriptor();
private:
wxConfig * m_config;
std::unique_ptr<StampDescriptor> m_descriptor;
wxGenericStaticBitmap * m_sbmpPreview;
void OnFontChanged(wxFontPickerEvent& evt);
void OnForegroundColourChanged(wxColourPickerEvent& evt);
void OnBackgroundColourChanged(wxColourPickerEvent& evt);
void OnAngleSliderChanged(wxScrollEvent& evt);
void OnTxtKeyPressed ( wxKeyEvent& evt );
void OnLocationChanged(wxCommandEvent& evt);
void OnTransparencyToggled(wxCommandEvent& evt);
void OnBitmapPreview(wxMouseEvent& evt);
};
#endif // XSTAMPWIDGET_H

View File

@@ -0,0 +1,88 @@
// /*
// * File: XStampWidgets.cpp
// * Author: Saleem Edah-Tally - nmset@yandex.com
// * License : CeCILL-C
// * Copyright Saleem Edah-Tally - © 2025
// *
// * Created on 06 07 2025, 22:41
// */
#include "XStampWidgets.h"
#include <XStampWidget.h>
#include <DefsStampWidget.h>
IMPLEMENT_CLASS( XStampWidgets, StampWidgets )
XStampWidgets::XStampWidgets(wxWindow* parent, wxWindowID id, const wxPoint& pos,
const wxSize& size, long style)
: StampWidgets(parent, id, pos, size, style)
{}
bool XStampWidgets::Setup(wxFileConfig* config)
{
m_config = config;
if (!m_config)
return false;
wxMouseEvent evt;
AddStampWidget(evt);
btnAddStampWidget->Bind(wxEVT_LEFT_UP, &XStampWidgets::AddStampWidget, this);
btnDeleteStampWidget->Bind(wxEVT_LEFT_UP, &XStampWidgets::DeleteStampWidget, this);
return true;
}
void XStampWidgets::AddStampWidget(wxMouseEvent& evt)
{
XStampWidget * stampWidget = new XStampWidget(nbStampWidgets);
stampWidget->Setup(m_config);
stampWidget->Bind(wxEVT_COMMAND_COMBOBOX_SELECTED, &XStampWidgets::OnStampLocationChanged, this);
nbStampWidgets->AddPage(stampWidget, stampWidget->cmbStampLocation->GetStringSelection(), true);
if (GetParent()->GetSizer())
GetParent()->GetSizer()->Layout();
evt.Skip();
}
void XStampWidgets::DeleteStampWidget(wxMouseEvent& evt)
{
int selectedPage = nbStampWidgets->GetSelection();
if (selectedPage == wxNOT_FOUND)
{
evt.Skip();
return;
}
nbStampWidgets->DeletePage(selectedPage);
evt.Skip();
}
std::vector<StampDescriptor*> * XStampWidgets::GetStampDescriptors()
{
m_stampDescriptors.clear(); // Stored as unique_ptr in XStampWidget.
for (uint i = 0; i < nbStampWidgets->GetPageCount(); i++)
{
XStampWidget * stampWidget = static_cast<XStampWidget*> (nbStampWidgets->GetPage(i));
if (stampWidget)
{
m_stampDescriptors.push_back(stampWidget->GetStampDescriptor());
}
}
return &m_stampDescriptors;
}
void XStampWidgets::OnStampLocationChanged(wxCommandEvent& evt)
{
int selectedPage = nbStampWidgets->GetSelection();
if (selectedPage == wxNOT_FOUND)
{
evt.Skip();
return;
}
XStampWidget * stampWidget = static_cast<XStampWidget*> (nbStampWidgets->GetPage(selectedPage));
if (!stampWidget)
{
evt.Skip();
return;
}
nbStampWidgets->SetPageText(selectedPage, stampWidget->cmbStampLocation->GetStringSelection());
evt.Skip();
}

View File

@@ -0,0 +1,41 @@
// /*
// * File: XStampWidgets.h
// * Author: Saleem Edah-Tally - nmset@yandex.com
// * License : CeCILL-C
// * Copyright Saleem Edah-Tally - © 2025
// *
// * Created on 06 07 2025, 22:41
// */
#ifndef XSTAMPWIDGETS_H
#define XSTAMPWIDGETS_H
#include "StampWidgets.h"
#include <wx/wx.h>
#include <wx/config.h>
#include <vector>
#include <memory>
struct StampDescriptor;
class XStampWidgets : public StampWidgets
{
DECLARE_DYNAMIC_CLASS( XStampWidgets )
public:
XStampWidgets(wxWindow* parent, wxWindowID id = SYMBOL_STAMPWIDGETS_IDNAME,
const wxPoint& pos = SYMBOL_STAMPWIDGETS_POSITION,
const wxSize& size = SYMBOL_STAMPWIDGETS_SIZE,
long style = SYMBOL_STAMPWIDGETS_STYLE);
bool Setup(wxConfig * config);
std::vector<StampDescriptor*> * GetStampDescriptors();
private:
wxConfig * m_config;
std::vector<StampDescriptor*> m_stampDescriptors;
void AddStampWidget(wxMouseEvent& evt);
void DeleteStampWidget(wxMouseEvent& evt);
void OnStampLocationChanged(wxCommandEvent& evt);
};
#endif // XSTAMPWIDGETS_H

View File

@@ -419,7 +419,7 @@
<bool name="proxy-wxRIGHT">1</bool>
<bool name="proxy-wxSHAPED">0</bool>
<bool name="proxy-wxTOP">1</bool>
<string name="title">"wxBoxSizer V"</string>
<string name="title">"wxBoxSizer V: szMain"</string>
<long name="title-mode">0</long>
<string name="type">"dialog-control-document"</string>
<document>
@@ -526,7 +526,7 @@ Double-click to go to the selected directory."</string>
<long name="proxy-Height">-1</long>
<string name="proxy-Help text">"Basename"</string>
<bool name="proxy-Hidden">0</bool>
<string name="proxy-Id name">"ID_TEXTCTRL"</string>
<string name="proxy-Id name">"ID_TEXTCTRL_S7"</string>
<long name="proxy-Id value">10003</long>
<string name="proxy-Implementation filename">""</string>
<string name="proxy-Initial value">""</string>
@@ -576,7 +576,7 @@ Double-click to go to the selected directory."</string>
<bool name="proxy-wxWANTS_CHARS">0</bool>
<long name="proxy-X">-1</long>
<long name="proxy-Y">-1</long>
<string name="title">"wxTextCtrl: ID_TEXTCTRL"</string>
<string name="title">"wxTextCtrl: ID_TEXTCTRL_S7"</string>
<long name="title-mode">0</long>
<string name="type">"dialog-control-document"</string>
</document>

View File

@@ -126,7 +126,7 @@ void S7::CreateControls()
dpkDestination->SetToolTip(_("Select a destination directory.\nDouble-click to go to the selected directory."));
szMain->Add(dpkDestination, 0, wxGROW|wxALL, 5);
txtBasename = new wxTextCtrl( panMain, ID_TEXTCTRL, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
txtBasename = new wxTextCtrl( panMain, ID_TEXTCTRL_S7, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
txtBasename->SetHelpText(_("Basename"));
if (S7::ShowToolTips())
txtBasename->SetToolTip(_("Specify a destination file basename (without extension).\n\n'CTRL + click' for about information."));

View File

@@ -39,7 +39,7 @@ class wxDirPickerCtrl;
#define ID_S7 10000
#define ID_PANEL 10001
#define ID_DIRPICKERCTRL 10002
#define ID_TEXTCTRL 10003
#define ID_TEXTCTRL_S7 10003
#define SYMBOL_S7_STYLE wxCAPTION|wxRESIZE_BORDER|wxSYSTEM_MENU|wxCLOSE_BOX
#define SYMBOL_S7_TITLE _("S7")
#define SYMBOL_S7_IDNAME ID_S7

View File

@@ -26,6 +26,7 @@
#include <globals.h>
#include "s7app.h"
#include <XS7.h>
#include <wx/cmdline.h>
////@begin XPM images
@@ -40,6 +41,7 @@
IMPLEMENT_APP( S7App )
////@end implement app
using namespace std;
/*
* S7App type definition
@@ -122,15 +124,16 @@ bool S7App::OnInit()
translations->AddCatalog(_APPNAME_);
}
bool res = ParseCmdLine();
if ( !res )
return res;
SetConfig();
XS7 * appWindow = new XS7(nullptr);
SetTopWindow(appWindow);
appWindow->Show ( false );
bool res = appWindow->ParseCmdLine();
if ( res )
{
appWindow->Setup();
appWindow->Show();
}
appWindow->Setup(m_config.get());
appWindow->Show();
return res;
}
@@ -147,3 +150,42 @@ int S7App::OnExit()
////@end S7App cleanup
}
bool S7App::ParseCmdLine()
{
wxCmdLineParser p;
p.SetCmdLine ( wxApp::GetInstance()->argc, wxApp::GetInstance()->argv );
p.SetSwitchChars ( _T ( "-" ) );
p.AddOption ( _T ( "c" ), wxEmptyString, _ ( "Config file tag." ) );
p.AddSwitch ( _T ( "v" ), wxEmptyString, _ ( "Show version and quit." ) );
p.AddSwitch ( _T ( "h" ), wxEmptyString, _ ( "Show help and quit." ) );
p.Parse ( false );
if ( p.Found ( _T ( "c" ) ) )
{
p.Found ( _T ( "c" ), &m_configTag );
return true;
}
if ( p.Found ( _T ( "h" ) ) )
{
p.Usage();
return false; //Exit code is 255, not clean.
}
if ( p.Found ( _T ( "v" ) ) )
{
cout << ( _APPNAME_ + _ ( " - version " ) + to_string(_APPVERSION_) ) << endl;
return false;
}
return true;
}
void S7App::SetConfig()
{
const wxString configDir = wxFileConfig::GetLocalFile ( _APPNAME_, wxCONFIG_USE_SUBDIR ).GetPath();
if ( !wxFileName::Exists ( configDir ) )
wxFileName::Mkdir ( configDir );
const wxString configBaseName = m_configTag.IsEmpty()
? _APPNAME_
: _APPNAME_ + wxString("-") + m_configTag;
m_config = std::make_unique<wxFileConfig>(_APPNAME_, _T("SET"), configBaseName,
wxEmptyString, wxCONFIG_USE_SUBDIR);
}

View File

@@ -21,6 +21,8 @@
#include "wx/image.h"
////@end includes
#include "wx/config.h"
/*!
* Forward declarations
*/
@@ -66,6 +68,15 @@ public:
////@end S7App member variables
private:
wxLocale m_locale;
std::unique_ptr<wxConfig> m_config = nullptr;
/*
* An optional tag for wxConfig files. This allows using different profiles
* by specifying the -c command line option.
*/
wxString m_configTag;
bool ParseCmdLine();
void SetConfig();
};
/*!

View File

@@ -30,7 +30,8 @@ PopupTransientWindow* ConfigEditorPopup::CreatePopup()
return m_popup;
}
wxCheckBox* ConfigEditorPopup::AddCheckBox ( const wxString& label, const wxString& configPath )
wxCheckBox* ConfigEditorPopup::AddCheckBox ( const wxString& label, const wxString& configPath,
bool * clientVar)
{
wxASSERT_MSG ( ( m_config != nullptr ),_T("CONFIG IS nullptr") );
wxString * cPath = new wxString ( configPath );
@@ -38,13 +39,22 @@ wxCheckBox* ConfigEditorPopup::AddCheckBox ( const wxString& label, const wxStri
m_flxsz->Add ( lbl, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 0 );
wxCheckBox * cb = new wxCheckBox ( m_pan, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxCHK_2STATE );
m_flxsz->Add ( cb, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 0 );
cb->SetValue ( m_config->ReadBool ( configPath, false ) );
cb->SetValue ( (!clientVar) ? m_config->ReadBool ( configPath, false ) : *clientVar);
cb->SetClientData ( cPath );
cb->Bind ( wxEVT_DESTROY, &ConfigEditorPopup::OnControlDestroy, this );
if (clientVar)
{
cb->Bind(wxEVT_COMMAND_CHECKBOX_CLICKED, [cb, clientVar] (wxCommandEvent& evt)
{
*clientVar = cb->GetValue();
}
);
}
return cb;
}
wxSpinCtrl* ConfigEditorPopup::AddSpinCtrl ( const wxString& label, const wxString& configPath )
wxSpinCtrl* ConfigEditorPopup::AddSpinCtrl ( const wxString& label, const wxString& configPath,
int * clientVar)
{
wxASSERT_MSG ( ( m_config != nullptr ),_T("CONFIG IS nullptr") );
wxString * cPath = new wxString ( configPath );
@@ -52,13 +62,22 @@ wxSpinCtrl* ConfigEditorPopup::AddSpinCtrl ( const wxString& label, const wxStri
m_flxsz->Add ( lbl, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxGROW | wxALL, 0 );
wxSpinCtrl * spn = new wxSpinCtrl ( m_pan, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS | wxALIGN_RIGHT, -100, 100 );
m_flxsz->Add ( spn, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 0 );
spn->SetValue ( ( int ) m_config->ReadLong ( configPath, 0 ) );
spn->SetValue ((!clientVar) ? ( int ) m_config->ReadLong ( configPath, 0 ) : *clientVar);
spn->SetClientData ( cPath );
spn->Bind ( wxEVT_DESTROY, &ConfigEditorPopup::OnControlDestroy, this );
if (clientVar)
{
spn->Bind(wxEVT_COMMAND_SPINCTRL_UPDATED, [spn, clientVar] (wxSpinEvent& evt)
{
*clientVar = spn->GetValue();
}
);
}
return spn;
}
wxTextCtrl* ConfigEditorPopup::AddTextCtrl ( const wxString& label, const wxString& configPath )
wxTextCtrl* ConfigEditorPopup::AddTextCtrl ( const wxString& label, const wxString& configPath,
wxString * clientVar)
{
wxASSERT_MSG ( ( m_config != nullptr ),_T("CONFIG IS nullptr") );
wxString * cPath = new wxString ( configPath );
@@ -66,9 +85,17 @@ wxTextCtrl* ConfigEditorPopup::AddTextCtrl ( const wxString& label, const wxStri
m_flxsz->Add ( lbl, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxGROW | wxALL, 0 );
wxTextCtrl * txt = new wxTextCtrl ( m_pan, wxID_ANY );
m_flxsz->Add ( txt, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 0 );
txt->SetValue ( m_config->Read ( configPath, wxEmptyString ) );
txt->SetValue ( (!clientVar) ? m_config->Read ( configPath, wxEmptyString ) : *clientVar );
txt->SetClientData ( cPath );
txt->Bind ( wxEVT_DESTROY, &ConfigEditorPopup::OnControlDestroy, this );
if (clientVar)
{
txt->Bind(wxEVT_COMMAND_TEXT_UPDATED, [txt, clientVar] (wxCommandEvent& evt)
{
*clientVar = txt->GetValue();
}
);
}
return txt;
}

View File

@@ -27,9 +27,9 @@ public:
virtual ~ConfigEditorPopup();
PopupTransientWindow* CreatePopup();
void ShowPopup();
wxCheckBox* AddCheckBox ( const wxString& label, const wxString& configPath );
wxSpinCtrl* AddSpinCtrl ( const wxString& label, const wxString& configPath );
wxTextCtrl * AddTextCtrl ( const wxString& label, const wxString& configPath );
wxCheckBox* AddCheckBox(const wxString& label, const wxString& configPath, bool * clientVar = nullptr);
wxSpinCtrl* AddSpinCtrl ( const wxString& label, const wxString& configPath, int * clientVar = nullptr );
wxTextCtrl * AddTextCtrl ( const wxString& label, const wxString& configPath, wxString * clientVar = nullptr );
private:
wxConfig * m_config = nullptr;

View File

@@ -116,6 +116,15 @@ void MiscTools::MessageBox ( const wxString& msg, const bool notify )
}
}
void MiscTools::AsyncMessageBox(const wxString& msg, const bool notify)
{
wxTheApp->CallAfter([msg, notify] ()
{
MiscTools::MessageBox(msg, notify);
}
);
}
wxTextValidator* MiscTools::MakeFileNameValidator ( bool excludeSpace )
{
wxTextValidator * tval = new wxTextValidator ( wxFILTER_EXCLUDE_CHAR_LIST );

View File

@@ -45,6 +45,7 @@ public:
* Shows a message as a modal dialog or as a notification.
*/
static void MessageBox ( const wxString& msg, const bool notify = false );
static void AsyncMessageBox ( const wxString& msg, const bool notify = false );
/**
* Creates a validator excluding file name forbidden characters, path

View File

@@ -14,7 +14,7 @@ TimeredStatusBar::TimeredStatusBar ( wxWindow * parent)
{
m_numFields = 3;
/* We use default reasonable widths for CAPS and NUM. */
const int widths[3] = {-1, 70, 70};
const int widths[3] = {-1, 70, 90};
SetFieldsCount ( m_numFields, widths );
m_timer.Stop();
m_timer.SetOwner ( this );

107
XS7.cpp
View File

@@ -14,56 +14,25 @@
#include "page.xpm"
#include <wx/stdpaths.h>
#include <wx/cmdline.h>
#include <wx/aboutdlg.h>
using namespace std;
IMPLEMENT_CLASS( XS7, S7 )
XS7::XS7(wxWindow* parent, wxWindowID id, const wxString& caption, const wxPoint& pos, const wxSize& size, long style)
: S7(parent, id, caption, pos, size, style)
{}
bool XS7::ParseCmdLine()
void XS7::Setup(wxConfig * config)
{
wxCmdLineParser p;
p.SetCmdLine ( wxApp::GetInstance()->argc, wxApp::GetInstance()->argv );
p.SetSwitchChars ( _T ( "-" ) );
p.AddOption ( _T ( "c" ), wxEmptyString, _ ( "Config file tag." ) );
p.AddSwitch ( _T ( "v" ), wxEmptyString, _ ( "Show version and quit." ) );
p.AddSwitch ( _T ( "h" ), wxEmptyString, _ ( "Show help and quit." ) );
p.Parse ( false );
if ( p.Found ( _T ( "c" ) ) )
{
p.Found ( _T ( "c" ), &m_configTag );
return true;
}
if ( p.Found ( _T ( "h" ) ) )
{
p.Usage();
return false; //Exit code is 255, not clean.
}
if ( p.Found ( _T ( "v" ) ) )
{
cout << ( _APPNAME_ + _ ( " - version " ) + to_string(_APPVERSION_) ) << endl;
return false;
}
return true;
}
void XS7::Setup()
{
const wxString configDir = wxFileConfig::GetLocalFile ( _APPNAME_, wxCONFIG_USE_SUBDIR ).GetPath();
if ( !wxFileName::Exists ( configDir ) )
wxFileName::Mkdir ( configDir );
const wxString configBaseName = m_configTag.IsEmpty()
? _APPNAME_
: _APPNAME_ + wxString("-") + m_configTag;
m_config = std::make_unique<wxFileConfig>(_APPNAME_, _T("SET"), configBaseName,
wxEmptyString, wxCONFIG_USE_SUBDIR);
m_config = config;
TimeredStatusBar * sb = new TimeredStatusBar(this);
SetStatusBar(sb);
m_insaneWidget = new XInsaneWidget(panMain, sb, m_config.get());
szMain->Add(m_insaneWidget, 0, wxGROW | wxALL);
m_insaneWidget = new XInsaneWidget(panMain);
m_insaneWidget->Setup(m_config, sb);
szMain->Insert(2, m_insaneWidget, 1, wxGROW | wxALL);
dpkDestination->Bind ( wxEVT_DIRPICKER_CHANGED, &XS7::OnDpkRepositoryChange, this );
dpkDestination->GetTextCtrl()->Bind ( wxEVT_LEFT_DCLICK, &XS7::OnDpkDoubleClick, this );
@@ -73,7 +42,7 @@ void XS7::Setup()
txtBasename->SetValidator(*MiscTools::MakeFileNameValidator(false));
txtBasename->Bind(wxEVT_LEFT_UP, &XS7::OnAbout, this);
MiscTools::RestoreSizePos(m_config.get(), this, wxString("/" + wxString(_APPNAME_)));
MiscTools::RestoreSizePos(m_config, this, wxString("/" + wxString(_APPNAME_)));
S7::SetTitle(wxString(_APPNAME_) + " - version " + to_string(_APPVERSION_));
@@ -81,14 +50,43 @@ void XS7::Setup()
wxString fixedTip = _("'Shift + left' click to generate a new destination file name.");
fixedTip += _T("\n") + m_insaneWidget->lblNewDoc->GetToolTipText();
m_insaneWidget->lblNewDoc->SetToolTip(fixedTip);
Bind(wxEVT_CLOSE_WINDOW, &XS7::OnClose, this);
}
XS7::~XS7()
{
if (m_config)
MiscTools::SaveSizePos(m_config.get(), this, wxString("/") + _APPNAME_);
MiscTools::SaveSizePos(m_config, this, wxString("/") + _APPNAME_);
}
void XS7::OnClose(wxCloseEvent& evt)
{
if (!m_insaneWidget)
{
evt.Skip();
return;
}
if (m_insaneWidget->IsScannerDiscoveryRunning())
{
evt.Veto();
TimeredStatusBar * sb = static_cast<TimeredStatusBar*> (GetStatusBar());
if (sb)
sb->SetTransientText(_("Veto: scanner discovery is running."));
evt.Skip(false);
return;
}
if(m_insaneWidget->IsScanning())
{
m_insaneWidget->CancelScanning();
evt.Skip(false); // The window remains open.
return;
}
evt.Skip();
}
void XS7::OnDpkRepositoryChange ( wxFileDirPickerEvent& evt )
{
m_config->Write ( _T ( "/DocRoot" ), dpkDestination->GetPath() );
@@ -114,6 +112,18 @@ void XS7::OnAppKeyPressed(wxKeyEvent& evt)
{
if (evt.GetKeyCode() == 'Q')
Close();
// Create a new instance of the application window.
if (evt.GetKeyCode() == 'N')
{
static int instanceId = 0;
instanceId++;
XS7 * instance = new XS7(nullptr, wxID_ANY);
instance->Show(false);
instance->Setup(m_config);
const wxString title = instance->GetTitle() + _T(" (#") + to_string(instanceId) + _T(")");
instance->SetTitle(title);
instance->Show(true);
}
}
if (evt.GetKeyCode() == WXK_ESCAPE)
if (m_insaneWidget)
@@ -123,7 +133,7 @@ void XS7::OnAppKeyPressed(wxKeyEvent& evt)
void XS7::OnNewDocLeftClick ( wxMouseEvent& evt )
{
if ( !evt.ShiftDown() )
if ( !evt.ShiftDown() || !m_insaneWidget->lblNewDoc->IsEnabled() )
{
evt.Skip();
return;
@@ -158,11 +168,14 @@ void XS7::OnAbout(wxMouseEvent& evt)
evt.Skip();
return;
}
const wxString msg = wxString(_APPNAME_) + _(" - version ") + to_string((_APPVERSION_))
+ _ (", using InsaneWidget.\n\n")
+ _ ( "Copyright: Saleem Edah-Tally [Surgeon] [Hobbyist developer]\n" )
+ _ ( "License: CeCILL/CeCILL-C per file header." );
wxAboutDialogInfo info;
info.AddDeveloper("Saleem Edah-Tally");
info.SetCopyright(_("Copyright: Saleem Edah-Tally [Surgeon] [Hobbyist developer]"));
info.SetLicence(_ ( "License: CeCILL/CeCILL-C per file header." ));
info.SetVersion(_("version ") + to_string(_APPVERSION_) + _ (", using InsaneWidget."));
info.AddTranslator("Saleem Edah-Tally (FR)");
info.SetIcon(page_xpm);
wxAboutBox(info, this);
MiscTools::MessageBox(msg);
evt.Skip();
}

14
XS7.h
View File

@@ -17,27 +17,23 @@
class XS7 : public S7
{
DECLARE_DYNAMIC_CLASS( XS7 )
public:
XS7(wxWindow* parent, wxWindowID id = SYMBOL_S7_IDNAME, const wxString& caption = SYMBOL_S7_TITLE,
const wxPoint& pos = SYMBOL_S7_POSITION, const wxSize& size = SYMBOL_S7_SIZE, long style = SYMBOL_S7_STYLE );
virtual ~XS7();
bool ParseCmdLine();
void Setup();
void Setup(wxConfig * config);
private:
std::unique_ptr<wxConfig> m_config = nullptr;
wxConfig * m_config = nullptr;
XInsaneWidget * m_insaneWidget = nullptr;
/*
* An optional tag for wxConfig files. This allows using different profiles
* by specifying the -c command line option.
*/
wxString m_configTag;
void OnDpkRepositoryChange ( wxFileDirPickerEvent& evt );
void OnDpkDoubleClick ( wxMouseEvent& evt );
void OnAppKeyPressed(wxKeyEvent& evt);
void OnNewDocLeftClick ( wxMouseEvent& evt ); // wxStaticText in m_insaneWidget.
void OnClose(wxCloseEvent& evt);
void OnAbout(wxMouseEvent& evt);
};

View File

@@ -11,7 +11,7 @@
#define GLOBALS_H
#define _APPNAME_ "S7"
#define _APPVERSION_ 1
#define _APPVERSION_ 2
#define _INSANEWIDGET_NAME "InsaneWidget"
#endif /* GLOBALS_H */