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.
This commit is contained in:
Saleem Edah-Tally
2025-07-11 21:10:59 +02:00
parent 3350f86ddf
commit 4b23b1f3de
12 changed files with 206 additions and 215 deletions

View File

@@ -31,6 +31,10 @@ 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.
Outputs:
- a single PDF file with the number of requested pages

View File

@@ -30,5 +30,4 @@ target_link_libraries(insanewidget minutils stampwidget
${wxWidgets_LIBRARIES}
${LIBINSANE_LIBRARIES}
${PODOFO_LIBRARIES}
${NETPBM_LIBRARIES}
${PAPER_LIBRARIES})

View File

@@ -22,7 +22,6 @@
using namespace std;
#define IERR(e) ("[" + to_string(e) + "] " + lis_strerror(e))
static bool gs_cancelRequested = false;
InsaneWorker::InsaneWorker ( InsaneWorkerEvent * evh )
{
@@ -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 << "ABORT: " << e.what() << " - could not allocate " << bytesPerRow << " bytes." << endl;
if (m_evh)
m_evh->OnError("Insufficient system RAM.");
return false;
@@ -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

@@ -40,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
{
@@ -117,6 +156,41 @@ public:
{
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.");
@@ -130,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,
@@ -158,7 +241,7 @@ public:
m_startPageIndex = pageIndex;
m_pixelFiles[pageIndex] = {filePath, imageAttributes};
auto informProgress = [&] ()
auto informProgress = [this] ()
{
if (!m_sb || (m_total == 1))
return;
@@ -175,20 +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.
// 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();
@@ -214,7 +296,7 @@ public:
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();
@@ -251,16 +333,19 @@ 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)
@@ -277,6 +362,7 @@ public:
m_increment = 1;
m_totalEven = 0;
m_pixelFiles.clear();
m_wip = false;
}
private:
@@ -295,6 +381,7 @@ private:
int m_totalEven = 0;
int m_startPageIndex = 0;
int m_increment = 1;
bool m_wip = false; // Is InsaneWorker busy?
};
@@ -316,7 +403,12 @@ void XInsaneWidget::Setup()
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());
@@ -336,12 +428,17 @@ void XInsaneWidget::Setup()
// 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 )
{
@@ -403,6 +500,12 @@ void XInsaneWidget::OnBtnScanRightClick ( wxMouseEvent& evt )
// Start scanning.
void XInsaneWidget::OnBtnScanClick ( wxMouseEvent& evt )
{
if (m_scanProject->GetWip())
{
CancelScanning();
evt.Skip();
return;
}
const wxString dest = txtNewDoc->GetValue();
if (dest.IsEmpty())
{
@@ -453,9 +556,14 @@ void XInsaneWidget::OnBtnScanClick ( wxMouseEvent& evt )
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(),
m_scanProject->UpdateControlsStatus(false);
BackgroundScan * bgScan = new BackgroundScan(this, m_insaneWorker.get(),
destFile.GetPath().ToStdString(),
destFile.GetName().ToStdString(),
startAndIncrement.first, padWidth, startAndIncrement.second);
startAndIncrement.first, padWidth,
startAndIncrement.second);
m_scanProject->SetWip(true);
bgScan->Run();
}
evt.Skip();
}
@@ -468,8 +576,11 @@ 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)

View File

@@ -63,7 +63,7 @@ public:
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 );
void ResetScanProject();
void CancelScanning(); // Not tested, probably doesn't work as intended.
void CancelScanning();
void EnableScanButton(bool enable); // For CallAfter.
void Setup();
private:

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

@@ -1,23 +1,23 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
# SPDX-FileCopyrightText: 2025 Saleem EDAH-TALLY <set@nmset.info>
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-07-09 16:00+0200\n"
"PO-Revision-Date: 2025-07-09 16:03+0200\n"
"Last-Translator: Saleem EDAH-TALLY <set@nmset.info>\n"
"Language-Team: French <kde-francophone@kde.org>\n"
"Language: fr_FR\n"
"POT-Creation-Date: 2025-07-11 21:35+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"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.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,24 +39,15 @@ msgstr ""
"\n"
"'CTRL + clic' : à propos"
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:128
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:129
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:132
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:125
msgid "New"
msgstr "Nouveau"
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:130
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:131
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:134
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:127
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
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:139
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:132
msgid ""
"Full path to destination file without the extension; it is determined by the "
"output type."
@@ -64,16 +55,12 @@ 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/UI/InsaneWidget.cpp:142
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:135
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:188
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:302
msgid "Scan"
msgstr "Numériser"
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:140
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:141
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:144
msgid ""
"'Left click' to start the scan project.\n"
"'Right click' to show the scanner widget."
@@ -105,179 +92,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:132
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:133
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:171
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:138
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:139
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:177
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:142
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:143
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:181
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:149
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:150
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:190
msgid "Session cancelled."
msgstr "Session annulée."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:162
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:141
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:166
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:167
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:208
msgid "Scanning: "
msgstr "Numérisation :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:164
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:143
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:168
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:169
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:210
msgid "Front face: "
msgstr "Recto :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:166
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:145
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:170
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:171
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:212
msgid "Back face: "
msgstr "Verso :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:170
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:149
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:174
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:175
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:216
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:206
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:207
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:248
msgid "Wrong paper size: "
msgstr "Mauvaise taille de papier :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:201
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:180
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:206
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:207
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:248
msgid "; using A4."
msgstr "; utilisation du format A4."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:207
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:186
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:212
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:213
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:214
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:255
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:255
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:256
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:257
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:298
msgid "Finished."
msgstr "Terminé."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:333
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:317
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:356
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:355
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:351
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:352
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:398
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:359
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:358
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:354
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:355
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:401
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:62
#: ../../Resources/InsaneWidget/XScannerWidget.cpp:64
msgid "Searching for devices..."
msgstr "Recherche de périphériques..."
#: ../../Resources/InsaneWidget/XScannerWidget.cpp:53
#: ../../Resources/InsaneWidget/XScannerWidget.cpp:65
#: ../../Resources/InsaneWidget/XScannerWidget.cpp:67
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:105 ../../XS7.cpp:107
#: ../../XS7.cpp:107
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:129 ../../XS7.cpp:131
#: ../../XS7.cpp:136
msgid "Invalid folder name."
msgstr "Nom de dossier invalide."
#: ../../XS7.cpp:101 ../../XS7.cpp:136 ../../XS7.cpp:137 ../../XS7.cpp:139
#: ../../XS7.cpp:144
msgid "Invalid file basename."
msgstr "Nom de base de fichier invalide."
#: ../../XS7.cpp:123 ../../XS7.cpp:158 ../../XS7.cpp:159 ../../XS7.cpp:161
#: ../../XS7.cpp:166
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:160 ../../XS7.cpp:162
#: ../../XS7.cpp:167
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:45
#: ../../Resources/InsaneWidget/UI/InsaneWidget.h:49
#: ../../Resources/InsaneWidget/UI/InsaneWidget.h:41
msgid "InsaneWidget"
msgstr "InsaneWidget"
@@ -310,34 +231,23 @@ msgstr "Résolution :"
msgid "Paper size:"
msgstr "Format de page :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:332
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:316
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:355
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:354
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:350
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:351
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:397
msgid "Double sided:"
msgstr "Recto-verso :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:334
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:318
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:357
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:356
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:352
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:353
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:399
msgid "Total:"
msgstr "Total :"
#: ../../XS7.cpp:46 ../../XS7.cpp:81 ../../XS7.cpp:82 ../../XS7.cpp:84
#: ../../XS7.cpp:84
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 ../../XS7.cpp:157
#: ../../XS7.cpp:47 ../../XS7.cpp:159
#: ../../XS7.cpp:47 ../../XS7.cpp:164
msgid " - version "
msgstr " - version "
#: ../../XS7.cpp:122 ../../XS7.cpp:157 ../../XS7.cpp:158 ../../XS7.cpp:160
#: ../../XS7.cpp:165
msgid ""
", using InsaneWidget.\n"
"\n"
@@ -345,34 +255,26 @@ msgstr ""
", utilisant InsaneWidget.\n"
"\n"
#: ../../Resources/InsaneWidget/XScannerWidget.cpp:73
#: ../../Resources/InsaneWidget/XScannerWidget.cpp:85
#: ../../Resources/InsaneWidget/XScannerWidget.cpp:87
msgid " device(s) found."
msgstr " périphériques trouvé(s)."
#: ../../XS7.cpp:29 ../../XS7.cpp:31
#: ../../XS7.cpp:31
msgid "Config file tag."
msgstr "Suffixe du fichier de configuration."
#: ../../XS7.cpp:30 ../../XS7.cpp:32
#: ../../XS7.cpp:32
msgid "Show version and quit."
msgstr "Afficher la version et quitter."
#: ../../XS7.cpp:31 ../../XS7.cpp:33
#: ../../XS7.cpp:33
msgid "Show help and quit."
msgstr "Afficher l'aide et quitter."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:352
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:391
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:390
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:408
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:409
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:461
msgid "Destination file missing."
msgstr "Fichier de destination manquant."
#: ../../Resources/InsaneWidget/UI/StampWidget.cpp:130
#: ../../Resources/InsaneWidget/UI/StampWidget.cpp:131
#: ../../Resources/StampWidget/UI/StampWidget.cpp:140
msgid ""
"Create a stamp with this text, which can be multiline.\n"
@@ -385,134 +287,92 @@ msgstr ""
"CTRL + S : sauvegarder le texte actuel.\n"
"CTRL + R : restaurer le texte sauvegardé."
#: ../../Resources/InsaneWidget/UI/StampWidget.cpp:138
#: ../../Resources/InsaneWidget/UI/StampWidget.cpp:139
#: ../../Resources/StampWidget/UI/StampWidget.cpp:188
msgid "Select the font of the stamp text."
msgstr "Sélectionnez la police de caractère du texte."
#: ../../Resources/InsaneWidget/UI/StampWidget.cpp:143
#: ../../Resources/InsaneWidget/UI/StampWidget.cpp:144
msgid "Select the colour of the stamp text."
msgstr "Sélectionnez la couleur du texte."
#: ../../Resources/InsaneWidget/UI/StampWidget.cpp:148
#: ../../Resources/InsaneWidget/UI/StampWidget.cpp:155
#: ../../Resources/StampWidget/UI/StampWidget.cpp:199
msgid "Select the rotation angle of the stamp text."
msgstr "Sélectionnez l'angle de rotation du texte."
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:144
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:147
msgid "Stamp"
msgstr "Tampon"
#: ../../Resources/InsaneWidget/PixelToImageWriter.cpp:34
#: ../../Resources/InsaneWidget/PixelToImageWriter.cpp:35
msgid "Failed to read raw file."
msgstr "Échec de lecture du fichier brut."
#: ../../Resources/InsaneWidget/PixelToImageWriter.cpp:60
#: ../../Resources/InsaneWidget/PixelToImageWriter.cpp:68
#: ../../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:187
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:188
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:229
msgid "Failed to create output image."
msgstr "Échec de création de l'image de sortie."
#: ../../Resources/InsaneWidget/UI/StampWidget.h:46
#: ../../Resources/InsaneWidget/UI/StampWidget.h:47
#: ../../Resources/StampWidget/UI/StampWidget.h:57
msgid "StampWidget"
msgstr ""
#: ../../Resources/InsaneWidget/UI/StampWidget.cpp:150
#: ../../Resources/StampWidget/UI/StampWidget.cpp:171
msgid "Location of the stamp on the output."
msgstr "Place du tampon dans la sortie."
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:156
#: ../../Resources/InsaneWidget/UI/StampWidgets.cpp:127
#: ../../Resources/StampWidget/UI/StampWidgets.cpp:127
msgid "+"
msgstr ""
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:158
#: ../../Resources/InsaneWidget/UI/StampWidgets.cpp:129
#: ../../Resources/StampWidget/UI/StampWidgets.cpp:129
msgid "Add a stamp widget."
msgstr "Ajouter un widget 'Tampon'."
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:161
#: ../../Resources/InsaneWidget/UI/StampWidgets.cpp:132
#: ../../Resources/StampWidget/UI/StampWidgets.cpp:132
msgid "-"
msgstr ""
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:163
#: ../../Resources/InsaneWidget/UI/StampWidgets.cpp:134
#: ../../Resources/StampWidget/UI/StampWidgets.cpp:134
msgid "Remove the selected stamp widget."
msgstr "Supprimer le widget 'Tampon' sélectionné."
#: ../../Resources/InsaneWidget/XStampWidget.cpp:56
#: ../../Resources/InsaneWidget/XStampWidget.cpp:57
#: ../../Resources/StampWidget/XStampWidget.cpp:76
msgid "Centre"
msgstr "Centre"
#: ../../Resources/InsaneWidget/XStampWidget.cpp:57
#: ../../Resources/InsaneWidget/XStampWidget.cpp:58
#: ../../Resources/StampWidget/XStampWidget.cpp:77
msgid "North"
msgstr "Nord"
#: ../../Resources/InsaneWidget/XStampWidget.cpp:57
#: ../../Resources/InsaneWidget/XStampWidget.cpp:58
#: ../../Resources/StampWidget/XStampWidget.cpp:77
msgid "South"
msgstr "Sud"
#: ../../Resources/InsaneWidget/XStampWidget.cpp:57
#: ../../Resources/InsaneWidget/XStampWidget.cpp:58
#: ../../Resources/StampWidget/XStampWidget.cpp:77
msgid "West"
msgstr "Ouest"
#: ../../Resources/InsaneWidget/XStampWidget.cpp:58
#: ../../Resources/InsaneWidget/XStampWidget.cpp:59
#: ../../Resources/StampWidget/XStampWidget.cpp:78
msgid "North-east"
msgstr "Nord-est"
#: ../../Resources/InsaneWidget/XStampWidget.cpp:58
#: ../../Resources/InsaneWidget/XStampWidget.cpp:59
#: ../../Resources/StampWidget/XStampWidget.cpp:78
msgid "North-west"
msgstr "Nord-ouest"
#: ../../Resources/InsaneWidget/XStampWidget.cpp:59
#: ../../Resources/InsaneWidget/XStampWidget.cpp:60
#: ../../Resources/StampWidget/XStampWidget.cpp:79
msgid "South-east"
msgstr "Sud-est"
#: ../../Resources/InsaneWidget/XStampWidget.cpp:59
#: ../../Resources/InsaneWidget/XStampWidget.cpp:60
#: ../../Resources/StampWidget/XStampWidget.cpp:79
msgid "South-west"
msgstr "Sud-ouest"
#: ../../Resources/InsaneWidget/XStampWidget.cpp:57
#: ../../Resources/InsaneWidget/XStampWidget.cpp:58
#: ../../Resources/StampWidget/XStampWidget.cpp:77
msgid "East"
msgstr "Est"
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:137
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:141
msgid ""
"'Left click' to start the scan project.\n"
@@ -523,12 +383,10 @@ msgstr ""
"'Clic droit' pour afficher les options du numériseur.\n"
"'Ctrl + Clic droit' pour définir un tampon."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:376
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:377
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:423
msgid "Stamps"
msgstr "Tampons"
#: ../../Resources/InsaneWidget/UI/StampWidgets.h:44
#: ../../Resources/StampWidget/UI/StampWidgets.h:44
msgid "StampWidgets"
msgstr ""
@@ -565,3 +423,7 @@ msgstr "Transparent"
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:511
msgid "Cancel"
msgstr "Annuler"

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 );

View File

@@ -126,7 +126,7 @@ void XS7::OnAppKeyPressed(wxKeyEvent& evt)
void XS7::OnNewDocLeftClick ( wxMouseEvent& evt )
{
if ( !evt.ShiftDown() )
if ( !evt.ShiftDown() || !m_insaneWidget->lblNewDoc->IsEnabled() )
{
evt.Skip();
return;