Initial commit.

This commit is contained in:
Saleem Edah-Tally
2025-06-28 17:40:42 +02:00
commit ef6f25ef27
52 changed files with 8156 additions and 0 deletions

30
CMakeLists.txt Normal file
View File

@@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 3.5)
project(s7)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMake")
find_package(wxWidgets COMPONENTS base core CONFIG REQUIRED)
# xpm include.
add_definitions(-Wno-write-strings)
include_directories(${CMAKE_CURRENT_LIST_DIR}
Resources/InsaneWidget
Resources/Utilities
Resources/InsaneWidget/UI
Resources/UI/S7)
add_subdirectory(Resources/Utilities)
add_subdirectory(Resources/InsaneWidget)
add_executable(s7
Resources/UI/S7/s7app.cpp
Resources/UI/S7/s7.cpp
XS7.cpp)
install(TARGETS s7 RUNTIME DESTINATION bin)
target_link_libraries(s7 minutils insanewidget
${wxWidgets_LIBRARIES}
)

55
README.md Normal file
View File

@@ -0,0 +1,55 @@
# S7
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.
It is based on [libinsane](https://gitlab.gnome.org/World/OpenPaperwork/libinsane) and written with [wxWidgets](https://wxwidgets.org).
![S7_01](S7_01.png)
### Usage
Inputs:
- a target directory
- the basename of the output file(s)
- the number of faces to scan
- double-sided or not
- the scanner and its minimal parameters (source, mode, resolution)
- page size
- output file format.
Right click on the 'New' label to specify the number of faces and whether double-sided scanning is needed.
'Shift + left click' on the 'New' label to update the output path.
Right click on the 'Scan' button to set the device properties.
Outputs:
- a single PDF file with the number of requested pages
- multiple PNG files with a padded index as suffix.
### Double-sided scanning
This has meaning if an automatic document feeder (ADF) is used to scan multiple pages on both sides.
Feed in all the pages on the front face, then turn the whole pile of pages even if an odd number of pages is requested and continue scanning.
If only front faces are needed with an ADF, uncheck the 'Double-sided' option.
### Notes
The project targets Linux only using wxGTK3.
When running under Wayland, set the 'GDK_BACKEND=x11' environment variable to avoid unexpected results.
### Disclaimer
Use at your own risks.

View File

@@ -0,0 +1,517 @@
CeCILL-C FREE SOFTWARE LICENSE AGREEMENT
Notice
This Agreement is a Free Software license agreement that is the result
of discussions between its authors in order to ensure compliance with
the two main principles guiding its drafting:
* firstly, compliance with the principles governing the distribution
of Free Software: access to source code, broad rights granted to
users,
* secondly, the election of a governing law, French law, with which
it is conformant, both as regards the law of torts and
intellectual property law, and the protection that it offers to
both authors and holders of the economic rights over software.
The authors of the CeCILL-C (for Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre])
license are:
Commissariat à l'Energie Atomique - CEA, a public scientific, technical
and industrial research establishment, having its principal place of
business at 25 rue Leblanc, immeuble Le Ponant D, 75015 Paris, France.
Centre National de la Recherche Scientifique - CNRS, a public scientific
and technological establishment, having its principal place of business
at 3 rue Michel-Ange, 75794 Paris cedex 16, France.
Institut National de Recherche en Informatique et en Automatique -
INRIA, a public scientific and technological establishment, having its
principal place of business at Domaine de Voluceau, Rocquencourt, BP
105, 78153 Le Chesnay cedex, France.
Preamble
The purpose of this Free Software license agreement is to grant users
the right to modify and re-use the software governed by this license.
The exercising of this right is conditional upon the obligation to make
available to the community the modifications made to the source code of
the software so as to contribute to its evolution.
In consideration of access to the source code and the rights to copy,
modify and redistribute granted by the license, users are provided only
with a limited warranty and the software's author, the holder of the
economic rights, and the successive licensors only have limited liability.
In this respect, the risks associated with loading, using, modifying
and/or developing or reproducing the software by the user are brought to
the user's attention, given its Free Software status, which may make it
complicated to use, with the result that its use is reserved for
developers and experienced professionals having in-depth computer
knowledge. Users are therefore encouraged to load and test the
suitability of the software as regards their requirements in conditions
enabling the security of their systems and/or data to be ensured and,
more generally, to use and operate it in the same conditions of
security. This Agreement may be freely reproduced and published,
provided it is not altered, and that no provisions are either added or
removed herefrom.
This Agreement may apply to any or all software for which the holder of
the economic rights decides to submit the use thereof to its provisions.
Article 1 - DEFINITIONS
For the purpose of this Agreement, when the following expressions
commence with a capital letter, they shall have the following meaning:
Agreement: means this license agreement, and its possible subsequent
versions and annexes.
Software: means the software in its Object Code and/or Source Code form
and, where applicable, its documentation, "as is" when the Licensee
accepts the Agreement.
Initial Software: means the Software in its Source Code and possibly its
Object Code form and, where applicable, its documentation, "as is" when
it is first distributed under the terms and conditions of the Agreement.
Modified Software: means the Software modified by at least one
Integrated Contribution.
Source Code: means all the Software's instructions and program lines to
which access is required so as to modify the Software.
Object Code: means the binary files originating from the compilation of
the Source Code.
Holder: means the holder(s) of the economic rights over the Initial
Software.
Licensee: means the Software user(s) having accepted the Agreement.
Contributor: means a Licensee having made at least one Integrated
Contribution.
Licensor: means the Holder, or any other individual or legal entity, who
distributes the Software under the Agreement.
Integrated Contribution: means any or all modifications, corrections,
translations, adaptations and/or new functions integrated into the
Source Code by any or all Contributors.
Related Module: means a set of sources files including their
documentation that, without modification to the Source Code, enables
supplementary functions or services in addition to those offered by the
Software.
Derivative Software: means any combination of the Software, modified or
not, and of a Related Module.
Parties: mean both the Licensee and the Licensor.
These expressions may be used both in singular and plural form.
Article 2 - PURPOSE
The purpose of the Agreement is the grant by the Licensor to the
Licensee of a non-exclusive, transferable and worldwide license for the
Software as set forth in Article 5 hereinafter for the whole term of the
protection granted by the rights over said Software.
Article 3 - ACCEPTANCE
3.1 The Licensee shall be deemed as having accepted the terms and
conditions of this Agreement upon the occurrence of the first of the
following events:
* (i) loading the Software by any or all means, notably, by
downloading from a remote server, or by loading from a physical
medium;
* (ii) the first time the Licensee exercises any of the rights
granted hereunder.
3.2 One copy of the Agreement, containing a notice relating to the
characteristics of the Software, to the limited warranty, and to the
fact that its use is restricted to experienced users has been provided
to the Licensee prior to its acceptance as set forth in Article 3.1
hereinabove, and the Licensee hereby acknowledges that it has read and
understood it.
Article 4 - EFFECTIVE DATE AND TERM
4.1 EFFECTIVE DATE
The Agreement shall become effective on the date when it is accepted by
the Licensee as set forth in Article 3.1.
4.2 TERM
The Agreement shall remain in force for the entire legal term of
protection of the economic rights over the Software.
Article 5 - SCOPE OF RIGHTS GRANTED
The Licensor hereby grants to the Licensee, who accepts, the following
rights over the Software for any or all use, and for the term of the
Agreement, on the basis of the terms and conditions set forth hereinafter.
Besides, if the Licensor owns or comes to own one or more patents
protecting all or part of the functions of the Software or of its
components, the Licensor undertakes not to enforce the rights granted by
these patents against successive Licensees using, exploiting or
modifying the Software. If these patents are transferred, the Licensor
undertakes to have the transferees subscribe to the obligations set
forth in this paragraph.
5.1 RIGHT OF USE
The Licensee is authorized to use the Software, without any limitation
as to its fields of application, with it being hereinafter specified
that this comprises:
1. permanent or temporary reproduction of all or part of the Software
by any or all means and in any or all form.
2. loading, displaying, running, or storing the Software on any or
all medium.
3. entitlement to observe, study or test its operation so as to
determine the ideas and principles behind any or all constituent
elements of said Software. This shall apply when the Licensee
carries out any or all loading, displaying, running, transmission
or storage operation as regards the Software, that it is entitled
to carry out hereunder.
5.2 RIGHT OF MODIFICATION
The right of modification includes the right to translate, adapt,
arrange, or make any or all modifications to the Software, and the right
to reproduce the resulting software. It includes, in particular, the
right to create a Derivative Software.
The Licensee is authorized to make any or all modification to the
Software provided that it includes an explicit notice that it is the
author of said modification and indicates the date of the creation thereof.
5.3 RIGHT OF DISTRIBUTION
In particular, the right of distribution includes the right to publish,
transmit and communicate the Software to the general public on any or
all medium, and by any or all means, and the right to market, either in
consideration of a fee, or free of charge, one or more copies of the
Software by any means.
The Licensee is further authorized to distribute copies of the modified
or unmodified Software to third parties according to the terms and
conditions set forth hereinafter.
5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION
The Licensee is authorized to distribute true copies of the Software in
Source Code or Object Code form, provided that said distribution
complies with all the provisions of the Agreement and is accompanied by:
1. a copy of the Agreement,
2. a notice relating to the limitation of both the Licensor's
warranty and liability as set forth in Articles 8 and 9,
and that, in the event that only the Object Code of the Software is
redistributed, the Licensee allows effective access to the full Source
Code of the Software at a minimum during the entire period of its
distribution of the Software, it being understood that the additional
cost of acquiring the Source Code shall not exceed the cost of
transferring the data.
5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE
When the Licensee makes an Integrated Contribution to the Software, the
terms and conditions for the distribution of the resulting Modified
Software become subject to all the provisions of this Agreement.
The Licensee is authorized to distribute the Modified Software, in
source code or object code form, provided that said distribution
complies with all the provisions of the Agreement and is accompanied by:
1. a copy of the Agreement,
2. a notice relating to the limitation of both the Licensor's
warranty and liability as set forth in Articles 8 and 9,
and that, in the event that only the object code of the Modified
Software is redistributed, the Licensee allows effective access to the
full source code of the Modified Software at a minimum during the entire
period of its distribution of the Modified Software, it being understood
that the additional cost of acquiring the source code shall not exceed
the cost of transferring the data.
5.3.3 DISTRIBUTION OF DERIVATIVE SOFTWARE
When the Licensee creates Derivative Software, this Derivative Software
may be distributed under a license agreement other than this Agreement,
subject to compliance with the requirement to include a notice
concerning the rights over the Software as defined in Article 6.4.
In the event the creation of the Derivative Software required modification
of the Source Code, the Licensee undertakes that:
1. the resulting Modified Software will be governed by this Agreement,
2. the Integrated Contributions in the resulting Modified Software
will be clearly identified and documented,
3. the Licensee will allow effective access to the source code of the
Modified Software, at a minimum during the entire period of
distribution of the Derivative Software, such that such
modifications may be carried over in a subsequent version of the
Software; it being understood that the additional cost of
purchasing the source code of the Modified Software shall not
exceed the cost of transferring the data.
5.3.4 COMPATIBILITY WITH THE CeCILL LICENSE
When a Modified Software contains an Integrated Contribution subject to
the CeCILL license agreement, or when a Derivative Software contains a
Related Module subject to the CeCILL license agreement, the provisions
set forth in the third item of Article 6.4 are optional.
Article 6 - INTELLECTUAL PROPERTY
6.1 OVER THE INITIAL SOFTWARE
The Holder owns the economic rights over the Initial Software. Any or
all use of the Initial Software is subject to compliance with the terms
and conditions under which the Holder has elected to distribute its work
and no one shall be entitled to modify the terms and conditions for the
distribution of said Initial Software.
The Holder undertakes that the Initial Software will remain ruled at
least by this Agreement, for the duration set forth in Article 4.2.
6.2 OVER THE INTEGRATED CONTRIBUTIONS
The Licensee who develops an Integrated Contribution is the owner of the
intellectual property rights over this Contribution as defined by
applicable law.
6.3 OVER THE RELATED MODULES
The Licensee who develops a Related Module is the owner of the
intellectual property rights over this Related Module as defined by
applicable law and is free to choose the type of agreement that shall
govern its distribution under the conditions defined in Article 5.3.3.
6.4 NOTICE OF RIGHTS
The Licensee expressly undertakes:
1. not to remove, or modify, in any manner, the intellectual property
notices attached to the Software;
2. to reproduce said notices, in an identical manner, in the copies
of the Software modified or not;
3. to ensure that use of the Software, its intellectual property
notices and the fact that it is governed by the Agreement is
indicated in a text that is easily accessible, specifically from
the interface of any Derivative Software.
The Licensee undertakes not to directly or indirectly infringe the
intellectual property rights of the Holder and/or Contributors on the
Software and to take, where applicable, vis-à-vis its staff, any and all
measures required to ensure respect of said intellectual property rights
of the Holder and/or Contributors.
Article 7 - RELATED SERVICES
7.1 Under no circumstances shall the Agreement oblige the Licensor to
provide technical assistance or maintenance services for the Software.
However, the Licensor is entitled to offer this type of services. The
terms and conditions of such technical assistance, and/or such
maintenance, shall be set forth in a separate instrument. Only the
Licensor offering said maintenance and/or technical assistance services
shall incur liability therefor.
7.2 Similarly, any Licensor is entitled to offer to its licensees, under
its sole responsibility, a warranty, that shall only be binding upon
itself, for the redistribution of the Software and/or the Modified
Software, under terms and conditions that it is free to decide. Said
warranty, and the financial terms and conditions of its application,
shall be subject of a separate instrument executed between the Licensor
and the Licensee.
Article 8 - LIABILITY
8.1 Subject to the provisions of Article 8.2, the Licensee shall be
entitled to claim compensation for any direct loss it may have suffered
from the Software as a result of a fault on the part of the relevant
Licensor, subject to providing evidence thereof.
8.2 The Licensor's liability is limited to the commitments made under
this Agreement and shall not be incurred as a result of in particular:
(i) loss due the Licensee's total or partial failure to fulfill its
obligations, (ii) direct or consequential loss that is suffered by the
Licensee due to the use or performance of the Software, and (iii) more
generally, any consequential loss. In particular the Parties expressly
agree that any or all pecuniary or business loss (i.e. loss of data,
loss of profits, operating loss, loss of customers or orders,
opportunity cost, any disturbance to business activities) or any or all
legal proceedings instituted against the Licensee by a third party,
shall constitute consequential loss and shall not provide entitlement to
any or all compensation from the Licensor.
Article 9 - WARRANTY
9.1 The Licensee acknowledges that the scientific and technical
state-of-the-art when the Software was distributed did not enable all
possible uses to be tested and verified, nor for the presence of
possible defects to be detected. In this respect, the Licensee's
attention has been drawn to the risks associated with loading, using,
modifying and/or developing and reproducing the Software which are
reserved for experienced users.
The Licensee shall be responsible for verifying, by any or all means,
the suitability of the product for its requirements, its good working
order, and for ensuring that it shall not cause damage to either persons
or properties.
9.2 The Licensor hereby represents, in good faith, that it is entitled
to grant all the rights over the Software (including in particular the
rights set forth in Article 5).
9.3 The Licensee acknowledges that the Software is supplied "as is" by
the Licensor without any other express or tacit warranty, other than
that provided for in Article 9.2 and, in particular, without any warranty
as to its commercial value, its secured, safe, innovative or relevant
nature.
Specifically, the Licensor does not warrant that the Software is free
from any error, that it will operate without interruption, that it will
be compatible with the Licensee's own equipment and software
configuration, nor that it will meet the Licensee's requirements.
9.4 The Licensor does not either expressly or tacitly warrant that the
Software does not infringe any third party intellectual property right
relating to a patent, software or any other property right. Therefore,
the Licensor disclaims any and all liability towards the Licensee
arising out of any or all proceedings for infringement that may be
instituted in respect of the use, modification and redistribution of the
Software. Nevertheless, should such proceedings be instituted against
the Licensee, the Licensor shall provide it with technical and legal
assistance for its defense. Such technical and legal assistance shall be
decided on a case-by-case basis between the relevant Licensor and the
Licensee pursuant to a memorandum of understanding. The Licensor
disclaims any and all liability as regards the Licensee's use of the
name of the Software. No warranty is given as regards the existence of
prior rights over the name of the Software or as regards the existence
of a trademark.
Article 10 - TERMINATION
10.1 In the event of a breach by the Licensee of its obligations
hereunder, the Licensor may automatically terminate this Agreement
thirty (30) days after notice has been sent to the Licensee and has
remained ineffective.
10.2 A Licensee whose Agreement is terminated shall no longer be
authorized to use, modify or distribute the Software. However, any
licenses that it may have granted prior to termination of the Agreement
shall remain valid subject to their having been granted in compliance
with the terms and conditions hereof.
Article 11 - MISCELLANEOUS
11.1 EXCUSABLE EVENTS
Neither Party shall be liable for any or all delay, or failure to
perform the Agreement, that may be attributable to an event of force
majeure, an act of God or an outside cause, such as defective
functioning or interruptions of the electricity or telecommunications
networks, network paralysis following a virus attack, intervention by
government authorities, natural disasters, water damage, earthquakes,
fire, explosions, strikes and labor unrest, war, etc.
11.2 Any failure by either Party, on one or more occasions, to invoke
one or more of the provisions hereof, shall under no circumstances be
interpreted as being a waiver by the interested Party of its right to
invoke said provision(s) subsequently.
11.3 The Agreement cancels and replaces any or all previous agreements,
whether written or oral, between the Parties and having the same
purpose, and constitutes the entirety of the agreement between said
Parties concerning said purpose. No supplement or modification to the
terms and conditions hereof shall be effective as between the Parties
unless it is made in writing and signed by their duly authorized
representatives.
11.4 In the event that one or more of the provisions hereof were to
conflict with a current or future applicable act or legislative text,
said act or legislative text shall prevail, and the Parties shall make
the necessary amendments so as to comply with said act or legislative
text. All other provisions shall remain effective. Similarly, invalidity
of a provision of the Agreement, for any reason whatsoever, shall not
cause the Agreement as a whole to be invalid.
11.5 LANGUAGE
The Agreement is drafted in both French and English and both versions
are deemed authentic.
Article 12 - NEW VERSIONS OF THE AGREEMENT
12.1 Any person is authorized to duplicate and distribute copies of this
Agreement.
12.2 So as to ensure coherence, the wording of this Agreement is
protected and may only be modified by the authors of the License, who
reserve the right to periodically publish updates or new versions of the
Agreement, each with a separate number. These subsequent versions may
address new issues encountered by Free Software.
12.3 Any Software distributed under a given version of the Agreement may
only be subsequently distributed under the same version of the Agreement
or a subsequent version.
Article 13 - GOVERNING LAW AND JURISDICTION
13.1 The Agreement is governed by French law. The Parties agree to
endeavor to seek an amicable solution to any disagreements or disputes
that may arise during the performance of the Agreement.
13.2 Failing an amicable solution within two (2) months as from their
occurrence, and unless emergency proceedings are necessary, the
disagreements or disputes shall be referred to the Paris Courts having
jurisdiction, by the more diligent Party.
Version 1.0 dated 2006-09-05.

View File

@@ -0,0 +1,519 @@
CeCILL FREE SOFTWARE LICENSE AGREEMENT
Version 2.1 dated 2013-06-21
Notice
This Agreement is a Free Software license agreement that is the result
of discussions between its authors in order to ensure compliance with
the two main principles guiding its drafting:
* firstly, compliance with the principles governing the distribution
of Free Software: access to source code, broad rights granted to users,
* secondly, the election of a governing law, French law, with which it
is conformant, both as regards the law of torts and intellectual
property law, and the protection that it offers to both authors and
holders of the economic rights over software.
The authors of the CeCILL (for Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre])
license are:
Commissariat à l'énergie atomique et aux énergies alternatives - CEA, a
public scientific, technical and industrial research establishment,
having its principal place of business at 25 rue Leblanc, immeuble Le
Ponant D, 75015 Paris, France.
Centre National de la Recherche Scientifique - CNRS, a public scientific
and technological establishment, having its principal place of business
at 3 rue Michel-Ange, 75794 Paris cedex 16, France.
Institut National de Recherche en Informatique et en Automatique -
Inria, a public scientific and technological establishment, having its
principal place of business at Domaine de Voluceau, Rocquencourt, BP
105, 78153 Le Chesnay cedex, France.
Preamble
The purpose of this Free Software license agreement is to grant users
the right to modify and redistribute the software governed by this
license within the framework of an open source distribution model.
The exercising of this right is conditional upon certain obligations for
users so as to preserve this status for all subsequent redistributions.
In consideration of access to the source code and the rights to copy,
modify and redistribute granted by the license, users are provided only
with a limited warranty and the software's author, the holder of the
economic rights, and the successive licensors only have limited liability.
In this respect, the risks associated with loading, using, modifying
and/or developing or reproducing the software by the user are brought to
the user's attention, given its Free Software status, which may make it
complicated to use, with the result that its use is reserved for
developers and experienced professionals having in-depth computer
knowledge. Users are therefore encouraged to load and test the
suitability of the software as regards their requirements in conditions
enabling the security of their systems and/or data to be ensured and,
more generally, to use and operate it in the same conditions of
security. This Agreement may be freely reproduced and published,
provided it is not altered, and that no provisions are either added or
removed herefrom.
This Agreement may apply to any or all software for which the holder of
the economic rights decides to submit the use thereof to its provisions.
Frequently asked questions can be found on the official website of the
CeCILL licenses family (http://www.cecill.info/index.en.html) for any
necessary clarification.
Article 1 - DEFINITIONS
For the purpose of this Agreement, when the following expressions
commence with a capital letter, they shall have the following meaning:
Agreement: means this license agreement, and its possible subsequent
versions and annexes.
Software: means the software in its Object Code and/or Source Code form
and, where applicable, its documentation, "as is" when the Licensee
accepts the Agreement.
Initial Software: means the Software in its Source Code and possibly its
Object Code form and, where applicable, its documentation, "as is" when
it is first distributed under the terms and conditions of the Agreement.
Modified Software: means the Software modified by at least one
Contribution.
Source Code: means all the Software's instructions and program lines to
which access is required so as to modify the Software.
Object Code: means the binary files originating from the compilation of
the Source Code.
Holder: means the holder(s) of the economic rights over the Initial
Software.
Licensee: means the Software user(s) having accepted the Agreement.
Contributor: means a Licensee having made at least one Contribution.
Licensor: means the Holder, or any other individual or legal entity, who
distributes the Software under the Agreement.
Contribution: means any or all modifications, corrections, translations,
adaptations and/or new functions integrated into the Software by any or
all Contributors, as well as any or all Internal Modules.
Module: means a set of sources files including their documentation that
enables supplementary functions or services in addition to those offered
by the Software.
External Module: means any or all Modules, not derived from the
Software, so that this Module and the Software run in separate address
spaces, with one calling the other when they are run.
Internal Module: means any or all Module, connected to the Software so
that they both execute in the same address space.
GNU GPL: means the GNU General Public License version 2 or any
subsequent version, as published by the Free Software Foundation Inc.
GNU Affero GPL: means the GNU Affero General Public License version 3 or
any subsequent version, as published by the Free Software Foundation Inc.
EUPL: means the European Union Public License version 1.1 or any
subsequent version, as published by the European Commission.
Parties: mean both the Licensee and the Licensor.
These expressions may be used both in singular and plural form.
Article 2 - PURPOSE
The purpose of the Agreement is the grant by the Licensor to the
Licensee of a non-exclusive, transferable and worldwide license for the
Software as set forth in Article 5 <#scope> hereinafter for the whole
term of the protection granted by the rights over said Software.
Article 3 - ACCEPTANCE
3.1 The Licensee shall be deemed as having accepted the terms and
conditions of this Agreement upon the occurrence of the first of the
following events:
* (i) loading the Software by any or all means, notably, by
downloading from a remote server, or by loading from a physical medium;
* (ii) the first time the Licensee exercises any of the rights granted
hereunder.
3.2 One copy of the Agreement, containing a notice relating to the
characteristics of the Software, to the limited warranty, and to the
fact that its use is restricted to experienced users has been provided
to the Licensee prior to its acceptance as set forth in Article 3.1
<#accepting> hereinabove, and the Licensee hereby acknowledges that it
has read and understood it.
Article 4 - EFFECTIVE DATE AND TERM
4.1 EFFECTIVE DATE
The Agreement shall become effective on the date when it is accepted by
the Licensee as set forth in Article 3.1 <#accepting>.
4.2 TERM
The Agreement shall remain in force for the entire legal term of
protection of the economic rights over the Software.
Article 5 - SCOPE OF RIGHTS GRANTED
The Licensor hereby grants to the Licensee, who accepts, the following
rights over the Software for any or all use, and for the term of the
Agreement, on the basis of the terms and conditions set forth hereinafter.
Besides, if the Licensor owns or comes to own one or more patents
protecting all or part of the functions of the Software or of its
components, the Licensor undertakes not to enforce the rights granted by
these patents against successive Licensees using, exploiting or
modifying the Software. If these patents are transferred, the Licensor
undertakes to have the transferees subscribe to the obligations set
forth in this paragraph.
5.1 RIGHT OF USE
The Licensee is authorized to use the Software, without any limitation
as to its fields of application, with it being hereinafter specified
that this comprises:
1. permanent or temporary reproduction of all or part of the Software
by any or all means and in any or all form.
2. loading, displaying, running, or storing the Software on any or all
medium.
3. entitlement to observe, study or test its operation so as to
determine the ideas and principles behind any or all constituent
elements of said Software. This shall apply when the Licensee
carries out any or all loading, displaying, running, transmission or
storage operation as regards the Software, that it is entitled to
carry out hereunder.
5.2 ENTITLEMENT TO MAKE CONTRIBUTIONS
The right to make Contributions includes the right to translate, adapt,
arrange, or make any or all modifications to the Software, and the right
to reproduce the resulting software.
The Licensee is authorized to make any or all Contributions to the
Software provided that it includes an explicit notice that it is the
author of said Contribution and indicates the date of the creation thereof.
5.3 RIGHT OF DISTRIBUTION
In particular, the right of distribution includes the right to publish,
transmit and communicate the Software to the general public on any or
all medium, and by any or all means, and the right to market, either in
consideration of a fee, or free of charge, one or more copies of the
Software by any means.
The Licensee is further authorized to distribute copies of the modified
or unmodified Software to third parties according to the terms and
conditions set forth hereinafter.
5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION
The Licensee is authorized to distribute true copies of the Software in
Source Code or Object Code form, provided that said distribution
complies with all the provisions of the Agreement and is accompanied by:
1. a copy of the Agreement,
2. a notice relating to the limitation of both the Licensor's warranty
and liability as set forth in Articles 8 and 9,
and that, in the event that only the Object Code of the Software is
redistributed, the Licensee allows effective access to the full Source
Code of the Software for a period of at least three years from the
distribution of the Software, it being understood that the additional
acquisition cost of the Source Code shall not exceed the cost of the
data transfer.
5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE
When the Licensee makes a Contribution to the Software, the terms and
conditions for the distribution of the resulting Modified Software
become subject to all the provisions of this Agreement.
The Licensee is authorized to distribute the Modified Software, in
source code or object code form, provided that said distribution
complies with all the provisions of the Agreement and is accompanied by:
1. a copy of the Agreement,
2. a notice relating to the limitation of both the Licensor's warranty
and liability as set forth in Articles 8 and 9,
and, in the event that only the object code of the Modified Software is
redistributed,
3. a note stating the conditions of effective access to the full source
code of the Modified Software for a period of at least three years
from the distribution of the Modified Software, it being understood
that the additional acquisition cost of the source code shall not
exceed the cost of the data transfer.
5.3.3 DISTRIBUTION OF EXTERNAL MODULES
When the Licensee has developed an External Module, the terms and
conditions of this Agreement do not apply to said External Module, that
may be distributed under a separate license agreement.
5.3.4 COMPATIBILITY WITH OTHER LICENSES
The Licensee can include a code that is subject to the provisions of one
of the versions of the GNU GPL, GNU Affero GPL and/or EUPL in the
Modified or unmodified Software, and distribute that entire code under
the terms of the same version of the GNU GPL, GNU Affero GPL and/or EUPL.
The Licensee can include the Modified or unmodified Software in a code
that is subject to the provisions of one of the versions of the GNU GPL,
GNU Affero GPL and/or EUPL and distribute that entire code under the
terms of the same version of the GNU GPL, GNU Affero GPL and/or EUPL.
Article 6 - INTELLECTUAL PROPERTY
6.1 OVER THE INITIAL SOFTWARE
The Holder owns the economic rights over the Initial Software. Any or
all use of the Initial Software is subject to compliance with the terms
and conditions under which the Holder has elected to distribute its work
and no one shall be entitled to modify the terms and conditions for the
distribution of said Initial Software.
The Holder undertakes that the Initial Software will remain ruled at
least by this Agreement, for the duration set forth in Article 4.2 <#term>.
6.2 OVER THE CONTRIBUTIONS
The Licensee who develops a Contribution is the owner of the
intellectual property rights over this Contribution as defined by
applicable law.
6.3 OVER THE EXTERNAL MODULES
The Licensee who develops an External Module is the owner of the
intellectual property rights over this External Module as defined by
applicable law and is free to choose the type of agreement that shall
govern its distribution.
6.4 JOINT PROVISIONS
The Licensee expressly undertakes:
1. not to remove, or modify, in any manner, the intellectual property
notices attached to the Software;
2. to reproduce said notices, in an identical manner, in the copies of
the Software modified or not.
The Licensee undertakes not to directly or indirectly infringe the
intellectual property rights on the Software of the Holder and/or
Contributors, and to take, where applicable, vis-à-vis its staff, any
and all measures required to ensure respect of said intellectual
property rights of the Holder and/or Contributors.
Article 7 - RELATED SERVICES
7.1 Under no circumstances shall the Agreement oblige the Licensor to
provide technical assistance or maintenance services for the Software.
However, the Licensor is entitled to offer this type of services. The
terms and conditions of such technical assistance, and/or such
maintenance, shall be set forth in a separate instrument. Only the
Licensor offering said maintenance and/or technical assistance services
shall incur liability therefor.
7.2 Similarly, any Licensor is entitled to offer to its licensees, under
its sole responsibility, a warranty, that shall only be binding upon
itself, for the redistribution of the Software and/or the Modified
Software, under terms and conditions that it is free to decide. Said
warranty, and the financial terms and conditions of its application,
shall be subject of a separate instrument executed between the Licensor
and the Licensee.
Article 8 - LIABILITY
8.1 Subject to the provisions of Article 8.2, the Licensee shall be
entitled to claim compensation for any direct loss it may have suffered
from the Software as a result of a fault on the part of the relevant
Licensor, subject to providing evidence thereof.
8.2 The Licensor's liability is limited to the commitments made under
this Agreement and shall not be incurred as a result of in particular:
(i) loss due the Licensee's total or partial failure to fulfill its
obligations, (ii) direct or consequential loss that is suffered by the
Licensee due to the use or performance of the Software, and (iii) more
generally, any consequential loss. In particular the Parties expressly
agree that any or all pecuniary or business loss (i.e. loss of data,
loss of profits, operating loss, loss of customers or orders,
opportunity cost, any disturbance to business activities) or any or all
legal proceedings instituted against the Licensee by a third party,
shall constitute consequential loss and shall not provide entitlement to
any or all compensation from the Licensor.
Article 9 - WARRANTY
9.1 The Licensee acknowledges that the scientific and technical
state-of-the-art when the Software was distributed did not enable all
possible uses to be tested and verified, nor for the presence of
possible defects to be detected. In this respect, the Licensee's
attention has been drawn to the risks associated with loading, using,
modifying and/or developing and reproducing the Software which are
reserved for experienced users.
The Licensee shall be responsible for verifying, by any or all means,
the suitability of the product for its requirements, its good working
order, and for ensuring that it shall not cause damage to either persons
or properties.
9.2 The Licensor hereby represents, in good faith, that it is entitled
to grant all the rights over the Software (including in particular the
rights set forth in Article 5 <#scope>).
9.3 The Licensee acknowledges that the Software is supplied "as is" by
the Licensor without any other express or tacit warranty, other than
that provided for in Article 9.2 <#good-faith> and, in particular,
without any warranty as to its commercial value, its secured, safe,
innovative or relevant nature.
Specifically, the Licensor does not warrant that the Software is free
from any error, that it will operate without interruption, that it will
be compatible with the Licensee's own equipment and software
configuration, nor that it will meet the Licensee's requirements.
9.4 The Licensor does not either expressly or tacitly warrant that the
Software does not infringe any third party intellectual property right
relating to a patent, software or any other property right. Therefore,
the Licensor disclaims any and all liability towards the Licensee
arising out of any or all proceedings for infringement that may be
instituted in respect of the use, modification and redistribution of the
Software. Nevertheless, should such proceedings be instituted against
the Licensee, the Licensor shall provide it with technical and legal
expertise for its defense. Such technical and legal expertise shall be
decided on a case-by-case basis between the relevant Licensor and the
Licensee pursuant to a memorandum of understanding. The Licensor
disclaims any and all liability as regards the Licensee's use of the
name of the Software. No warranty is given as regards the existence of
prior rights over the name of the Software or as regards the existence
of a trademark.
Article 10 - TERMINATION
10.1 In the event of a breach by the Licensee of its obligations
hereunder, the Licensor may automatically terminate this Agreement
thirty (30) days after notice has been sent to the Licensee and has
remained ineffective.
10.2 A Licensee whose Agreement is terminated shall no longer be
authorized to use, modify or distribute the Software. However, any
licenses that it may have granted prior to termination of the Agreement
shall remain valid subject to their having been granted in compliance
with the terms and conditions hereof.
Article 11 - MISCELLANEOUS
11.1 EXCUSABLE EVENTS
Neither Party shall be liable for any or all delay, or failure to
perform the Agreement, that may be attributable to an event of force
majeure, an act of God or an outside cause, such as defective
functioning or interruptions of the electricity or telecommunications
networks, network paralysis following a virus attack, intervention by
government authorities, natural disasters, water damage, earthquakes,
fire, explosions, strikes and labor unrest, war, etc.
11.2 Any failure by either Party, on one or more occasions, to invoke
one or more of the provisions hereof, shall under no circumstances be
interpreted as being a waiver by the interested Party of its right to
invoke said provision(s) subsequently.
11.3 The Agreement cancels and replaces any or all previous agreements,
whether written or oral, between the Parties and having the same
purpose, and constitutes the entirety of the agreement between said
Parties concerning said purpose. No supplement or modification to the
terms and conditions hereof shall be effective as between the Parties
unless it is made in writing and signed by their duly authorized
representatives.
11.4 In the event that one or more of the provisions hereof were to
conflict with a current or future applicable act or legislative text,
said act or legislative text shall prevail, and the Parties shall make
the necessary amendments so as to comply with said act or legislative
text. All other provisions shall remain effective. Similarly, invalidity
of a provision of the Agreement, for any reason whatsoever, shall not
cause the Agreement as a whole to be invalid.
11.5 LANGUAGE
The Agreement is drafted in both French and English and both versions
are deemed authentic.
Article 12 - NEW VERSIONS OF THE AGREEMENT
12.1 Any person is authorized to duplicate and distribute copies of this
Agreement.
12.2 So as to ensure coherence, the wording of this Agreement is
protected and may only be modified by the authors of the License, who
reserve the right to periodically publish updates or new versions of the
Agreement, each with a separate number. These subsequent versions may
address new issues encountered by Free Software.
12.3 Any Software distributed under a given version of the Agreement may
only be subsequently distributed under the same version of the Agreement
or a subsequent version, subject to the provisions of Article 5.3.4
<#compatibility>.
Article 13 - GOVERNING LAW AND JURISDICTION
13.1 The Agreement is governed by French law. The Parties agree to
endeavor to seek an amicable solution to any disagreements or disputes
that may arise during the performance of the Agreement.
13.2 Failing an amicable solution within two (2) months as from their
occurrence, and unless emergency proceedings are necessary, the
disagreements or disputes shall be referred to the Paris Courts having
jurisdiction, by the more diligent Party.

View File

@@ -0,0 +1,29 @@
#
# - Try to find LibInsane
# Once done this will define
# LIBINSANE_FOUND - System has LibInsane
# LIBINSANE_INCLUDE_DIRS - The LibInsane include directories
# LIBINSANE_LIBRARIES - The libraries needed to use LibInsane
# LIBINSANE_DEFINITIONS - Compiler switches required for using LibInsane
find_package(PkgConfig)
pkg_check_modules(PC_LIBINSANE QUIET libinsane.pc)
set(LIBINSANE_DEFINITIONS ${PC_LIBINSANE_CFLAGS_OTHER})
find_path(LIBINSANE_INCLUDE_DIR libinsane/sane.h
HINTS ${PC_LIBINSANE_INCLUDEDIR} ${PC_LIBINSANE_INCLUDE_DIRS}
PATH_SUFFIXES libinsane )
find_library(LIBINSANE_LIBRARY NAMES libinsane.so
HINTS ${PC_LIBINSANE_LIBDIR} ${PC_LIBINSANE_LIBRARY_DIRS} )
set(LIBINSANE_LIBRARIES ${LIBINSANE_LIBRARY})
set(LIBINSANE_INCLUDE_DIRS ${LIBINSANE_INCLUDE_DIR} )
include(FindPackageHandleStandardArgs)
# handle the QUIETLY and REQUIRED arguments and set LIBINSANE_FOUND to TRUE
# if all listed variables are TRUE
find_package_handle_standard_args(LibInsane DEFAULT_MSG
LIBINSANE_LIBRARY LIBINSANE_INCLUDE_DIR)
mark_as_advanced(LIBINSANE_INCLUDE_DIR LIBINSANE_LIBRARY )

View File

@@ -0,0 +1,23 @@
# - Try to find Paper
# Once done this will define
# PAPER_FOUND - System has Paper
# PAPER_INCLUDE_DIRS - The Paper include directories
# PAPER_LIBRARIES - The libraries needed to use Paper
# PAPER_DEFINITIONS - Compiler switches required for using Paper
find_path(PAPER_INCLUDE_DIR paper.h
HINTS ${PC_PAPER_INCLUDEDIR} ${PC_PAPER_INCLUDE_DIRS}
PATH_SUFFIXES paper )
find_library(PAPER_LIBRARY NAMES libpaper.so
HINTS ${PC_PAPER_LIBDIR} ${PC_PAPER_LIBRARY_DIRS} )
set(PAPER_LIBRARIES ${PAPER_LIBRARY} )
set(PAPER_INCLUDE_DIRS ${PAPER_INCLUDE_DIR} )
include(FindPackageHandleStandardArgs)
# handle the QUIETLY and REQUIRED arguments and set PAPER_FOUND to TRUE
# if all listed variables are TRUE
find_package_handle_standard_args(Paper DEFAULT_MSG
PAPER_LIBRARY PAPER_INCLUDE_DIR)
mark_as_advanced(PAPER_INCLUDE_DIR PAPER_LIBRARY )

View File

@@ -0,0 +1,27 @@
# - Try to find PoDoFo
# Once done this will define
# PODOFO_FOUND - System has PoDoFo
# PODOFO_INCLUDE_DIRS - The PoDoFo include directories
# PODOFO_LIBRARIES - The libraries needed to use PoDoFo
# PODOFO_DEFINITIONS - Compiler switches required for using PoDoFo
find_package(PkgConfig)
pkg_check_modules(PC_PODOFO QUIET libpodofo.pc)
set(PODOFO_DEFINITIONS ${PC_PODOFO_CFLAGS_OTHER})
find_path(PODOFO_INCLUDE_DIR podofo/podofo.h
HINTS ${PC_PODOFO_INCLUDEDIR} ${PC_PODOFO_INCLUDE_DIRS}
PATH_SUFFIXES podofo )
find_library(PODOFO_LIBRARY NAMES libpodofo.so
HINTS ${PC_PODOFO_LIBDIR} ${PC_PODOFO_LIBRARY_DIRS} )
set(PODOFO_LIBRARIES ${PODOFO_LIBRARY} )
set(PODOFO_INCLUDE_DIRS ${PODOFO_INCLUDE_DIR} )
include(FindPackageHandleStandardArgs)
# handle the QUIETLY and REQUIRED arguments and set PODOFO_FOUND to TRUE
# if all listed variables are TRUE
find_package_handle_standard_args(PoDoFo DEFAULT_MSG
PODOFO_LIBRARY PODOFO_INCLUDE_DIR)
mark_as_advanced(PODOFO_INCLUDE_DIR PODOFO_LIBRARY )

View File

@@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 3.5)
project(InsaneWidget)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMake")
find_package(wxWidgets COMPONENTS base core CONFIG REQUIRED)
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}/../Utilities)
add_library(insanewidget STATIC
UI/InsaneWidget.cpp
UI/ScannerWidget.cpp
Common.h
XInsaneWidget.cpp
XScannerWidget.cpp
InsaneWorker.cpp
PixelToImageWriter.cpp
PixelToPdfWriter.cpp)
target_link_libraries(insanewidget minutils
${wxWidgets_LIBRARIES}
${LIBINSANE_LIBRARIES}
${PODOFO_LIBRARIES}
${NETPBM_LIBRARIES}
${PAPER_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 <string>
#include <map>
enum {PDF, PNG, JPEG, TIFF, PNM};
typedef std::map <int, std::string> ExtensionsMap;
static ExtensionsMap Extensions;
static void UpdateExtensionsMap()
{
Extensions[PDF] = "pdf";
Extensions[PNG] = "png";
Extensions[JPEG] = "jpeg";
Extensions[TIFF] = "tiff";
Extensions[PNM] = "pnm";
}
#endif // COMMON_H

View File

@@ -0,0 +1,582 @@
/*
* File: InsaneWorker.cpp
* Author: Saleem Edah-Tally - nmset@yandex.com
* License : CeCILL-C
* Copyright Saleem Edah-Tally - © 2024
*
* Created on 14 02 2024, 13:00
*/
#include "InsaneWorker.h"
#include <iostream>
#include <iomanip>
#include <sstream>
#include <fstream>
#include <paper.h>
#include <libinsane/log.h>
#include <libinsane/safebet.h>
#include <libinsane/error.h>
#include <libinsane/util.h>
#include <libinsane/normalizers.h>
#include <libinsane/constants.h>
using namespace std;
#define IERR(e) ("[" + to_string(e) + "] " + lis_strerror(e))
static bool gs_cancelRequested = false;
InsaneWorker::InsaneWorker ( InsaneWorkerEvent * evh )
{
m_evh = evh;
}
InsaneWorker::~InsaneWorker()
{
if (m_api)
m_api->cleanup(m_api);
}
bool InsaneWorker::Init()
{
// Terminate a previous one.
if (m_api)
{
m_devices.clear();
m_deviceId.clear();
m_api->cleanup(m_api);
m_sourceItem = nullptr;
m_rootSourceItem = nullptr;
}
lis_error err;
err = lis_safebet ( &m_api );
if ( err )
{
if ( m_evh )
{
m_evh->OnInsaneError ( IERR(err) );
return false;
}
}
lis_device_descriptor ** devices = nullptr; // nullptr terminated.
err = m_api->list_devices ( m_api, LIS_DEVICE_LOCATIONS_LOCAL_ONLY, &devices ); // Gets network scanners though.
if ( err )
{
m_evh->OnInsaneError ( IERR(err) );
return false;
}
uint numberOfDevices = 0;
for ( uint i = 0; devices[i] != nullptr; i++ )
{
numberOfDevices++;
}
// Collect available descriptions for all devices.
for ( uint i = 0; i < numberOfDevices; i++ )
{
DeviceDescriptor dd;
lis_device_descriptor * descriptor = devices[i];
dd.id = descriptor->dev_id;
dd.model = descriptor->model;
dd.type = descriptor->type;
dd.vendor = descriptor->vendor;
m_devices.push_back(dd);
}
return true;
}
// Determine if a source item is a feeder.
int InsaneWorker::IsADF(const std::string& deviceId, int sourceChildIndex)
{
if ( !m_api )
{
if ( m_evh )
{
m_evh->OnError ( "Insane api is NULL; has Init() been called?");
return -1;
}
}
lis_error err;
lis_item * rootSourceItem = nullptr;
err = m_api->get_device ( m_api, deviceId.c_str(), &rootSourceItem );
if ( err )
{
if ( m_evh )
m_evh->OnInsaneError ( IERR ( err ) );
return false;
}
lis_item ** children = nullptr;
err = rootSourceItem->get_children(rootSourceItem, &children);
if ( err )
{
if ( m_evh )
m_evh->OnInsaneError ( IERR ( err ) );
return -1;
}
bool adf = (children[sourceChildIndex]->type == LIS_ITEM_ADF);
rootSourceItem->close(rootSourceItem);
return adf;
}
// Ex: all supported resolutions.
std::string InsaneWorker::GetOptionPossibleValues(const std::string& deviceId,
const std::string& optionName, std::vector<std::string>& possible)
{
if ( !m_api )
{
if ( m_evh )
{
m_evh->OnError ( "Insane api is NULL; has Init() been called?");
return "";
}
}
lis_error err;
lis_item * item = nullptr;
err = m_api->get_device ( m_api, deviceId.c_str(), &item );
if ( err )
{
if ( m_evh )
m_evh->OnInsaneError ( IERR ( err ) );
return "";
}
lis_option_descriptor ** options = nullptr;
err = item->get_options ( item, &options );
if ( err )
{
if ( m_evh )
m_evh->OnInsaneError ( IERR ( err ) );
item->close(item);
return "";
}
uint numberOfOptions = 0;
for ( uint i = 0; options[i] != nullptr; i++ )
{
numberOfOptions++;
}
std::string defaultValue;
for ( uint i = 0; i < numberOfOptions; i++ )
{
lis_option_descriptor * optionDescriptor = options[i];
const std::string name = optionDescriptor->name;
if ( ToLower(name) != optionName )
continue;
union lis_value value;
err = optionDescriptor->fn.get_value ( optionDescriptor, &value );
if ( err )
{
if ( m_evh )
m_evh->OnInsaneError ( IERR ( err ) );
item->close(item);
return "";
}
lis_value_type type = optionDescriptor->value.type;
bool isString = false;
switch ( type )
{
case LIS_TYPE_BOOL:
defaultValue = std::to_string(( bool ) value.boolean);
break;
case LIS_TYPE_INTEGER:
defaultValue = std::to_string(( long ) value.integer);
break;
case LIS_TYPE_DOUBLE:
defaultValue = std::to_string(( double ) value.dbl);
break;
case LIS_TYPE_STRING:
isString = true;
defaultValue = value.string;
break;
case LIS_TYPE_IMAGE_FORMAT:
defaultValue = "Unmanaged";
break;
default:
defaultValue = "Unknown";
break;
}
lis_value_list valueList = optionDescriptor->constraint.possible.list;
int numberOfListItems = valueList.nb_values;
if ( numberOfListItems )
{
for ( int j = 0; j < numberOfListItems; j++ )
{
if ( isString )
possible.push_back( valueList.values[j].string );
else
possible.push_back( std::to_string ( valueList.values[j].integer ) );
}
}
}
item->close(item);
return defaultValue;
}
// Open a source item and prepare for the next step, i.e, scanning.
bool InsaneWorker::ConfigureDevice(const std::string& deviceId,
const std::string& source, int sourceChildIndex,
const std::string& mode, int resolution,
const std::string& paperSize)
{
if ( !m_api )
{
if ( m_evh )
{
m_evh->OnError ( "Insane api is NULL; has Init() been called?");
return false;
}
}
m_deviceId = deviceId;
m_source = source;
m_sourceChildIndex = (sourceChildIndex > -1) ? sourceChildIndex : 0;
m_mode = mode;
m_resolution = resolution;
m_paperSize = paperSize;
lis_error err;
err = m_api->get_device ( m_api, m_deviceId.c_str(), &m_rootSourceItem );
if ( err )
{
if ( m_evh )
m_evh->OnInsaneError ( IERR ( err ) );
return false;
}
lis_item ** children = nullptr;
err = m_rootSourceItem->get_children(m_rootSourceItem, &children);
if ( err )
{
if ( m_evh )
m_evh->OnInsaneError ( IERR ( err ) );
return false;
}
// Selecting a lis_item by the source index.
// One child item is created by libinsane if there is no source: v4l.
m_sourceItem = children[m_sourceChildIndex];
if (!m_sourceItem)
{
if (m_evh)
m_evh->OnError("Could not determine a device item");
m_rootSourceItem->close(m_rootSourceItem);
return false;
}
// Error status not checked; use defaults if failure.
lis_set_option(m_sourceItem, OPT_NAME_MODE, mode.c_str());
/*
* Selecting a source on a child item that has been selected by a source index.
* 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());
pair<double, double> br;
if (GetBottomRight(br))
{
// For v4l, these do not get applied.
lis_set_option(m_sourceItem, "br-x", to_string(br.first).c_str());
lis_set_option(m_sourceItem, "br-y", to_string(br.second).c_str());
}
// Don't close m_rootDeviceItem, it is prepared for ::Scan().
return true;
}
// Perform scanning: create raw data files.
bool InsaneWorker::Scan(const std::string& dir, const std::string& basename,
int startIndex, int padwidth, int increment)
{
if ( !m_api || !m_sourceItem )
{
if ( m_evh )
{
m_evh->OnError ( "Insane api or device item is NULL, cannot scan.");
return false;
}
}
if (dir.empty() || basename.empty() || startIndex < 0 || padwidth < 0 || (increment == 0))
{
if ( m_evh )
{
m_evh->OnError ( "Invalid input, cannot scan.");
return false;
}
}
auto makeFileName = [&] (int index)
{
// std:format is not friendly with a variable padwidth; requires a literal format.
stringstream ss;
ss << setfill('0') << setw(padwidth) << index;
return (dir + "/" + basename + "-" + ss.str());
};
lis_error err;
lis_scan_session * session;
// Scanning starts here on device.
err = m_sourceItem->scan_start(m_sourceItem, &session);
if (err || !session)
{
if ( m_evh )
m_evh->OnInsaneError ( IERR ( err ) );
return false;
}
lis_scan_parameters parameters; // DO NOT use a POINTER here.
err = session->get_scan_parameters(session, &parameters);
if (err)
{
if ( m_evh )
m_evh->OnInsaneError ( IERR ( err ) );
return false;
}
/*
* format is troublesome: always 0.
* But for LineArt, in the logs, sane_get_parameters() returns format=1 (v4l)
* or format=2 (remote).
*/
// lis_img_format format = parameters.format; // guaranteed, but always 0, even for LineArt.
const int lineArtFactor = (m_mode == string("LineArt") ? 3 : 1); // insane always outputs RGB.
int imageWidth = parameters.width; // pixels, guaranteed.
int imageHeight = parameters.height; // pixels, not guaranteed.
size_t imageSize = parameters.image_size * lineArtFactor; // bytes, not guaranteed.
InsaneWorkerEvent::ImageAttributes imageAttributes;
imageAttributes.size = imageSize;
imageAttributes.width = imageWidth;
imageAttributes.height = imageHeight;
// This file is valid raw pixels. It can be converted with rawtoppm and rawtopgm.
uint pageIndex = startIndex;
string filePath = makeFileName(pageIndex);
const int bytesPerRow = imageSize / imageHeight;
/*
* Needs confirmation.
* LineArt : 1 pixel = 1 bit - maximum resolution = 9600
* Gray : 1 pixel = 1 byte - maximum resolution = 2400
* Color : 1 pixel = 3 bytes - maximum resolution = 600
*
* A4, LineArt, 4800: 1690000620 bytes file OK (1.6 GiB).
* Above that resolution:
* std::bad_alloc - 1993034797 bytes (1.856 GiB) cannot be allocated
* for one row. System with 64 GB RAM.
*/
if (m_evh)
m_evh->OnStartScanSession(startIndex, imageAttributes);
do
{
ofstream outFile;
if (m_evh)
m_evh->OnPageStartScan(filePath, pageIndex, imageAttributes);
do
{
if (gs_cancelRequested)
{
session->cancel(session);
if (m_evh)
m_evh->OnSessionCancelled(filePath);
gs_cancelRequested = false;
return false;
}
try
{
char * readBytes = new char[bytesPerRow];
size_t bufferSize = bytesPerRow;
err = session->scan_read(session, readBytes, &bufferSize); // bufferSize is in/out.
if (LIS_IS_ERROR(err))
{
delete[] readBytes;
if (m_evh)
m_evh->OnSessionReadError(filePath);
return false;
}
/*
* bufferSize == 0 does not mean end_of_page or end of file.
* But we get here when end_of_feed is false while there's nothing more to read.
*/
if (bufferSize)
{
if (!outFile.is_open())
outFile.open(filePath, ios::binary | ios::trunc);
outFile.write((const char*) readBytes, bufferSize);
}
delete[] readBytes;
}
catch (std::bad_alloc& e)
{
m_rootSourceItem->close(m_rootSourceItem);
cout << "ABORT: " << e.what() << " - could not allocate " << bytesPerRow << " bytes." << endl;
if (m_evh)
m_evh->OnError("Insufficient system RAM.");
return false;
}
} while (!session->end_of_page(session));
/*
* end_of_feed may return false though there is no more to read.
* If outFile is not open, nothing was read; don't call OnPageEndScan.
*/
bool outFileWasOpen = outFile.is_open();
// It may not have been opened.
outFile.flush();
outFile.close();
if (m_evh && outFileWasOpen)
m_evh->OnPageEndScan(filePath, pageIndex, imageAttributes);
pageIndex += increment;
if (m_sourceItem->type != LIS_ITEM_ADF) // v4l also
break;
filePath = makeFileName(pageIndex);
} while( !session->end_of_feed(session));
if (m_evh)
m_evh->OnEndScanSession(pageIndex - increment, imageAttributes);
m_rootSourceItem->close(m_sourceItem); // Child.
m_rootSourceItem->close(m_rootSourceItem); // Root.
return true;
}
// Determine the page extent.
bool InsaneWorker::GetBottomRight(std::pair<double, double>& br)
{
int res = paperinit();
if (res != PAPER_OK)
{
const string msg = "Could not initialise the paper library.";
cerr << msg << endl;
if (m_evh)
m_evh->OnError(msg);
return false;
}
const paper * p = paperinfo(m_paperSize.c_str());
if (!p)
{
string msg = "Failed to find the requested paper; attempt to use a default paper size.";
cerr << 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;
if (m_evh)
m_evh->OnError(msg);
return false;
}
}
int unit = paperunit(p);
double conversionFactor = 1.0;
if (unit == PAPER_UNIT_MM)
conversionFactor = 1.0;
else if (unit == PAPER_UNIT_IN)
conversionFactor = 25.4;
else
{
const string msg = "The measurement unit of the paper size is not handled; using the default scanner sizes.";
cerr << msg << endl;
if (m_evh)
m_evh->OnError(msg);
return false;
}
br.first = paperwidth(p) * conversionFactor;
br.second = paperheight(p) * conversionFactor;
res = paperdone();
if (res != PAPER_OK)
{
const string msg = "Could not cleanly end the paper library.";
cerr << msg << endl;
if (m_evh)
m_evh->OnError(msg);
}
return true;
}
std::string InsaneWorker::ToLower(const std::string& input)
{
// We are dealing with single byte characters.
string output;
for ( char c : input )
output.append ( 1, std::tolower ( c ) );
return output;
}
void InsaneWorker::Cancel()
{
gs_cancelRequested = true;
}
std::pair<int, int> InsaneWorker::UpdateStartAndIncrement(const int startPageIndex, const int increment,
const bool adf, const bool doubleSided, const int total)
{
int newStartPageIndex = startPageIndex;
int newIncrement = increment;
int totalEven = total;
if (total % 2)
totalEven++;
// Scanning forward before max.
if ((increment > 0) && adf && doubleSided && (startPageIndex < (totalEven - 2)))
{
newIncrement = 2;
newStartPageIndex += newIncrement;
}
// Scanning forward at max.
else if ((increment > 0) && adf && doubleSided && (startPageIndex == (totalEven - 2)))
{
newIncrement = -2;
newStartPageIndex++;
}
// Scanning backward.
else if ((increment < 0) && adf && doubleSided && (startPageIndex <= (totalEven - 1)))
{
newIncrement = -2;
newStartPageIndex += newIncrement;
}
else
{
newIncrement = 1;
newStartPageIndex++;
}
std::pair<int, int> result = {newStartPageIndex, newIncrement};
return result;
}
// ----------------------------------------------------------------------------
InsaneWorkerEvent::InsaneWorkerEvent()
{}
InsaneWorkerEvent::~InsaneWorkerEvent()
{}
void InsaneWorkerEvent::OnInsaneError ( const std::string& message )
{}
void InsaneWorkerEvent::OnError ( const std::string& message )
{}
void InsaneWorkerEvent::OnSessionCancelled(const std::string& filePath)
{}
void InsaneWorkerEvent::OnSessionReadError(const std::string& filePath)
{}
void InsaneWorkerEvent::OnPageStartScan(const std::string& filePath,uint pageIndex, const ImageAttributes& imageAttributes)
{}
void InsaneWorkerEvent::OnPageEndScan(const std::string& filePath, uint pageIndex, const ImageAttributes& imageAttributes)
{}
void InsaneWorkerEvent::OnStartScanSession(uint pageIndex, const ImageAttributes& imageAttributes)
{}
void InsaneWorkerEvent::OnEndScanSession(uint pageIndex, const ImageAttributes& imageAttributes)
{}

View File

@@ -0,0 +1,128 @@
/*
* File: InsaneWorker.h
* Author: Saleem Edah-Tally - nmset@yandex.com
* License : CeCILL-C
* Copyright Saleem Edah-Tally - © 2024
*
* Created on 14 02 2024, 13:00
*/
#ifndef INSANEWORKER_H
#define INSANEWORKER_H
#include <libinsane/capi.h>
#include <string>
#include <vector>
class InsaneWorkerEvent;
/**
* Performs scanning to raw data as files.\n
* Input values are as obtained from the scanner widget:
*
* - device identifier - ex: sane:brother5:net1;dev0
* - source - ex: Automatic Document Feeder(centrally aligned)
* - source index - important for an ADF source
* - mode - Color, LineArt, anything else is Gray
* - resolution
* - paper size - only those handled by PoDoFo
*
* Output files are numbered with a padded suffix according to an increment that
* can be negative.
*/
/*
* All C objects must be initialised.
* Initialise to nullptr and not to NULL.
* Otherwise, unhealthy things are observed,
* like 'if (err)' always evaluated to true.
*/
class InsaneWorker
{
public:
InsaneWorker ( InsaneWorkerEvent* evh = nullptr );
~InsaneWorker();
struct DeviceDescriptor
{
std::string id;
std::string model;
std::string type;
std::string vendor;
std::string GetLabel()
{
return model + "-" + type + " [" + vendor + "]";
}
};
bool Init(); // Must be explicitly called, once only.
std::vector<DeviceDescriptor> GetDeviceDescriptors()
{
return m_devices;
}
int IsADF(const std::string& deviceId, int sourceChildIndex);;
std::string GetOptionPossibleValues(const std::string& deviceId,
const std::string& optionName, std::vector<std::string>& possible);
bool ConfigureDevice(const std::string& deviceId,
const std::string& source = "FlatBed", int sourceChildIndex = 0,
const std::string& mode = "Color", int resolution = 300,
const std::string& paperSize = "A4");
bool Scan(const std::string& dir, const std::string& basename,
int startIndex = 0, int padwidth = 2, int increment = 1); // increment can be negative
void Cancel();
InsaneWorkerEvent * GetEventHandler() const
{
return m_evh;
}
/**
* Update the start page index and increment to pass to ::Scan(), accounting
* for ADF and double sided scanning.
*/
std::pair<int, int> UpdateStartAndIncrement(const int startPageIndex, const int increment,
const bool adf, const bool doubleSided, const int total);
private:
lis_api * m_api = nullptr;
lis_item * m_rootSourceItem = nullptr; // May have child items.
lis_item * m_sourceItem = nullptr;
InsaneWorkerEvent * m_evh = nullptr;
std::vector<DeviceDescriptor> m_devices;
std::string m_deviceId;
std::string m_source = "FlatBed";
int m_sourceChildIndex = 0;
std::string m_mode = "Color";
int m_resolution = 300;
std::string m_paperSize = "A4";
bool GetBottomRight(std::pair<double, double>& br);
std::string ToLower(const std::string& input);
};
class InsaneWorkerEvent
{
public:
InsaneWorkerEvent();
virtual ~InsaneWorkerEvent();
struct ImageAttributes
{
size_t size = 0; // bytes
uint width = 0; // pixels
uint height = 0; // pixels
};
virtual void OnInsaneError ( const std::string& message );
virtual void OnError ( const std::string& message );
virtual void OnSessionCancelled(const std::string& filePath);
virtual void OnSessionReadError(const std::string& filePath);
virtual void OnPageStartScan(const std::string& filePath, uint pageIndex,
const ImageAttributes& imageAttributes);
virtual void OnPageEndScan(const std::string& filePath, uint pageIndex,
const ImageAttributes& imageAttributes);
virtual void OnStartScanSession(uint pageIndex, const ImageAttributes& imageAttributes);
virtual void OnEndScanSession(uint pageIndex, const ImageAttributes& imageAttributes);
};
#endif // INSANEWORKER_H

View File

@@ -0,0 +1,61 @@
// /*
// * File: PixelToImageWriter.cpp
// * Author: Saleem Edah-Tally - nmset@yandex.com
// * License : CeCILL-C
// * Copyright Saleem Edah-Tally - © 2025
// *
// * Created on 27 06 2025, 20:28
// */
#include "PixelToImageWriter.h"
#include <Common.h>
#include <fstream>
using namespace std;
bool PixelToImageWriter::Convert(const std::string& pixelFilePath,
int imageWidth, int imageHeight,
int outputFormat, wxImage * image)
{
UpdateExtensionsMap();
wxImage * outImage = image;
unique_ptr<wxImage> tmpImage;
if (!image)
{
tmpImage = make_unique<wxImage>();
outImage = tmpImage.get();
}
string raw;
ifstream ifs(pixelFilePath, ios::binary);
if (!ifs.good())
{
cerr << _("Failed to read raw file.") << endl;
return false;
}
raw.assign(istreambuf_iterator<char>(ifs), istreambuf_iterator<char>());
outImage->SetData((unsigned char*) raw.data(), imageWidth, imageHeight, true); // true +++
switch (outputFormat)
{
case PNG:
outImage->SaveFile(pixelFilePath + "." + Extensions[PNG], wxBITMAP_TYPE_PNG);
break;
case JPEG:
outImage->SaveFile(pixelFilePath + "." + Extensions[JPEG], wxBITMAP_TYPE_JPEG);
break;
case TIFF:
outImage->SaveFile(pixelFilePath + "." + Extensions[TIFF], wxBITMAP_TYPE_TIFF);
break;
case PNM:
outImage->SaveFile(pixelFilePath + "." + Extensions[PNM], wxBITMAP_TYPE_PNM);
break;
case PDF:
break;
default:
cerr << _("Unhandled output image format.") << endl;
return false;
};
return true;
}

View File

@@ -0,0 +1,28 @@
// /*
// * File: PixelToImageWriter.h
// * Author: Saleem Edah-Tally - nmset@yandex.com
// * License : CeCILL-C
// * Copyright Saleem Edah-Tally - © 2025
// *
// * Created on 27 06 2025, 20:28
// */
#ifndef PIXELTOIMAGEWRITER_H
#define PIXELTOIMAGEWRITER_H
#include "Common.h"
#include <string>
#include <wx/wx.h>
/**
* Create an image file from a raw scanned file.\n
*/
class PixelToImageWriter
{
public:
static bool Convert(const std::string& pixelFilePath,
int imageWidth, int imageHeight, int outputFormat = PNG,
wxImage * image = nullptr);
};
#endif // PIXELTOIMAGEWRITER_H

View File

@@ -0,0 +1,83 @@
/*
* File: PixelToPdfWriter.cpp
* Author: Saleem Edah-Tally - nmset@yandex.com
* License : CeCILL-C
* Copyright Saleem Edah-Tally - © 2025
*
* Created on 14 06 2025, 17:15
*/
#include "PixelToPdfWriter.h"
#include <iostream>
#include <memory>
using namespace std;
using namespace PoDoFo;
PixelToPdfWriter::PixelToPdfWriter()
{
m_pageSizes["A0"] = PoDoFo::PdfPageSize::A0;
m_pageSizes["A1"] = PoDoFo::PdfPageSize::A1;
m_pageSizes["A2"] = PoDoFo::PdfPageSize::A2;
m_pageSizes["A3"] = PoDoFo::PdfPageSize::A3;
m_pageSizes["A4"] = PoDoFo::PdfPageSize::A4;
m_pageSizes["A5"] = PoDoFo::PdfPageSize::A5;
m_pageSizes["A6"] = PoDoFo::PdfPageSize::A6;
m_pageSizes["Letter"] = PoDoFo::PdfPageSize::Letter;
m_pageSizes["Legal"] = PoDoFo::PdfPageSize::Legal;
m_pageSizes["Tabloid"] = PoDoFo::PdfPageSize::Tabloid;
}
bool PixelToPdfWriter::AddPageAt(const std::string& pixelFile, uint width, uint height, uint index,
PoDoFo::PdfPageSize pageSize, PoDoFo::PdfColorSpace)
{
try
{
Rect pageRect = PdfPage::CreateStandardPageSize(pageSize);
PdfPage& page = m_doc.GetPages().CreatePageAt(index, pageRect);
PdfImageInfo info;
info.Width = width; // Must be known beforehand and must be exact.
info.Height = height;
info.BitsPerComponent = 8;
info.ColorSpace = PdfColorSpace::DeviceRGB; // Is always RGB from libinsane.
ifstream ifs(pixelFile, ios::binary);
string content;
content.assign(istreambuf_iterator<char>(ifs), istreambuf_iterator<char>());
bufferview bv(content);
const uint pageNumber = m_doc.GetPages().GetCount();
std::unique_ptr<PdfImage> image = m_doc.CreateImage("Page_" + to_string(pageNumber)); // No space.
image->SetDataRaw(bv, info); // OK for pixel file, including LineArt
double scale = std::min(pageRect.Width / image->GetWidth(), pageRect.Height / image->GetHeight());
PdfPainter painter;
painter.SetCanvas(page);
painter.DrawImage(*(image), 0.0, 0.0, scale, scale);
painter.FinishDrawing();
}
catch (PdfError& e)
{
cerr << e.ErrorMessage(e.GetCode()) << endl << e.what() << endl;
return false;
}
return true;
}
void PixelToPdfWriter::Save(const std::string& pdfFile)
{
m_doc.Save(pdfFile);
}
uint PixelToPdfWriter::GetNumberOfPages() const
{
return m_doc.GetPages().GetCount();
}
void PixelToPdfWriter::RemovePageAt(uint index)
{
m_doc.GetPages().RemovePageAt(index);
}

View File

@@ -0,0 +1,49 @@
/*
* File: PixelToPdfWriter.h
* Author: Saleem Edah-Tally - nmset@yandex.com
* License : CeCILL-C
* Copyright Saleem Edah-Tally - © 2025
*
* Created on 14 06 2025, 17:15
*/
#ifndef PIXELTOPDFWRITER_H
#define PIXELTOPDFWRITER_H
#include <string>
#include <map>
#include <podofo/podofo.h>
/**
* 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.
*/
class PixelToPdfWriter
{
public:
typedef std::map<std::string, PoDoFo::PdfPageSize> PageSizeMap;
PixelToPdfWriter();
bool AddPageAt(const std::string& pixelFile, uint width, uint height, uint index,
PoDoFo::PdfPageSize pageSize = PoDoFo::PdfPageSize::A4,
PoDoFo::PdfColorSpace = PoDoFo::PdfColorSpace::DeviceRGB /*Unused*/);
void Save(const std::string& pdfFile);
uint GetNumberOfPages() const;
void RemovePageAt(uint index);
PoDoFo::PdfPageSize GetPageSize(const std::string& literal)
{
return m_pageSizes[literal]; // 0 if not found, PdfPageSize::Unknown.
}
PageSizeMap GetPageSizes() const
{
return m_pageSizes;
}
private:
PoDoFo::PdfMemDocument m_doc;
PageSizeMap m_pageSizes;
};
#endif // PIXELTOPDFWRITER_H

View File

@@ -0,0 +1,180 @@
/////////////////////////////////////////////////////////////////////////////
// Name: InsaneWidgetcpp.cpp
// Purpose:
// Author: Saleem EDAH-TALLY
// Modified by:
// Created: dim. 15 juin 2025 19:37:17
// RCS-ID:
// Copyright: Copyright Saleem EDAH-TALLY. All rights reserved.
// Licence: CeCILL-C
/////////////////////////////////////////////////////////////////////////////
// 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 "InsaneWidget.h"
////@begin XPM images
////@end XPM images
/*
* InsaneWidget type definition
*/
IMPLEMENT_DYNAMIC_CLASS( InsaneWidget, wxPanel )
/*
* InsaneWidget event table definition
*/
BEGIN_EVENT_TABLE( InsaneWidget, wxPanel )
////@begin InsaneWidget event table entries
////@end InsaneWidget event table entries
END_EVENT_TABLE()
/*
* InsaneWidget constructors
*/
InsaneWidget::InsaneWidget()
{
Init();
}
InsaneWidget::InsaneWidget( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style )
{
Init();
Create(parent, id, pos, size, style);
}
/*
* InsaneWidget creator
*/
bool InsaneWidget::Create( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style )
{
////@begin InsaneWidget creation
SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY);
wxPanel::Create( parent, id, pos, size, style );
CreateControls();
if (GetSizer())
{
GetSizer()->SetSizeHints(this);
}
Centre();
////@end InsaneWidget creation
return true;
}
/*
* InsaneWidget destructor
*/
InsaneWidget::~InsaneWidget()
{
////@begin InsaneWidget destruction
////@end InsaneWidget destruction
}
/*
* Member initialisation
*/
void InsaneWidget::Init()
{
////@begin InsaneWidget member initialisation
lblNewDoc = NULL;
txtNewDoc = NULL;
btnScan = NULL;
////@end InsaneWidget member initialisation
}
/*
* Control creation for InsaneWidget
*/
void InsaneWidget::CreateControls()
{
////@begin InsaneWidget content construction
InsaneWidget* itemPanel1 = this;
wxBoxSizer* itemBoxSizer2 = new wxBoxSizer(wxVERTICAL);
itemPanel1->SetSizer(itemBoxSizer2);
wxBoxSizer* itemBoxSizer1 = new wxBoxSizer(wxHORIZONTAL);
itemBoxSizer2->Add(itemBoxSizer1, 0, wxGROW|wxALL, 5);
lblNewDoc = new wxStaticText( itemPanel1, ID_NewDoc_LBL, _("New"), wxDefaultPosition, wxDefaultSize, 0 );
if (InsaneWidget::ShowToolTips())
lblNewDoc->SetToolTip(_("'Right' click to define a scan project."));
itemBoxSizer1->Add(lblNewDoc, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
txtNewDoc = new wxTextCtrl( itemPanel1, ID_NewDoc_TXT, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY );
if (InsaneWidget::ShowToolTips())
txtNewDoc->SetToolTip(_("Full path to destination file without the extension; it is determined by the output type."));
itemBoxSizer1->Add(txtNewDoc, 1, wxGROW|wxALL, 5);
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."));
itemBoxSizer1->Add(btnScan, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
////@end InsaneWidget content construction
}
/*
* Should we show tooltips?
*/
bool InsaneWidget::ShowToolTips()
{
return true;
}
/*
* Get bitmap resources
*/
wxBitmap InsaneWidget::GetBitmapResource( const wxString& name )
{
// Bitmap retrieval
////@begin InsaneWidget bitmap retrieval
wxUnusedVar(name);
return wxNullBitmap;
////@end InsaneWidget bitmap retrieval
}
/*
* Get icon resources
*/
wxIcon InsaneWidget::GetIconResource( const wxString& name )
{
// Icon retrieval
////@begin InsaneWidget icon retrieval
wxUnusedVar(name);
return wxNullIcon;
////@end InsaneWidget icon retrieval
}

View File

@@ -0,0 +1,97 @@
/////////////////////////////////////////////////////////////////////////////
// Name: InsaneWidget.h
// Purpose:
// Author: Saleem EDAH-TALLY
// Modified by:
// Created: dim. 15 juin 2025 19:37:17
// RCS-ID:
// Copyright: Copyright Saleem EDAH-TALLY. All rights reserved.
// Licence: CeCILL-C
/////////////////////////////////////////////////////////////////////////////
#ifndef _INSANEWIDGET_H_
#define _INSANEWIDGET_H_
/*!
* Includes
*/
////@begin includes
////@end includes
/*!
* Forward declarations
*/
////@begin forward declarations
////@end forward declarations
/*!
* Control identifiers
*/
////@begin control identifiers
#define ID_INSANEWIDGET 10000
#define ID_NewDoc_LBL 10014
#define ID_NewDoc_TXT 10005
#define ID_Scan_BTN 10006
#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_POSITION wxDefaultPosition
////@end control identifiers
/*!
* InsaneWidget class declaration
*/
class InsaneWidget: public wxPanel
{
DECLARE_DYNAMIC_CLASS( InsaneWidget )
DECLARE_EVENT_TABLE()
public:
/// Constructors
InsaneWidget();
InsaneWidget( wxWindow* parent, wxWindowID id = SYMBOL_INSANEWIDGET_IDNAME, const wxPoint& pos = SYMBOL_INSANEWIDGET_POSITION, const wxSize& size = SYMBOL_INSANEWIDGET_SIZE, long style = SYMBOL_INSANEWIDGET_STYLE );
/// Creation
bool Create( wxWindow* parent, wxWindowID id = SYMBOL_INSANEWIDGET_IDNAME, const wxPoint& pos = SYMBOL_INSANEWIDGET_POSITION, const wxSize& size = SYMBOL_INSANEWIDGET_SIZE, long style = SYMBOL_INSANEWIDGET_STYLE );
/// Destructor
~InsaneWidget();
/// Initialises member variables
void Init();
/// Creates the controls and sizers
void CreateControls();
////@begin InsaneWidget event handler declarations
////@end InsaneWidget event handler declarations
////@begin InsaneWidget member function declarations
/// Retrieves bitmap resources
wxBitmap GetBitmapResource( const wxString& name );
/// Retrieves icon resources
wxIcon GetIconResource( const wxString& name );
////@end InsaneWidget member function declarations
/// Should we show tooltips?
static bool ShowToolTips();
////@begin InsaneWidget member variables
wxStaticText* lblNewDoc;
wxTextCtrl* txtNewDoc;
wxButton* btnScan;
////@end InsaneWidget member variables
};
#endif
// _INSANEWIDGET_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,237 @@
/////////////////////////////////////////////////////////////////////////////
// Name: ScannerWidget.cpp
// Purpose:
// Author: Saleem EDAH-TALLY
// Modified by:
// Created: dim. 15 juin 2025 19:39:52
// RCS-ID:
// Copyright: Copyright Saleem EDAH-TALLY. All rights reserved.
// Licence: CeCILL-C
/////////////////////////////////////////////////////////////////////////////
// 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 "ScannerWidget.h"
////@begin XPM images
////@end XPM images
/*
* ScannerWidget type definition
*/
IMPLEMENT_DYNAMIC_CLASS( ScannerWidget, wxPanel )
/*
* ScannerWidget event table definition
*/
BEGIN_EVENT_TABLE( ScannerWidget, wxPanel )
////@begin ScannerWidget event table entries
////@end ScannerWidget event table entries
END_EVENT_TABLE()
/*
* ScannerWidget constructors
*/
ScannerWidget::ScannerWidget()
{
Init();
}
ScannerWidget::ScannerWidget( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style )
{
Init();
Create(parent, id, pos, size, style);
}
/*
* ScannerWidget creator
*/
bool ScannerWidget::Create( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style )
{
////@begin ScannerWidget creation
SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY);
wxPanel::Create( parent, id, pos, size, style );
CreateControls();
if (GetSizer())
{
GetSizer()->SetSizeHints(this);
}
Centre();
////@end ScannerWidget creation
return true;
}
/*
* ScannerWidget destructor
*/
ScannerWidget::~ScannerWidget()
{
////@begin ScannerWidget destruction
////@end ScannerWidget destruction
}
/*
* Member initialisation
*/
void ScannerWidget::Init()
{
////@begin ScannerWidget member initialisation
lblImageType = NULL;
cmbOutputType = NULL;
btnRefreshDevices = NULL;
lblDevices = NULL;
cmbDevices = NULL;
lblSource = NULL;
cmbSource = NULL;
lblMode = NULL;
cmbMode = NULL;
lblResolution = NULL;
cmbResolution = NULL;
lblPaperSize = NULL;
cmbPaperSize = NULL;
////@end ScannerWidget member initialisation
}
/*
* Control creation for ScannerWidget
*/
void ScannerWidget::CreateControls()
{
////@begin ScannerWidget content construction
ScannerWidget* itemPanel1 = this;
wxBoxSizer* itemBoxSizer1 = new wxBoxSizer(wxVERTICAL);
itemPanel1->SetSizer(itemBoxSizer1);
wxFlexGridSizer* itemFlexGridSizer2 = new wxFlexGridSizer(0, 2, 0, 0);
itemBoxSizer1->Add(itemFlexGridSizer2, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5);
lblImageType = new wxStaticText( itemPanel1, wxID_STATIC_IMAGE_TYPE, _("Format:"), wxDefaultPosition, wxDefaultSize, 0 );
itemFlexGridSizer2->Add(lblImageType, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
wxArrayString cmbOutputTypeStrings;
cmbOutputType = new wxComboBox( itemPanel1, ID_COMBOBOX_IMAGE_TYPE, wxEmptyString, wxDefaultPosition, wxDefaultSize, cmbOutputTypeStrings, wxCB_READONLY );
if (ScannerWidget::ShowToolTips())
cmbOutputType->SetToolTip(_("Output document format"));
itemFlexGridSizer2->Add(cmbOutputType, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
wxBoxSizer* itemBoxSizer5 = new wxBoxSizer(wxHORIZONTAL);
itemFlexGridSizer2->Add(itemBoxSizer5, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5);
btnRefreshDevices = new wxButton( itemPanel1, ID_BUTTON_REFRESH_DEVICES, wxGetTranslation(wxString() + (wxChar) 0x21BB), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT );
if (ScannerWidget::ShowToolTips())
btnRefreshDevices->SetToolTip(_("Refresh available devices"));
itemBoxSizer5->Add(btnRefreshDevices, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
lblDevices = new wxStaticText( itemPanel1, wxID_STATIC_DEVICES, _("Devices:"), wxDefaultPosition, wxDefaultSize, 0 );
itemBoxSizer5->Add(lblDevices, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
wxArrayString cmbDevicesStrings;
cmbDevices = new wxComboBox( itemPanel1, ID_COMBOBOX_Devices, wxEmptyString, wxDefaultPosition, wxDefaultSize, cmbDevicesStrings, wxCB_READONLY );
if (ScannerWidget::ShowToolTips())
cmbDevices->SetToolTip(_("Available devices"));
itemFlexGridSizer2->Add(cmbDevices, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxALL, 5);
lblSource = new wxStaticText( itemPanel1, wxID_STATIC_SOURCE, _("Source:"), wxDefaultPosition, wxDefaultSize, 0 );
itemFlexGridSizer2->Add(lblSource, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
wxArrayString cmbSourceStrings;
cmbSource = new wxComboBox( itemPanel1, ID_COMBOBOX_SOURCE, wxEmptyString, wxDefaultPosition, wxDefaultSize, cmbSourceStrings, wxCB_READONLY );
if (ScannerWidget::ShowToolTips())
cmbSource->SetToolTip(_("Scan source"));
itemFlexGridSizer2->Add(cmbSource, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxALL, 5);
lblMode = new wxStaticText( itemPanel1, wxID_STATIC_MODE, _("Mode:"), wxDefaultPosition, wxDefaultSize, 0 );
itemFlexGridSizer2->Add(lblMode, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
wxArrayString cmbModeStrings;
cmbMode = new wxComboBox( itemPanel1, ID_COMBOBOX_MODE, wxEmptyString, wxDefaultPosition, wxDefaultSize, cmbModeStrings, wxCB_READONLY );
if (ScannerWidget::ShowToolTips())
cmbMode->SetToolTip(_("Scan mode"));
itemFlexGridSizer2->Add(cmbMode, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxALL, 5);
lblResolution = new wxStaticText( itemPanel1, wxID_STATIC_RESOLUTION, _("Resolution:"), wxDefaultPosition, wxDefaultSize, 0 );
itemFlexGridSizer2->Add(lblResolution, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
wxArrayString cmbResolutionStrings;
cmbResolution = new wxComboBox( itemPanel1, ID_COMBOBOX_RESOLUTION, wxEmptyString, wxDefaultPosition, wxDefaultSize, cmbResolutionStrings, wxCB_READONLY );
if (ScannerWidget::ShowToolTips())
cmbResolution->SetToolTip(_("Scan resolution"));
itemFlexGridSizer2->Add(cmbResolution, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
lblPaperSize = new wxStaticText( itemPanel1, wxID_STATIC, _("Paper size:"), wxDefaultPosition, wxDefaultSize, 0 );
itemFlexGridSizer2->Add(lblPaperSize, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5);
wxArrayString cmbPaperSizeStrings;
cmbPaperSize = new wxComboBox( itemPanel1, ID_COMBOBOX, wxEmptyString, wxDefaultPosition, wxDefaultSize, cmbPaperSizeStrings, wxCB_READONLY );
itemFlexGridSizer2->Add(cmbPaperSize, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
itemFlexGridSizer2->AddGrowableCol(1);
////@end ScannerWidget content construction
}
/*
* Should we show tooltips?
*/
bool ScannerWidget::ShowToolTips()
{
return true;
}
/*
* Get bitmap resources
*/
wxBitmap ScannerWidget::GetBitmapResource( const wxString& name )
{
// Bitmap retrieval
////@begin ScannerWidget bitmap retrieval
wxUnusedVar(name);
return wxNullBitmap;
////@end ScannerWidget bitmap retrieval
}
/*
* Get icon resources
*/
wxIcon ScannerWidget::GetIconResource( const wxString& name )
{
// Icon retrieval
////@begin ScannerWidget icon retrieval
wxUnusedVar(name);
return wxNullIcon;
////@end ScannerWidget icon retrieval
}

View File

@@ -0,0 +1,116 @@
/////////////////////////////////////////////////////////////////////////////
// Name: ScannerWidget.h
// Purpose:
// Author: Saleem EDAH-TALLY
// Modified by:
// Created: dim. 15 juin 2025 19:39:52
// RCS-ID:
// Copyright: Copyright Saleem EDAH-TALLY. All rights reserved.
// Licence: CeCILL-C
/////////////////////////////////////////////////////////////////////////////
#ifndef _SCANNERWIDGET_H_
#define _SCANNERWIDGET_H_
/*!
* Includes
*/
////@begin includes
////@end includes
/*!
* Forward declarations
*/
////@begin forward declarations
////@end forward declarations
/*!
* Control identifiers
*/
////@begin control identifiers
#define ID_SCANNERWIDGET 10000
#define wxID_STATIC_IMAGE_TYPE 10031
#define ID_COMBOBOX_IMAGE_TYPE 10007
#define ID_BUTTON_REFRESH_DEVICES 10006
#define wxID_STATIC_DEVICES 10029
#define ID_COMBOBOX_Devices 10001
#define wxID_STATIC_SOURCE 10028
#define ID_COMBOBOX_SOURCE 10002
#define wxID_STATIC_MODE 10027
#define ID_COMBOBOX_MODE 10003
#define wxID_STATIC_RESOLUTION 10030
#define ID_COMBOBOX_RESOLUTION 10004
#define ID_COMBOBOX 10005
#define SYMBOL_SCANNERWIDGET_STYLE wxTAB_TRAVERSAL
#define SYMBOL_SCANNERWIDGET_TITLE _("ScannerWidget")
#define SYMBOL_SCANNERWIDGET_IDNAME ID_SCANNERWIDGET
#define SYMBOL_SCANNERWIDGET_SIZE wxSize(400, 300)
#define SYMBOL_SCANNERWIDGET_POSITION wxDefaultPosition
////@end control identifiers
/*!
* ScannerWidget class declaration
*/
class ScannerWidget: public wxPanel
{
DECLARE_DYNAMIC_CLASS( ScannerWidget )
DECLARE_EVENT_TABLE()
public:
/// Constructors
ScannerWidget();
ScannerWidget( wxWindow* parent, wxWindowID id = SYMBOL_SCANNERWIDGET_IDNAME, const wxPoint& pos = SYMBOL_SCANNERWIDGET_POSITION, const wxSize& size = SYMBOL_SCANNERWIDGET_SIZE, long style = SYMBOL_SCANNERWIDGET_STYLE );
/// Creation
bool Create( wxWindow* parent, wxWindowID id = SYMBOL_SCANNERWIDGET_IDNAME, const wxPoint& pos = SYMBOL_SCANNERWIDGET_POSITION, const wxSize& size = SYMBOL_SCANNERWIDGET_SIZE, long style = SYMBOL_SCANNERWIDGET_STYLE );
/// Destructor
~ScannerWidget();
/// Initialises member variables
void Init();
/// Creates the controls and sizers
void CreateControls();
////@begin ScannerWidget event handler declarations
////@end ScannerWidget event handler declarations
////@begin ScannerWidget member function declarations
/// Retrieves bitmap resources
wxBitmap GetBitmapResource( const wxString& name );
/// Retrieves icon resources
wxIcon GetIconResource( const wxString& name );
////@end ScannerWidget member function declarations
/// Should we show tooltips?
static bool ShowToolTips();
////@begin ScannerWidget member variables
wxStaticText* lblImageType;
wxComboBox* cmbOutputType;
wxButton* btnRefreshDevices;
wxStaticText* lblDevices;
wxComboBox* cmbDevices;
wxStaticText* lblSource;
wxComboBox* cmbSource;
wxStaticText* lblMode;
wxComboBox* cmbMode;
wxStaticText* lblResolution;
wxComboBox* cmbResolution;
wxStaticText* lblPaperSize;
wxComboBox* cmbPaperSize;
////@end ScannerWidget member variables
};
#endif
// _SCANNERWIDGET_H_

View File

@@ -0,0 +1,412 @@
/*
* File: XInsaneWidget.cpp
* Author: Saleem Edah-Tally - nmset@yandex.com
* License : CeCILL-C
* Copyright Saleem Edah-Tally - © 2025
*
* Created on 15 06 2025, 21:24
*/
#include "XInsaneWidget.h"
#include <PopupTransientWindow.h>
#include <MiscTools.h>
#include "PixelToImageWriter.h"
#include "PixelToPdfWriter.h"
#include <Common.h>
using namespace std;
// When device discovery in a detached thread completes.
// ----------------------------------------------------------------------------
class BackgroundScannerDiscoveryEVH : public BackgroundScannerDiscoveryEvent
{
public:
BackgroundScannerDiscoveryEVH(XInsaneWidget * owner)
{
m_owner = owner;
}
virtual ~BackgroundScannerDiscoveryEVH()
{}
void OnDone() override
{
if (!m_owner)
return;
m_owner->CallAfter(&XInsaneWidget::EnableScanButton, true);
}
private:
wxWeakRef<XInsaneWidget> m_owner = nullptr;
};
// ----------------------------------------------------------------------------
class ScanProjectHandler : public InsaneWorkerEvent
{
public:
ScanProjectHandler(XInsaneWidget * owner, TimeredStatusBar * sb = nullptr)
: InsaneWorkerEvent(), m_owner(owner), m_sb (sb)
{
}
/*
* mode, 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.
*/
void SetMode(const string& mode)
{
if (m_pixelFiles.size())
return;
m_mode = mode;
}
void SetOutputType(int outputType)
{
if (m_pixelFiles.size())
return;
m_outputType = outputType;
}
void SetADF(bool adf)
{
if (m_pixelFiles.size())
return;
m_adf = adf;
if (m_adf && m_doubleSided)
m_startPageIndex = -2;
else
m_startPageIndex = -1;
}
void SetDoubleSided(bool doubleSided)
{
if (m_pixelFiles.size())
return;
m_doubleSided = doubleSided;
if (m_adf && m_doubleSided)
m_startPageIndex = -2;
else
m_startPageIndex = -1;
}
void SetTotalNumberOfSides(uint total)
{
if (m_pixelFiles.size())
return;
m_total = total;
m_totalEven = total;
if (total % 2)
m_totalEven = total + 1;
}
void SetPaperSize(const wxString& paperSize)
{
if (m_pixelFiles.size())
return;
m_paperSize = paperSize;
}
std::pair<int, int> GetStartAndIncrement(InsaneWorker * insaneWorker)
{
wxASSERT_MSG(insaneWorker != nullptr, "insaneWorker is NULL.");
std::pair<int, int> startAndIncrement = insaneWorker->UpdateStartAndIncrement(m_startPageIndex, m_increment,
m_adf, m_doubleSided, m_total);
m_startPageIndex = startAndIncrement.first;
m_increment = startAndIncrement.second;
return startAndIncrement;
}
void OnInsaneError ( const std::string& message ) override
{
cerr << message << endl;
Reset();
MiscTools::MessageBox(_("A scan library error occurred."), true);
}
void OnError ( const std::string& message ) override
{
cerr << message << endl;
Reset();
MiscTools::MessageBox(_("A general error occurred."), true);
}
void OnSessionReadError(const std::string & filePath) override
{
const wxString msg = _("A session read error occurred.");
cerr << msg << endl;
Reset();
MiscTools::MessageBox(msg, true);
}
void OnSessionCancelled(const std::string & filePath) override
{
const wxString msg = _("Session cancelled.");
Reset();
MiscTools::MessageBox(msg, true);
}
// Every time a page is fully scanned.
void OnPageEndScan(const std::string & filePath, uint pageIndex,
const ImageAttributes& imageAttributes) override
{
m_startPageIndex = pageIndex;
m_pixelFiles[pageIndex] = {filePath, imageAttributes};
auto informProgress = [&] ()
{
if (!m_sb || (m_total == 1))
return;
int max = (m_adf && m_doubleSided) ? m_totalEven : m_total;
wxString progress = to_string(m_pixelFiles.size()) + "/" + to_string(max);
wxString info = _("Scanning: ");
if (m_increment == 2)
info = _("Front face: ");
else if (m_increment == -2)
info = _("Back face: ");
wxString msg = info + progress;
if (m_increment == 2 && ((max / 2) == (m_pixelFiles.size())))
{
wxString upperBoundMessage = _(". Turn the whole stack of pages.");
msg += upperBoundMessage;
}
m_sb->SetStatusText(msg);
};
if (m_outputType != PDF)
{
// Convert pixel file to PNG using netpbm.
if (!PixelToImageWriter::Convert(filePath, imageAttributes.width, imageAttributes.height, m_outputType))
{
const wxString msg = _("Failed to create output image.");
cerr << msg << endl;
if (m_sb)
m_sb->SetTransientText(msg);
}
wxRemoveFile(filePath);
informProgress();
}
else
{
// Append or insert PDF page from pixel file using PoDoFo.
if (!m_pixelToPdfWriter.get())
m_pixelToPdfWriter = std::unique_ptr<PixelToPdfWriter> (new PixelToPdfWriter());
uint index = (m_increment > 0) // 1 or 2
? m_pixelToPdfWriter->GetNumberOfPages() // Append.
: m_totalEven - m_pixelToPdfWriter->GetNumberOfPages(); // Insert.
PoDoFo::PdfPageSize pageSize = m_pixelToPdfWriter->GetPageSize(m_paperSize.ToStdString());
if (pageSize == PoDoFo::PdfPageSize::Unknown)
{
const wxString msg = _("Wrong paper size: ") + m_paperSize + _("; using A4.");
cerr << msg << endl;
pageSize = PoDoFo::PdfPageSize::A4;
}
if (!m_pixelToPdfWriter->AddPageAt(filePath, imageAttributes.width, imageAttributes.height, index, pageSize))
{
const wxString msg = _("Failed to add page to PDF document.");
cerr << msg << endl;
if (m_sb)
m_sb->SetTransientText(msg);
}
wxRemoveFile(filePath);
informProgress();
}
}
void OnEndScanSession(uint pageIndex, const ImageAttributes & imageAttributes) override
{
int max = (m_adf && m_doubleSided) ? m_totalEven : m_total;
// A special case is made for (total == 1): double-sided does not have meaning.
if (m_total == 1)
max = m_total;
if (m_pixelFiles.size() >= max)
{
// All pages have been scanned, accounting for ADF and double-sided.
if (m_outputType != PDF)
{
// Remove the last image if an odd number of pages was requested.
if (m_increment != 1 && (m_total != m_totalEven))
{
const string filePath = std::get<0> (m_pixelFiles.rbegin()->second) + "." + Extensions[m_outputType];
wxRemoveFile(filePath);
}
}
else
{
// Remove the last page if an odd number of pages was requested.
if (m_increment != 1 && (m_total != m_totalEven))
{
uint lastIndex = m_pixelFiles.end()->first;
m_pixelToPdfWriter->RemovePageAt(lastIndex - 1);
}
const string filePath = m_owner->txtNewDoc->GetValue().ToStdString() + "." + Extensions[m_outputType];
m_pixelToPdfWriter->Save(filePath);
m_pixelToPdfWriter.reset((new PixelToPdfWriter())); // For next scan project.
}
Reset();
m_owner->txtNewDoc->Clear();
if (m_sb)
{
const wxString msg = _("Finished.");
m_sb->SetTransientText(msg);
}
}
}
void Reset()
{
// Don't reset calculated variables that depend on widgets.
m_startPageIndex = 0;
m_increment = 1;
m_totalEven = 0;
m_pixelFiles.clear();
}
private:
wxWeakRef<XInsaneWidget> m_owner = nullptr;
wxWeakRef<TimeredStatusBar> m_sb = nullptr;
std::unique_ptr<PixelToPdfWriter> m_pixelToPdfWriter;
PixelFilesMap m_pixelFiles;
string m_mode = "Color";
uint m_outputType = PDF;
wxString m_paperSize = _T("A4");
bool m_adf = false;
bool m_doubleSided = false;
int m_total = 0;
int m_totalEven = 0;
int m_startPageIndex = 0;
int m_increment = 1;
};
// ----------------------------------------------------------------------------
XInsaneWidget::XInsaneWidget(wxWindow* parent, TimeredStatusBar * sb, wxConfig * config, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: InsaneWidget(parent, id, pos, size, style)
{
UpdateExtensionsMap();
m_config = config;
m_sb = sb;
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() ));
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 );
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();
}
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 )
{
/*
* 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));
PopupTransientWindow * ptw = m_pageStack->CreatePopup();
if ( !ptw )
{
evt.Skip();
return;
}
wxCheckBox * cb = m_pageStack->AddCheckBox (_("Double sided:"),_T("/Scanner/DoubleSided") );
cb->SetToolTip (_("Scan all front faces first, then all back faces in reverse order.") );
wxSpinCtrl * spn = m_pageStack->AddSpinCtrl (_("Total:"),_T("/Scanner/Total") );
spn->SetRange ( 1, 50 );
spn->SetToolTip (_("Total number of sides to scan (not total number of sheets).") );
m_pageStack->ShowPopup();
evt.Skip();
}
void XInsaneWidget::OnTxtNewDocKeyPressed ( wxKeyEvent& evt )
{
if ( evt.GetKeyCode() == WXK_BACK )
{
txtNewDoc->Clear();
}
evt.Skip();
}
// Show the scanner widget.
void XInsaneWidget::OnBtnScanRightClick ( wxMouseEvent& evt )
{
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() );
evt.Skip();
}
// Start scanning.
void XInsaneWidget::OnBtnScanClick ( wxMouseEvent& evt )
{
const wxString dest = txtNewDoc->GetValue();
if (dest.IsEmpty())
{
MiscTools::MessageBox(_("Destination file missing."), true);
evt.Skip();
return;
}
const uint outputType = m_scannerWidget->GetScannerOutputType();
bool adf = false;
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");
}
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;
if ( !m_scannerWidget->GetScannerResolution().IsEmpty() )
{
resolution = std::stoi ( m_scannerWidget->GetScannerResolution().ToStdString() );
}
int sourceIndex = sourceAttributes.first == wxNOT_FOUND
? 0 : sourceAttributes.first;
adf = m_insaneWorker->IsADF(deviceId, sourceIndex);
if (m_insaneWorker->ConfigureDevice(deviceId, source, sourceIndex, mode, resolution, paperSize.ToStdString()))
{
m_scanProject->SetADF(adf);
m_scanProject->SetMode(mode);
m_scanProject->SetOutputType(outputType);
m_scanProject->SetPaperSize(paperSize);
m_scanProject->SetDoubleSided(doubleSided);
m_scanProject->SetTotalNumberOfSides(total);
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);
}
evt.Skip();
}
void XInsaneWidget::ResetScanProject()
{
if (m_scanProject)
m_scanProject->Reset();
}
void XInsaneWidget::CancelScanning()
{
if (m_insaneWorker)
m_insaneWorker->Cancel();
}
void XInsaneWidget::EnableScanButton(bool enable)
{
btnScan->Enable(enable);
}

View File

@@ -0,0 +1,81 @@
/*
* File: XInsaneWidget.h
* Author: Saleem Edah-Tally - nmset@yandex.com
* License : CeCILL-C
* Copyright Saleem Edah-Tally - © 2025
*
* Created on 15 06 2025, 21:24
*/
#ifndef XINSANEWIDGET_H
#define XINSANEWIDGET_H
#include <wx/wx.h>
#include <wx/config.h>
#include <wx/popupwin.h>
#include <InsaneWidget.h>
#include <InsaneWorker.h>
#include <XScannerWidget.h>
#include <TimeredStatusBar.h>
#include <ConfigEditorPopup.h>
#include <memory>
class BackgroundScannerDiscoveryEVH;
class ScanProjectHandler; // An event handler extending InsaneWorkerEvent.
// 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.
*
* Label:
* - Right click: define the number of pages to scan and double-sided scanning.
* - Number of pages: it's the number of sides, not the number of sheets.
* - Backface: if an automatic document feeder is used. If 5 pages are to be
* scanned, feed 3 pages on front face, turn the whole stack including the
* 6th page to scan the backfaces; the last page will be discarded.
* With a feeder, the 'double-sided' option can be off if all backfaces
* are blank.
* With a flatbed scanner, the 'double-sided' should typically be off and
* the pages scanned in their logical order.
*
* Text box:
* - shows the path and the basename of the files to create. This must be set
* by the application in any conveniant manner.
* Pressing the backspace key clears the text box.
*
* Button:
* - Right click: shows the scanner widget.
* - Left click: starts scanning.
*/
class XInsaneWidget : public InsaneWidget
{
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 );
void ResetScanProject();
void CancelScanning(); // Not tested, probably doesn't work as intended.
void EnableScanButton(bool enable); // For CallAfter.
private:
wxConfig * m_config;
wxWeakRef<TimeredStatusBar> m_sb;
// 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;
// Available devices and minimal options.
std::unique_ptr<XScannerWidget> m_scannerWidget;
std::unique_ptr<InsaneWorker> m_insaneWorker;
std::unique_ptr<BackgroundScannerDiscoveryEVH> m_backgroundScannerDiscoveryEvh;
std::unique_ptr<ScanProjectHandler> m_scanProject;
void OnLblNewDocRightClick ( wxMouseEvent& evt );
void OnTxtNewDocKeyPressed ( wxKeyEvent& evt );
void OnBtnScanRightClick ( wxMouseEvent& evt );
void OnBtnScanClick ( wxMouseEvent& evt );
};
#endif // XINSANEWIDGET_H

View File

@@ -0,0 +1,318 @@
/*
* File: XScannerWidget.cpp
* Author: Saleem Edah-Tally - nmset@yandex.com
* License: CeCILL-C
* Copyright Saleem Edah-Tally - © 2024
*
* Created on ?
*/
#include "XScannerWidget.h"
#include "XClientData.hpp"
#include "Common.h"
#include <libinsane/constants.h>
#include <fstream>
#include <vector>
using namespace std;
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 )
{
UpdateExtensionsMap();
m_sb = sb;
m_insaneWorker = insaneWorker;
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]);
#endif
#if wxUSE_LIBJPEG
cmbOutputType->Append (Extensions[JPEG]);
#endif
#if wxUSE_LIBTIFF
cmbOutputType->Append (Extensions[TIFF]);
#endif
#if wxUSE_PNM
cmbOutputType->Append (Extensions[PNM]);
#endif
// Paper sizes handled by podofo.
cmbPaperSize->Append(wxArrayString({"A0", "A1", "A2", "A3", "A4", "A5", "A6",
"Letter", "Legal", "Tabloid"}));
btnRefreshDevices->Bind ( wxEVT_COMMAND_BUTTON_CLICKED, &XScannerWidget::OnButtonRefreshDevices, this );
cmbDevices->Bind ( wxEVT_COMMAND_COMBOBOX_SELECTED, &XScannerWidget::OnDeviceSelected, this );
cmbOutputType->Bind ( wxEVT_COMMAND_COMBOBOX_SELECTED, &XScannerWidget::OnImageTypeSelected, this );
cmbSource->Bind ( wxEVT_COMMAND_COMBOBOX_SELECTED, &XScannerWidget::OnSourceSelected, this );
cmbMode->Bind ( wxEVT_COMMAND_COMBOBOX_SELECTED, &XScannerWidget::OnModeSelected, this );
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 );
}
bool XScannerWidget::FindDevices ( bool async )
{
if ( m_sb && !async )
m_sb->SetTransientText (_("Searching for devices...") );
if ( !m_insaneWorker->Init() )
{
const wxString msg = _("Could not initialise insane api.");
cerr << msg << endl;
if (m_insaneWorkerEvh)
m_insaneWorkerEvh->OnInsaneError(msg.ToStdString());
return false;
}
std::vector<InsaneWorker::DeviceDescriptor> devices = m_insaneWorker->GetDeviceDescriptors();
CallAfter ( &XScannerWidget::ClearScannerItems );
uint numberOfDevices = devices.size();
for ( uint i = 0; i < numberOfDevices; i++ )
{
InsaneWorker::DeviceDescriptor device = devices.at(i);
CallAfter ( &XScannerWidget::AppendScannerItem, device.GetLabel(), device.id );
}
if ( m_sb )
{
const wxString msg = wxVariant ( ( long ) numberOfDevices ).GetString() + _(" device(s) found.");
// Should be but don't : it's as if the above code has not been run.
// m_sb->CallAfter(&TimeredStatusBar::SetTransientText, msg, 3000);
m_sb->SetTransientText(msg);
}
return true;
}
void XScannerWidget::AppendScannerItem ( const wxString& display, const wxString& clientData )
{
cmbDevices->Append ( display, new XClientData ( clientData ) );
}
void XScannerWidget::ClearScannerItems()
{
cmbDevices->Clear();
cmbSource->Clear();
cmbMode->Clear();
cmbResolution->Clear();
}
void XScannerWidget::OnButtonRefreshDevices ( wxCommandEvent& evt )
{
btnRefreshDevices->Enable ( false );
m_insaneWorker->Init();
FindDevices();
evt.Skip();
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 ) )
return;
int currentSelection = cmbDevices->GetCurrentSelection();
if ( currentSelection == wxNOT_FOUND )
return;
XClientData * lastDevice = static_cast<XClientData*> ( cmbDevices->GetClientObject ( currentSelection ) );
if ( !lastDevice )
return;
m_config->Write ( "/Scanner/Last/Device", lastDevice->GetData().GetString() );
m_config->Flush();
UpdateScannerOptions ( lastDevice->GetData().GetString() );
}
void XScannerWidget::OnImageTypeSelected ( wxCommandEvent& evt )
{
if ( !m_config )
return;
m_config->Write ( "/Scanner/Last/OutputType", cmbOutputType->GetStringSelection() );
m_config->Flush();
}
void XScannerWidget::OnSourceSelected ( wxCommandEvent& evt )
{
if ( !m_config )
return;
const wxString deviceId = GetCurrentDeviceId();
if ( wxIsEmpty ( deviceId ) )
return;
const wxString key =_T("/Scanner/") + deviceId +_T("/") + _T ( OPT_NAME_SOURCE );
m_config->Write ( key, cmbSource->GetStringSelection() );
m_config->Flush();
}
void XScannerWidget::OnModeSelected ( wxCommandEvent& evt )
{
if ( !m_config )
return;
const wxString deviceId = GetCurrentDeviceId();
if ( wxIsEmpty ( deviceId ) )
return;
const wxString key =_T("/Scanner/") + deviceId +_T("/") + _T ( OPT_NAME_MODE );
m_config->Write ( key, cmbMode->GetStringSelection() );
m_config->Flush();
}
void XScannerWidget::OnResolutionSelected ( wxCommandEvent& evt )
{
if ( !m_config )
return;
const wxString deviceId = GetCurrentDeviceId();
if ( wxIsEmpty ( deviceId ) )
return;
const wxString key =_T("/Scanner/") + deviceId +_T("/") + _T ( OPT_NAME_RESOLUTION );
m_config->Write ( key, cmbResolution->GetStringSelection() );
m_config->Flush();
}
void XScannerWidget::OnPaperSizeSelected(wxCommandEvent& evt)
{
if ( !m_config )
return;
m_config->Write ( "/Scanner/Last/PaperSize", cmbPaperSize->GetStringSelection() );
m_config->Flush();
}
void XScannerWidget::UpdateScannerOptions ( const wxString& deviceId )
{
if ( wxIsEmpty ( deviceId ) )
return;
auto addListConstrainedOptions = [&] ( wxComboBox * cmb, const wxString& lastKey )
{
if (!cmb)
return;
cmb->Clear();
vector<string> possible;
const string defaultValue = m_insaneWorker->GetOptionPossibleValues(deviceId.ToStdString(), lastKey.ToStdString(), possible);
for (string itemValue : possible)
{
cmb->Append(itemValue);
}
cmb->Enable(cmb->GetCount());
if ( m_config )
{
wxString savedValue;
const wxString key =_T("Scanner/") + deviceId +_T("/") + lastKey;
m_config->Read ( key, &savedValue );
if ( !savedValue.IsEmpty() )
{
cmb->SetStringSelection ( savedValue );
return;
}
}
cmb->SetStringSelection ( defaultValue );
};
/*
* For a device with flatbed and ADF, there are multiple sources, each with a name.
* The display name obtained here is different from the child devices of lis_item.
* Each child item is a source. The root item must not be used for scanning
* following the documentation. Fortunately, they are listed in the same order
* with both enumerations.
* Ex: Brother DCP-L8410CDW
* 0 - FlatBed - flatbed
* 1 - Automatic Document Feeder(left aligned) - feeder(left aligned)
* 2 - Automatic Document Feeder(centrally aligned) - feeder(centrally aligned)
* We must therefore select a source by its index.
*/
addListConstrainedOptions(cmbSource, OPT_NAME_SOURCE);
addListConstrainedOptions(cmbMode, OPT_NAME_MODE);
addListConstrainedOptions(cmbResolution, OPT_NAME_RESOLUTION);
}
wxString XScannerWidget::GetCurrentDeviceId() const
{
int currentSelection = cmbDevices->GetCurrentSelection();
if ( currentSelection == wxNOT_FOUND )
return wxEmptyString;
XClientData * clientData = static_cast<XClientData*> ( cmbDevices->GetClientObject ( currentSelection ) );
if ( !clientData )
return wxEmptyString;
return clientData->GetData().GetString();
}
void XScannerWidget::OnActivated ( wxShowEvent& evt )
{
if ( evt.IsShown() && cmbDevices->GetCount() && cmbDevices->GetCurrentSelection() == wxNOT_FOUND )
{
if ( m_config )
{
wxString lastDevice;
m_config->Read (_T("/Scanner/Last/Device"), &lastDevice );
if ( lastDevice.IsEmpty() || !cmbDevices->GetCount() )
{
evt.Skip();
return;
}
for ( uint i = 0; i < cmbDevices->GetCount(); i++ )
{
XClientData * clientData = static_cast<XClientData*> ( cmbDevices->GetClientObject ( i ) );
if ( !clientData )
continue;
if ( clientData->GetData().GetString() == lastDevice )
{
cmbDevices->Select ( i );
UpdateScannerOptions ( lastDevice );
break;
}
}
}
}
evt.Skip();
}
// ----------------------------------------------------------------------------
wxThread::ExitCode BackgroundScannerDiscovery::Entry()
{
if (m_owner)
{
m_owner->FindDevices ( true );
wxShowEvent evt;
evt.SetShow ( true );
m_owner->OnActivated ( evt );
if (m_evh)
m_evh->OnDone();
}
return ( wxThread::ExitCode ) 0;
}
// ----------------------------------------------------------------------------
void BackgroundScannerDiscoveryEvent::OnDone()
{}

View File

@@ -0,0 +1,120 @@
/*
* File: XScannerWidget.h
* Author: Saleem Edah-Tally - nmset@yandex.com
* License: CeCILL-C
* Copyright Saleem Edah-Tally - © 2024
*
* Created on ?
*/
#ifndef XSCANNERWIDGET_H
#define XSCANNERWIDGET_H
#include <wx/wx.h>
#include <wx/thread.h>
#include <wx/config.h>
#include <ScannerWidget.h>
#include "TimeredStatusBar.h"
#include "InsaneWorker.h"
#include "Common.h"
#include <map>
class BackgroundScannerDiscoveryEvent;
/**
* Find available scanner devices, USB or network attached.\n
* Show the devices and their minimal properties in a popup.\n
* Make the properties available through API.\n
* Specify output file format and page sizes.\n
* All selections are saved and restored using wxConfig.\n
* Devices can be searched in a detached thread, typically on application
* startup.\n
*/
class XScannerWidget : public ScannerWidget
{
friend class BackgroundScannerDiscovery;
public:
XScannerWidget() {};
~XScannerWidget();
XScannerWidget ( wxWindow* parent, TimeredStatusBar * sb,
InsaneWorker * insaneWorker,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = SYMBOL_SCANNERWIDGET_SIZE,
long int style = SYMBOL_SCANNERWIDGET_STYLE );
void SetConfig ( wxConfig * config );
bool FindDevices ( bool async = false );
wxString GetCurrentDeviceId() const;
wxString GetScannerMode() const
{
return cmbMode->GetValue();
}
std::pair<int, wxString> GetScannerSource() const
{
return {cmbSource->GetCurrentSelection(), cmbSource->GetValue()};
}
wxString GetScannerResolution() const
{
return cmbResolution->GetValue();
}
uint GetScannerOutputType()
{
return cmbOutputType->GetCurrentSelection();
}
wxString GetPaperSize() const
{
return cmbPaperSize->GetValue();
}
private:
wxWeakRef<TimeredStatusBar> m_sb = nullptr;
wxConfig * m_config = nullptr;
InsaneWorker * m_insaneWorker;
InsaneWorkerEvent * m_insaneWorkerEvh;
void OnButtonRefreshDevices ( wxCommandEvent& evt );
/*
* For synchronous and asynchronous calls/
*/
void AppendScannerItem ( const wxString& display, const wxString& clientData );
void ClearScannerItems();
void OnActivated ( wxShowEvent& evt );
void OnImageTypeSelected ( wxCommandEvent& evt );
void OnDeviceSelected ( wxCommandEvent& evt );
void OnSourceSelected ( wxCommandEvent& evt );
void OnModeSelected ( wxCommandEvent& evt );
void OnResolutionSelected ( wxCommandEvent& evt );
void OnPaperSizeSelected ( wxCommandEvent& evt );
void UpdateScannerOptions ( const wxString& deviceId );
};
// Asynchronous device discovery.
class BackgroundScannerDiscovery : public wxThread
{
public:
BackgroundScannerDiscovery ( XScannerWidget * owner, BackgroundScannerDiscoveryEvent * evh )
{
m_owner = owner;
m_evh = evh;
}
virtual ~BackgroundScannerDiscovery() {};
virtual ExitCode Entry();
private:
XScannerWidget * m_owner = nullptr;
BackgroundScannerDiscoveryEvent * m_evh = nullptr;
};
class BackgroundScannerDiscoveryEvent
{
public:
virtual void OnDone();
};
#endif // XSCANNERWIDGET_H

View File

@@ -0,0 +1,21 @@
#!/bin/bash
if [ ! -x 0_getstrings.sh ]
then
echo "Wrong working directory. Please cd to the directory containing this script."
exit 0
fi
COMPONENT_NAME=S7
SRC=../../
DOMAIN=fr
DEST=$DOMAIN/${COMPONENT_NAME}.po
[ ! -d $DOMAIN ] && mkdir $DOMAIN
[ -f $DEST ] && JOIN="-j"
[ -f $DEST ] && cp $DEST $DEST.bak-$(date +%F-%T)
[ ! -f $DEST ] && touch $DEST
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")
exit 0

14
Resources/Lokalize/2_makemo.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/bash
if [ ! -x 2_makemo.sh ]
then
echo "Wrong working directory. Please cd to the directory containing this script."
exit 0
fi
DOMAIN=fr
COMPONENT_NAME=S7
msgfmt $DOMAIN/${COMPONENT_NAME}.po -o $DOMAIN/${COMPONENT_NAME}.mo
exit 0

Binary file not shown.

302
Resources/Lokalize/fr/S7.po Normal file
View File

@@ -0,0 +1,302 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# SPDX-FileCopyrightText: 2025 Saleem EDAH-TALLY <set@nmset.info>
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"
"Last-Translator: Saleem EDAH-TALLY <set@nmset.info>\n"
"Language-Team: French <kde-francophone@kde.org>\n"
"Language: fr_FR\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.2\n"
#: ../../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."
msgstr ""
"Choisissez un dossier destination\n"
"Double-clic pour aller au dossier."
#: ../../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
msgid ""
"Specify a destination file basename (without extension).\n"
"\n"
"'CTRL + click' for about information."
msgstr ""
"Spécifiez un nom de base du fichier de destination (sans l'extension).\n"
"\n"
"'CTRL + clic' : à propos"
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:128
msgid "New"
msgstr "Nouveau"
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:130
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
msgid ""
"Full path to destination file without the extension; it is determined by the "
"output type."
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
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."
msgstr ""
"'Clic gauche' pour démarrer la numérisation.\n"
"'Clic droit' pour afficher les options du numériseur."
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:144
msgid "Output document format"
msgstr "Format du fichier de sortie"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:152
msgid "Refresh available devices"
msgstr "Rafraîchir la liste des périphériques"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:161
msgid "Available devices"
msgstr "Périphériques disponibles"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:170
msgid "Scan source"
msgstr "Sources disponibles du numériseur"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:179
msgid "Scan mode"
msgstr "Mode de numérisation"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:188
msgid "Scan resolution"
msgstr "Résolution de la numérisation"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:128
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:107
msgid "A scan library error occurred."
msgstr "Une erreur de bibliothèque est survenue."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:134
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:113
msgid "A general error occurred."
msgstr "Une erreur générale est survenue."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:138
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:117
msgid "A session read error occurred."
msgstr "Une erreur de lecture est survenue."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:145
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:124
msgid "Session cancelled."
msgstr "Session annulée."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:162
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:141
msgid "Scanning: "
msgstr "Numérisation :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:164
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:143
msgid "Front face: "
msgstr "Recto :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:166
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:145
msgid "Back face: "
msgstr "Verso :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:170
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:149
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
msgid "Wrong paper size: "
msgstr "Mauvaise taille de papier :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:201
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:180
msgid "; using A4."
msgstr "; utilisation du format A4."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:207
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:186
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
msgid "Finished."
msgstr "Terminé."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:333
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:317
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
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
msgid "Searching for devices..."
msgstr "Recherche de périphériques..."
#: ../../Resources/InsaneWidget/XScannerWidget.cpp:53
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
msgid "Could not launch default file manager"
msgstr "Échec de lancement du gestionnaire de fichier par défaut."
#: ../../XS7.cpp:93 ../../XS7.cpp:128
msgid "Invalid folder name."
msgstr "Nom de dossier invalide."
#: ../../XS7.cpp:101 ../../XS7.cpp:136
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
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
msgid "S7"
msgstr "S7"
#: ../../Resources/InsaneWidget/UI/InsaneWidget.h:40
msgid "InsaneWidget"
msgstr "InsaneWidget"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.h:49
msgid "ScannerWidget"
msgstr "ScannerWidget"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:138
msgid "Format:"
msgstr "Format : "
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:155
msgid "Devices:"
msgstr "Périphériques :"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:164
msgid "Source:"
msgstr "Source :"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:173
msgid "Mode:"
msgstr "Mode :"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:182
msgid "Resolution:"
msgstr "Résolution :"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:191
msgid "Paper size:"
msgstr "Format de page :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:332
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:316
msgid "Double sided:"
msgstr "Recto-verso :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:334
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:318
msgid "Total:"
msgstr "Total :"
#: ../../XS7.cpp:46 ../../XS7.cpp:81
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
msgid " - version "
msgstr " - version "
#: ../../XS7.cpp:122 ../../XS7.cpp:157
msgid ""
", using InsaneWidget.\n"
"\n"
msgstr ""
", utilisant InsaneWidget.\n"
"\n"
#: ../../Resources/InsaneWidget/XScannerWidget.cpp:73
msgid " device(s) found."
msgstr " périphériques trouvé(s)."
#: ../../XS7.cpp:29
msgid "Config file tag."
msgstr "Suffixe du fichier de configuration."
#: ../../XS7.cpp:30
msgid "Show version and quit."
msgstr "Afficher la version et quitter."
#: ../../XS7.cpp:31
msgid "Show help and quit."
msgstr "Afficher l'aide et quitter."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:352
msgid "Destination file missing."
msgstr "Fichier de destination manquant."

View File

@@ -0,0 +1,298 @@
# 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.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-06-26 21:20+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"
#: ../../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."
msgstr ""
"Choisissez un dossier destination\n"
"Double-clic pour aller au dossier."
#: ../../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
msgid ""
"Specify a destination file basename (without extension).\n"
"\n"
"'CTRL + click' for about information."
msgstr ""
"Spécifiez un nom de base du fichier de destination (sans l'extension).\n"
"\n"
"'CTRL + clic' : à propos"
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:128
msgid "New"
msgstr "Nouveau"
#: ../../Resources/InsaneWidget/UI/InsaneWidget.cpp:130
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
msgid ""
"Full path to destination file without the extension; it is determined by the "
"output type."
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
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."
msgstr ""
"'Clic gauche' pour démarrer la numérisation.\n"
"'Clic droit' pour afficher les options du numériseur."
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:144
msgid "Output document format"
msgstr "Format du fichier de sortie"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:152
msgid "Refresh available devices"
msgstr "Rafraîchir la liste des périphériques"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:161
msgid "Available devices"
msgstr "Périphériques disponibles"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:170
msgid "Scan source"
msgstr "Sources disponibles du numériseur"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:179
msgid "Scan mode"
msgstr "Mode de numérisation"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:188
msgid "Scan resolution"
msgstr "Résolution de la numérisation"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:128
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:107
msgid "A scan library error occurred."
msgstr "Une erreur de bibliothèque est survenue."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:134
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:113
msgid "A general error occurred."
msgstr "Une erreur générale est survenue."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:138
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:117
msgid "A session read error occurred."
msgstr "Une erreur de lecture est survenue."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:145
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:124
msgid "Session cancelled."
msgstr "Session annulée."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:162
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:141
msgid "Scanning: "
msgstr "Numérisation :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:164
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:143
msgid "Front face: "
msgstr "Recto :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:166
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:145
msgid "Back face: "
msgstr "Verso :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:170
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:149
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
msgid "Wrong paper size: "
msgstr "Mauvaise taille de papier :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:201
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:180
msgid "; using A4."
msgstr "; utilisation du format A4."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:207
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:186
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
msgid "Finished."
msgstr "Terminé."
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:333
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:317
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
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
msgid "Searching for devices..."
msgstr "Recherche de périphériques..."
#: ../../Resources/InsaneWidget/XScannerWidget.cpp:53
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
msgid "Could not launch default file manager"
msgstr "Échec de lancement du gestionnaire de fichier par défaut."
#: ../../XS7.cpp:93 ../../XS7.cpp:128
msgid "Invalid folder name."
msgstr "Nom de dossier invalide."
#: ../../XS7.cpp:101 ../../XS7.cpp:136
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
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
msgid "S7"
msgstr "S7"
#: ../../Resources/InsaneWidget/UI/InsaneWidget.h:40
msgid "InsaneWidget"
msgstr "InsaneWidget"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.h:49
msgid "ScannerWidget"
msgstr "ScannerWidget"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:138
msgid "Format:"
msgstr "Format : "
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:155
msgid "Devices:"
msgstr "Périphériques :"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:164
msgid "Source:"
msgstr "Source :"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:173
msgid "Mode:"
msgstr "Mode :"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:182
msgid "Resolution:"
msgstr "Résolution :"
#: ../../Resources/InsaneWidget/UI/ScannerWidget.cpp:191
msgid "Paper size:"
msgstr "Format de page :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:332
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:316
msgid "Double sided:"
msgstr "Recto-verso :"
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:334
#: ../../Resources/InsaneWidget/XInsaneWidget.cpp:318
msgid "Total:"
msgstr "Total :"
#: ../../XS7.cpp:46 ../../XS7.cpp:81
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
msgid " - version "
msgstr " - version "
#: ../../XS7.cpp:122 ../../XS7.cpp:157
msgid ""
", using InsaneWidget.\n"
"\n"
msgstr ""
", utilisant InsaneWidget.\n"
"\n"
#: ../../Resources/InsaneWidget/XScannerWidget.cpp:73
msgid " device(s) found."
msgstr " périphériques trouvé(s)."
#: ../../XS7.cpp:29
msgid "Config file tag."
msgstr "Suffixe du fichier de configuration."
#: ../../XS7.cpp:30
msgid "Show version and quit."
msgstr "Afficher la version et quitter."
#: ../../XS7.cpp:31
msgid "Show help and quit."
msgstr "Afficher l'aide et quitter."

31
Resources/PKGBUILD Normal file
View File

@@ -0,0 +1,31 @@
#Simple PKGBUILD that suits my need.
pkgname=(s7)
pkgver=1
pkgrel=0
arch=('x86_64' 'i686' 'armv7h' 'aarch64')
url='http://github.com/nmset/s7/'
# Add 'wxwidgets-gtk3' below.
depends=('libinsane' 'podofo' 'libpaper')
license=('CeCILL' 'CeCILL-C')
OPTIONS=(strip)
build() {
cd ${startdir}
[ ! -d build ] && mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=${pkgdir}/usr/local ../../
make -j8
cd ${startdir}/../Resources/Lokalize
./2_makemo.sh
cd -
}
package() {
cd ${startdir}/build
make install
cd -
mkdir -p ${pkgdir}/usr/local/share/locale/fr/LC_MESSAGES
cp ${startdir}/../Resources/Lokalize/fr/S7.mo ${pkgdir}/usr/local/share/locale/fr/LC_MESSAGES
rm -fr ${startdir}/build
}

622
Resources/UI/S7/S7.pjd Normal file
View File

@@ -0,0 +1,622 @@
<?xml version="1.0" encoding="UTF-8"?>
<anthemion-project version="1.0.0.0" xmlns="http://www.anthemion.co.uk">
<header>
<bool name="always_generate_xrc">1</bool>
<string name="app_class_name">"Application"</string>
<string name="app_file_header">"app.h"</string>
<string name="app_file_implementation">"app.cpp"</string>
<string name="app_kind">"Standard"</string>
<bool name="archive_all_image_files">0</bool>
<bool name="archive_image_files">1</bool>
<bool name="archive_xrc_files">1</bool>
<string name="author">""</string>
<string name="configuration">"&lt;None&gt;"</string>
<string name="copyright_string">"Copyright Saleem EDAH-TALLY. All rights reserved."</string>
<string name="cpp_function_declaration_comment">" /// %BODY%
"</string>
<string name="cpp_function_implementation_comment">"
/*
* %BODY%
*/
"</string>
<string name="cpp_header_comment">"/////////////////////////////////////////////////////////////////////////////
// Name: %HEADER-FILENAME%
// Purpose:
// Author: %AUTHOR%
// Modified by:
// Created: %DATE%
// RCS-ID:
// Copyright: %COPYRIGHT%
// Licence:
/////////////////////////////////////////////////////////////////////////////
"</string>
<string name="cpp_header_preamble">""</string>
<string name="cpp_implementation_comment">"/////////////////////////////////////////////////////////////////////////////
// Name: %SOURCE-FILENAME%
// Purpose:
// Author: %AUTHOR%
// Modified by:
// Created: %DATE%
// RCS-ID:
// Copyright: %COPYRIGHT%
// Licence:
/////////////////////////////////////////////////////////////////////////////
"</string>
<string name="cpp_implementation_preamble">"// For compilers that support precompilation, includes &quot;wx/wx.h&quot;.
#include &quot;wx/wxprec.h&quot;
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#ifndef WX_PRECOMP
#include &quot;wx/wx.h&quot;
#endif
"</string>
<string name="cpp_symbols_file_comment">"/////////////////////////////////////////////////////////////////////////////
// Name: %SYMBOLS-FILENAME%
// Purpose: Symbols file
// Author: %AUTHOR%
// Modified by:
// Created: %DATE%
// RCS-ID:
// Copyright: %COPYRIGHT%
// Licence:
/////////////////////////////////////////////////////////////////////////////
"</string>
<string name="current_platform">"&lt;All platforms&gt;"</string>
<string name="description">""</string>
<string name="external_symbol_filenames">""</string>
<bool name="extract_strings">0</bool>
<bool name="generate_app_class">0</bool>
<bool name="generate_cpp_for_xrc">0</bool>
<bool name="generate_for_xrced">0</bool>
<bool name="generate_virtual_eventhandlers">0</bool>
<string name="html_path">""</string>
<long name="image_mode">0</long>
<long name="indent_size">4</long>
<bool name="inline_images">0</bool>
<bool name="make_unicode_strings">1</bool>
<long name="name_counter">0</long>
<string name="project_encoding">"utf-8"</string>
<string name="resource_archive">""</string>
<string name="resource_class_name">"AppResources"</string>
<string name="resource_file_header">"app_resources.h"</string>
<string name="resource_file_implementation">"app_resources.cpp"</string>
<string name="resource_prefix">""</string>
<string name="resource_xrc_cpp">""</string>
<string name="source_encoding">"utf-8"</string>
<string name="target_wx_version">"2.9.2"</string>
<long name="text_file_type">0</long>
<string name="title">""</string>
<bool name="translate_strings">1</bool>
<bool name="use_enums">0</bool>
<bool name="use_generated_xrc_cpp">0</bool>
<bool name="use_help_text_for_tooltips">1</bool>
<bool name="use_id_name_for_name">0</bool>
<bool name="use_resource_archive">0</bool>
<bool name="use_sizer_pixel_compatibility">0</bool>
<bool name="use_tabs">0</bool>
<bool name="use_two_step_construction">0</bool>
<string name="user_name">"Saleem EDAH-TALLY"</string>
<string name="whitespace_after_return_type">" "</string>
<long name="working_mode">1</long>
<string name="xrc_encoding">"utf-8"</string>
<string name="xrc_filename">""</string>
<bool name="xrc_generate_id_tags">0</bool>
<bool name="xrc_retain_relative_paths">1</bool>
<bool name="xrc_use_name_property">0</bool>
</header>
<data>
<document>
<string name="title">""</string>
<string name="type">"data-document"</string>
<string name="filename">""</string>
<string name="icon-name">""</string>
<long name="is-transient">0</long>
<long name="owns-file">1</long>
<long name="title-mode">0</long>
<long name="locked">0</long>
<document>
<string name="Build mode">"Debug"</string>
<string name="C command">"%AUTO%"</string>
<string name="C++ command">"%AUTO%"</string>
<string name="CFG">""</string>
<string name="Compiler bin path">"%AUTO%"</string>
<string name="Compiler include path">"%AUTO%"</string>
<string name="Compiler lib path">"%AUTO%"</string>
<string name="Compiler location">"%AUTO%"</string>
<string name="Compiler name">""</string>
<string name="Debug flags">"%AUTO%"</string>
<bool name="dirty">1</bool>
<bool name="Enable makefile generation">1</bool>
<string name="Executable name">"%EXECUTABLE%"</string>
<string name="Extra compile flags">"%AUTO%"</string>
<string name="Extra dependencies">"%AUTO%"</string>
<string name="filename">""</string>
<string name="GUI mode">"GUI"</string>
<string name="icon-name">""</string>
<string name="Include path">"%AUTO%"</string>
<long name="is-transient">0</long>
<string name="Libraries">"%AUTO%"</string>
<string name="Library path">"%AUTO%"</string>
<string name="Linker command">"%AUTO%"</string>
<string name="Linker flags">"%AUTO%"</string>
<long name="locked">0</long>
<string name="Make command">"%AUTO%"</string>
<long name="makefile-last-written">4286447616</long>
<string name="Modularity">"Modular"</string>
<string name="Objects path">"%AUTO%"</string>
<string name="Optimizations">"%AUTO%"</string>
<string name="Output path">"%AUTO%"</string>
<long name="owns-file">1</long>
<string name="PATH variable">"%AUTO%"</string>
<string name="Preprocessor flags">"%AUTO%"</string>
<string name="Processor type">"Default"</string>
<string name="Program arguments">""</string>
<string name="Project makefile">"%AUTO%"</string>
<string name="Resource compiler">"%AUTO%"</string>
<string name="Resource flags">"%AUTO%"</string>
<string name="Resource path">"%AUTO%"</string>
<string name="Runtime linking">"Dynamic"</string>
<string name="Shared mode">"Static"</string>
<bool name="Suppress source rules">0</bool>
<string name="template-name">""</string>
<string name="title">"Configurations"</string>
<long name="title-mode">0</long>
<string name="Toolkit">"wxGTK+2"</string>
<string name="type">"config-data-document"</string>
<string name="Unicode mode">"Unicode"</string>
<string name="Use exceptions">"Yes"</string>
<string name="Use ODBC">"No"</string>
<string name="Use OpenGL">"No"</string>
<string name="Use wxAUI">"Yes"</string>
<string name="Use wxHTML">"Yes"</string>
<string name="Use wxMediaCtrl">"No"</string>
<string name="Use wxPropertyGrid">"Yes"</string>
<string name="Use wxRegEx">"builtin"</string>
<string name="Use wxRibbonControl">"Yes"</string>
<string name="Use wxRichTextCtrl">"Yes"</string>
<string name="Use wxSTC">"Yes"</string>
<string name="Use XRC">"Yes"</string>
<string name="Warnings">"%AUTO%"</string>
<string name="Working path">"%AUTO%"</string>
<string name="wxWidgets build command">"%AUTO%"</string>
<string name="wxWidgets build path">"%AUTO%"</string>
<string name="wxWidgets clean command">"%AUTO%"</string>
<string name="wxWidgets location">"%AUTO%"</string>
<string name="wxWidgets makefile">"%AUTO%"</string>
<string name="wxWidgets version">"%WXVERSION%"</string>
</document>
</document>
</data>
<documents>
<document>
<string name="title">"Projects"</string>
<string name="type">"root-document"</string>
<string name="filename">""</string>
<string name="icon-name">"project"</string>
<long name="is-transient">1</long>
<long name="owns-file">1</long>
<long name="title-mode">0</long>
<long name="locked">1</long>
<document>
<string name="title">"Windows"</string>
<string name="type">"html-document"</string>
<string name="filename">""</string>
<string name="icon-name">"dialogsfolder"</string>
<long name="is-transient">1</long>
<long name="owns-file">1</long>
<long name="title-mode">0</long>
<long name="locked">1</long>
<document>
<string name="proxy-type">"wbAppProxy"</string>
<string name="app-kind">"Standard"</string>
<long name="base-id">10000</long>
<string name="filename">""</string>
<string name="icon-name">"app"</string>
<string name="id-prefix">""</string>
<string name="id-suffix">""</string>
<long name="is-transient">0</long>
<long name="locked">0</long>
<long name="owns-file">1</long>
<string name="proxy-Base class">"wxApp"</string>
<string name="proxy-Class">"S7App"</string>
<string name="proxy-Event sources">""</string>
<string name="proxy-Header filename">"s7app.h"</string>
<string name="proxy-Help filename">""</string>
<string name="proxy-Implementation filename">"s7app.cpp"</string>
<string name="proxy-Main window">""</string>
<string name="title">"S7App"</string>
<long name="title-mode">0</long>
<string name="type">"dialog-document"</string>
<bool name="use-id-prefix">0</bool>
<bool name="use-id-suffix">0</bool>
<long name="use-xrc">0</long>
<long name="working-mode">0</long>
</document>
<document>
<string name="proxy-type">"wbFrameProxy"</string>
<string name="app-kind">"Standard"</string>
<long name="base-id">10000</long>
<string name="filename">""</string>
<string name="icon-name">"frame"</string>
<string name="id-prefix">""</string>
<string name="id-suffix">""</string>
<string name="identifier">"itemFrame1"</string>
<long name="is-transient">0</long>
<long name="locked">0</long>
<long name="owns-file">1</long>
<bool name="proxy-AUI manager">0</bool>
<string name="proxy-Background colour">""</string>
<string name="proxy-Base class">"wxFrame"</string>
<bool name="proxy-Centre">1</bool>
<string name="proxy-Class">"S7"</string>
<string name="proxy-Custom styles">""</string>
<string name="proxy-Data class header filename">""</string>
<string name="proxy-Data class implementation filename">""</string>
<string name="proxy-Data class manager window">""</string>
<string name="proxy-Data class name">""</string>
<string name="proxy-Data source">""</string>
<bool name="proxy-Dialog units">0</bool>
<bool name="proxy-Enabled">1</bool>
<string name="proxy-Event sources">""</string>
<bool name="proxy-Fit to content">1</bool>
<string name="proxy-Font">""</string>
<string name="proxy-Foreground colour">""</string>
<string name="proxy-Header filename">"s7.h"</string>
<long name="proxy-Height">300</long>
<string name="proxy-Help text">""</string>
<bool name="proxy-Hidden">0</bool>
<string name="proxy-Icon">""</string>
<string name="proxy-Id name">"ID_S7"</string>
<long name="proxy-Id value">10000</long>
<string name="proxy-Implementation filename">"s7.cpp"</string>
<string name="proxy-Platform">"&lt;Any platform&gt;"</string>
<string name="proxy-Title">"S7"</string>
<string name="proxy-Tooltip text">""</string>
<long name="proxy-Width">400</long>
<bool name="proxy-wxBORDER_THEME">0</bool>
<bool name="proxy-wxCAPTION">1</bool>
<bool name="proxy-wxCLIP_CHILDREN">0</bool>
<bool name="proxy-wxCLOSE_BOX">1</bool>
<bool name="proxy-wxDEFAULT_FRAME_STYLE">0</bool>
<bool name="proxy-wxDOUBLE_BORDER">0</bool>
<bool name="proxy-wxFRAME_EX_CONTEXTHELP">0</bool>
<bool name="proxy-wxFRAME_FLOAT_ON_PARENT">0</bool>
<bool name="proxy-wxFRAME_NO_TASKBAR">0</bool>
<bool name="proxy-wxFRAME_TOOL_WINDOW">0</bool>
<bool name="proxy-wxFULL_REPAINT_ON_RESIZE">0</bool>
<bool name="proxy-wxMAXIMIZE">0</bool>
<bool name="proxy-wxMAXIMIZE_BOX">0</bool>
<bool name="proxy-wxMINIMIZE">0</bool>
<bool name="proxy-wxMINIMIZE_BOX">0</bool>
<bool name="proxy-wxNO_BORDER">0</bool>
<bool name="proxy-wxNO_FULL_REPAINT_ON_RESIZE">0</bool>
<bool name="proxy-wxRAISED_BORDER">0</bool>
<bool name="proxy-wxRESIZE_BORDER">1</bool>
<bool name="proxy-wxSIMPLE_BORDER">0</bool>
<bool name="proxy-wxSTATIC_BORDER">0</bool>
<bool name="proxy-wxSTAY_ON_TOP">0</bool>
<bool name="proxy-wxSUNKEN_BORDER">0</bool>
<bool name="proxy-wxSYSTEM_MENU">1</bool>
<bool name="proxy-wxTAB_TRAVERSAL">0</bool>
<bool name="proxy-wxWANTS_CHARS">0</bool>
<bool name="proxy-wxWS_EX_BLOCK_EVENTS">0</bool>
<bool name="proxy-wxWS_EX_TRANSIENT">0</bool>
<bool name="proxy-wxWS_EX_VALIDATE_RECURSIVELY">0</bool>
<long name="proxy-X">-1</long>
<string name="proxy-XRC filename">""</string>
<long name="proxy-Y">-1</long>
<string name="title">"S7"</string>
<long name="title-mode">0</long>
<string name="type">"dialog-document"</string>
<bool name="use-id-prefix">0</bool>
<bool name="use-id-suffix">0</bool>
<long name="use-xrc">0</long>
<long name="working-mode">0</long>
<document>
<string name="proxy-type">"wbPanelProxy"</string>
<string name="filename">""</string>
<string name="icon-name">"panel"</string>
<string name="identifier">"itemPanel1"</string>
<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>
<bool name="proxy-AUI manager">0</bool>
<string name="proxy-Background colour">""</string>
<string name="proxy-Base class">"wxPanel"</string>
<long name="proxy-Border">5</long>
<string name="proxy-Class">"wxPanel"</string>
<string name="proxy-Custom arguments">""</string>
<string name="proxy-Custom ctor arguments">""</string>
<string name="proxy-Custom styles">""</string>
<string name="proxy-Data class header filename">""</string>
<string name="proxy-Data class implementation filename">""</string>
<string name="proxy-Data class manager window">""</string>
<string name="proxy-Data class name">""</string>
<string name="proxy-Data source">""</string>
<bool name="proxy-Enabled">1</bool>
<string name="proxy-Event sources">""</string>
<bool name="proxy-External implementation">1</bool>
<bool name="proxy-Fit to content">1</bool>
<string name="proxy-Font">""</string>
<string name="proxy-Foreground colour">""</string>
<string name="proxy-Header filename">""</string>
<long name="proxy-Height">-1</long>
<string name="proxy-Help text">""</string>
<bool name="proxy-Hidden">0</bool>
<string name="proxy-Id name">"ID_PANEL"</string>
<long name="proxy-Id value">10001</long>
<string name="proxy-Implementation filename">""</string>
<string name="proxy-Member variable name">"panMain"</string>
<string name="proxy-Name">""</string>
<string name="proxy-Platform">"&lt;Any platform&gt;"</string>
<bool name="proxy-Separate files">0</bool>
<long name="proxy-Stretch factor">0</long>
<string name="proxy-Texture">""</string>
<string name="proxy-Texture style">"Tiled"</string>
<string name="proxy-Tooltip text">""</string>
<long name="proxy-Width">-1</long>
<bool name="proxy-wxADJUST_MINSIZE">0</bool>
<bool name="proxy-wxALWAYS_SHOW_SB">0</bool>
<bool name="proxy-wxBORDER_THEME">0</bool>
<bool name="proxy-wxBOTTOM">1</bool>
<bool name="proxy-wxCLIP_CHILDREN">0</bool>
<bool name="proxy-wxDOUBLE_BORDER">0</bool>
<bool name="proxy-wxFIXED_MINSIZE">0</bool>
<bool name="proxy-wxFULL_REPAINT_ON_RESIZE">0</bool>
<bool name="proxy-wxHSCROLL">0</bool>
<bool name="proxy-wxLEFT">1</bool>
<bool name="proxy-wxNO_BORDER">0</bool>
<bool name="proxy-wxNO_FULL_REPAINT_ON_RESIZE">0</bool>
<bool name="proxy-wxRAISED_BORDER">0</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-wxSIMPLE_BORDER">0</bool>
<bool name="proxy-wxSTATIC_BORDER">0</bool>
<bool name="proxy-wxSUNKEN_BORDER">1</bool>
<bool name="proxy-wxTAB_TRAVERSAL">1</bool>
<bool name="proxy-wxTOP">1</bool>
<bool name="proxy-wxVSCROLL">0</bool>
<bool name="proxy-wxWANTS_CHARS">0</bool>
<bool name="proxy-wxWS_EX_BLOCK_EVENTS">0</bool>
<bool name="proxy-wxWS_EX_VALIDATE_RECURSIVELY">1</bool>
<long name="proxy-X">-1</long>
<long name="proxy-Y">-1</long>
<string name="title">"wxPanel: ID_PANEL"</string>
<long name="title-mode">0</long>
<string name="type">"dialog-control-document"</string>
<document>
<string name="proxy-type">"wbBoxSizerProxy"</string>
<string name="filename">""</string>
<string name="icon-name">"sizer"</string>
<string name="identifier">"itemBoxSizer2"</string>
<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">"szMain"</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>
<document>
<string name="proxy-type">"wbDirPickerCtrlProxy"</string>
<string name="filename">""</string>
<string name="icon-name">"dialogcontrol"</string>
<string name="identifier">"itemDirPickerCtrl3"</string>
<long name="is-transient">0</long>
<long name="locked">0</long>
<long name="owns-file">1</long>
<string name="proxy-AlignH">"Expand"</string>
<string name="proxy-AlignV">"Centre"</string>
<string name="proxy-Background colour">""</string>
<string name="proxy-Base class">"wxDirPickerCtrl"</string>
<long name="proxy-Border">5</long>
<string name="proxy-Class">"wxDirPickerCtrl"</string>
<string name="proxy-Custom arguments">""</string>
<string name="proxy-Custom ctor arguments">""</string>
<string name="proxy-Custom styles">""</string>
<string name="proxy-Data class header filename">""</string>
<string name="proxy-Data class implementation filename">""</string>
<string name="proxy-Data class manager window">""</string>
<string name="proxy-Data class name">""</string>
<string name="proxy-Data source">""</string>
<string name="proxy-Data validator">""</string>
<string name="proxy-Data variable">""</string>
<string name="proxy-Default path">""</string>
<bool name="proxy-Enabled">1</bool>
<bool name="proxy-External implementation">1</bool>
<string name="proxy-Font">""</string>
<string name="proxy-Foreground colour">""</string>
<string name="proxy-Header filename">""</string>
<long name="proxy-Height">-1</long>
<string name="proxy-Help text">""</string>
<bool name="proxy-Hidden">0</bool>
<string name="proxy-Id name">"ID_DIRPICKERCTRL"</string>
<long name="proxy-Id value">10002</long>
<string name="proxy-Implementation filename">""</string>
<string name="proxy-Member variable name">"dpkDestination"</string>
<string name="proxy-Message">""</string>
<string name="proxy-Name">""</string>
<string name="proxy-Platform">"&lt;Any platform&gt;"</string>
<bool name="proxy-Separate files">0</bool>
<long name="proxy-Stretch factor">0</long>
<string name="proxy-Tooltip text">"Select a destination directory.
Double-click to go to the selected directory."</string>
<long name="proxy-Width">-1</long>
<bool name="proxy-wxADJUST_MINSIZE">0</bool>
<bool name="proxy-wxBORDER_THEME">0</bool>
<bool name="proxy-wxBOTTOM">1</bool>
<bool name="proxy-wxCLIP_CHILDREN">0</bool>
<bool name="proxy-wxDIRP_CHANGE_DIR">0</bool>
<bool name="proxy-wxDIRP_DEFAULT_STYLE">1</bool>
<bool name="proxy-wxDIRP_DIR_MUST_EXIST">0</bool>
<bool name="proxy-wxDIRP_USE_TEXTCTRL">1</bool>
<bool name="proxy-wxDOUBLE_BORDER">0</bool>
<bool name="proxy-wxFIXED_MINSIZE">0</bool>
<bool name="proxy-wxLEFT">1</bool>
<bool name="proxy-wxNO_BORDER">0</bool>
<bool name="proxy-wxRAISED_BORDER">0</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-wxSIMPLE_BORDER">0</bool>
<bool name="proxy-wxSTATIC_BORDER">0</bool>
<bool name="proxy-wxSUNKEN_BORDER">0</bool>
<bool name="proxy-wxTOP">1</bool>
<bool name="proxy-wxWANTS_CHARS">0</bool>
<long name="proxy-X">-1</long>
<long name="proxy-Y">-1</long>
<string name="title">"wxDirPickerCtrl: ID_DIRPICKERCTRL"</string>
<long name="title-mode">0</long>
<string name="type">"dialog-control-document"</string>
</document>
<document>
<string name="proxy-type">"wbTextCtrlProxy"</string>
<string name="filename">""</string>
<string name="icon-name">"textctrl"</string>
<string name="identifier">"itemTextCtrl4"</string>
<long name="is-transient">0</long>
<long name="locked">0</long>
<long name="owns-file">1</long>
<string name="proxy-AlignH">"Expand"</string>
<string name="proxy-AlignV">"Centre"</string>
<string name="proxy-Background colour">""</string>
<string name="proxy-Base class">"wxTextCtrl"</string>
<long name="proxy-Border">5</long>
<string name="proxy-Class">"wxTextCtrl"</string>
<string name="proxy-Custom arguments">""</string>
<string name="proxy-Custom ctor arguments">""</string>
<string name="proxy-Custom styles">""</string>
<string name="proxy-Data class header filename">""</string>
<string name="proxy-Data class implementation filename">""</string>
<string name="proxy-Data class manager window">""</string>
<string name="proxy-Data class name">""</string>
<string name="proxy-Data source">""</string>
<string name="proxy-Data validator">""</string>
<string name="proxy-Data variable">""</string>
<bool name="proxy-Enabled">1</bool>
<bool name="proxy-External implementation">1</bool>
<string name="proxy-Font">""</string>
<string name="proxy-Foreground colour">""</string>
<string name="proxy-Header filename">""</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>
<long name="proxy-Id value">10003</long>
<string name="proxy-Implementation filename">""</string>
<string name="proxy-Initial value">""</string>
<long name="proxy-Max length">0</long>
<string name="proxy-Member variable name">"txtBasename"</string>
<string name="proxy-Name">""</string>
<string name="proxy-Platform">"&lt;Any platform&gt;"</string>
<bool name="proxy-Separate files">0</bool>
<long name="proxy-Stretch factor">0</long>
<string name="proxy-Tooltip text">"Specify a destination file basename (without extension).
'CTRL + click' for about information."</string>
<long name="proxy-Width">-1</long>
<bool name="proxy-wxADJUST_MINSIZE">0</bool>
<bool name="proxy-wxBORDER_THEME">0</bool>
<bool name="proxy-wxBOTTOM">1</bool>
<bool name="proxy-wxDOUBLE_BORDER">0</bool>
<bool name="proxy-wxFIXED_MINSIZE">0</bool>
<bool name="proxy-wxFULL_REPAINT_ON_RESIZE">0</bool>
<bool name="proxy-wxHSCROLL">0</bool>
<bool name="proxy-wxLEFT">1</bool>
<bool name="proxy-wxNO_BORDER">0</bool>
<bool name="proxy-wxNO_FULL_REPAINT_ON_RESIZE">0</bool>
<bool name="proxy-wxRAISED_BORDER">0</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-wxSIMPLE_BORDER">0</bool>
<bool name="proxy-wxSTATIC_BORDER">0</bool>
<bool name="proxy-wxSUNKEN_BORDER">0</bool>
<bool name="proxy-wxTE_AUTO_URL">0</bool>
<bool name="proxy-wxTE_CAPITALIZE">0</bool>
<bool name="proxy-wxTE_CENTRE">0</bool>
<bool name="proxy-wxTE_CHARWRAP">0</bool>
<bool name="proxy-wxTE_LEFT">0</bool>
<bool name="proxy-wxTE_MULTILINE">0</bool>
<bool name="proxy-wxTE_NOHIDESEL">0</bool>
<bool name="proxy-wxTE_PASSWORD">0</bool>
<bool name="proxy-wxTE_PROCESS_ENTER">0</bool>
<bool name="proxy-wxTE_PROCESS_TAB">0</bool>
<bool name="proxy-wxTE_READONLY">0</bool>
<bool name="proxy-wxTE_RICH">0</bool>
<bool name="proxy-wxTE_RICH2">0</bool>
<bool name="proxy-wxTE_RIGHT">0</bool>
<bool name="proxy-wxTE_WORDWRAP">0</bool>
<bool name="proxy-wxTOP">1</bool>
<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>
<long name="title-mode">0</long>
<string name="type">"dialog-control-document"</string>
</document>
</document>
</document>
</document>
</document>
<document>
<string name="title">"Sources"</string>
<string name="type">"html-document"</string>
<string name="filename">""</string>
<string name="icon-name">"sourcesfolder"</string>
<long name="is-transient">1</long>
<long name="owns-file">1</long>
<long name="title-mode">0</long>
<long name="locked">1</long>
<document>
<string name="title">"S7.rc"</string>
<string name="type">"source-editor-document"</string>
<string name="filename">"S7.rc"</string>
<string name="icon-name">"source-editor"</string>
<long name="is-transient">0</long>
<long name="owns-file">0</long>
<long name="title-mode">1</long>
<long name="locked">0</long>
<string name="created">"16/6/2025"</string>
<string name="language">""</string>
</document>
</document>
<document>
<string name="title">"Images"</string>
<string name="type">"html-document"</string>
<string name="filename">""</string>
<string name="icon-name">"bitmapsfolder"</string>
<long name="is-transient">1</long>
<long name="owns-file">1</long>
<long name="title-mode">0</long>
<long name="locked">1</long>
</document>
</document>
</documents>
</anthemion-project>

1
Resources/UI/S7/S7.rc Normal file
View File

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

172
Resources/UI/S7/s7.cpp Normal file
View File

@@ -0,0 +1,172 @@
/////////////////////////////////////////////////////////////////////////////
// Name: s7.cpp
// Purpose:
// Author: Saleem EDAH-TALLY
// Modified by:
// Created: lun. 16 juin 2025 22:42:13
// 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 "s7.h"
////@begin XPM images
////@end XPM images
/*
* S7 type definition
*/
IMPLEMENT_CLASS( S7, wxFrame )
/*
* S7 event table definition
*/
BEGIN_EVENT_TABLE( S7, wxFrame )
////@begin S7 event table entries
////@end S7 event table entries
END_EVENT_TABLE()
/*
* S7 constructors
*/
S7::S7()
{
Init();
}
S7::S7( wxWindow* parent, wxWindowID id, const wxString& caption, const wxPoint& pos, const wxSize& size, long style )
{
Init();
Create( parent, id, caption, pos, size, style );
}
/*
* S7 creator
*/
bool S7::Create( wxWindow* parent, wxWindowID id, const wxString& caption, const wxPoint& pos, const wxSize& size, long style )
{
////@begin S7 creation
wxFrame::Create( parent, id, caption, pos, size, style );
CreateControls();
Centre();
////@end S7 creation
return true;
}
/*
* S7 destructor
*/
S7::~S7()
{
////@begin S7 destruction
////@end S7 destruction
}
/*
* Member initialisation
*/
void S7::Init()
{
////@begin S7 member initialisation
panMain = NULL;
szMain = NULL;
dpkDestination = NULL;
txtBasename = NULL;
////@end S7 member initialisation
}
/*
* Control creation for S7
*/
void S7::CreateControls()
{
////@begin S7 content construction
S7* itemFrame1 = this;
panMain = new wxPanel( itemFrame1, ID_PANEL, wxDefaultPosition, wxDefaultSize, wxSUNKEN_BORDER|wxTAB_TRAVERSAL );
panMain->SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY);
szMain = new wxBoxSizer(wxVERTICAL);
panMain->SetSizer(szMain);
dpkDestination = new wxDirPickerCtrl( panMain, ID_DIRPICKERCTRL, wxEmptyString, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxDIRP_DEFAULT_STYLE|wxDIRP_USE_TEXTCTRL );
if (S7::ShowToolTips())
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->SetHelpText(_("Basename"));
if (S7::ShowToolTips())
txtBasename->SetToolTip(_("Specify a destination file basename (without extension).\n\n'CTRL + click' for about information."));
szMain->Add(txtBasename, 0, wxGROW|wxALL, 5);
////@end S7 content construction
}
/*
* Should we show tooltips?
*/
bool S7::ShowToolTips()
{
return true;
}
/*
* Get bitmap resources
*/
wxBitmap S7::GetBitmapResource( const wxString& name )
{
// Bitmap retrieval
////@begin S7 bitmap retrieval
wxUnusedVar(name);
return wxNullBitmap;
////@end S7 bitmap retrieval
}
/*
* Get icon resources
*/
wxIcon S7::GetIconResource( const wxString& name )
{
// Icon retrieval
////@begin S7 icon retrieval
wxUnusedVar(name);
return wxNullIcon;
////@end S7 icon retrieval
}

101
Resources/UI/S7/s7.h Normal file
View File

@@ -0,0 +1,101 @@
/////////////////////////////////////////////////////////////////////////////
// Name: s7.h
// Purpose:
// Author: Saleem EDAH-TALLY
// Modified by:
// Created: lun. 16 juin 2025 22:42:13
// RCS-ID:
// Copyright: Copyright Saleem EDAH-TALLY. All rights reserved.
// Licence:
/////////////////////////////////////////////////////////////////////////////
#ifndef _S7_H_
#define _S7_H_
/*!
* Includes
*/
////@begin includes
#include "wx/frame.h"
#include "wx/filepicker.h"
////@end includes
#include "wx/panel.h"
/*!
* Forward declarations
*/
////@begin forward declarations
class wxBoxSizer;
class wxDirPickerCtrl;
////@end forward declarations
/*!
* Control identifiers
*/
////@begin control identifiers
#define ID_S7 10000
#define ID_PANEL 10001
#define ID_DIRPICKERCTRL 10002
#define ID_TEXTCTRL 10003
#define SYMBOL_S7_STYLE wxCAPTION|wxRESIZE_BORDER|wxSYSTEM_MENU|wxCLOSE_BOX
#define SYMBOL_S7_TITLE _("S7")
#define SYMBOL_S7_IDNAME ID_S7
#define SYMBOL_S7_SIZE wxSize(400, 300)
#define SYMBOL_S7_POSITION wxDefaultPosition
////@end control identifiers
/*!
* S7 class declaration
*/
class S7: public wxFrame
{
DECLARE_CLASS( S7 )
DECLARE_EVENT_TABLE()
public:
/// Constructors
S7();
S7( 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 );
bool Create( 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 );
/// Destructor
~S7();
/// Initialises member variables
void Init();
/// Creates the controls and sizers
void CreateControls();
////@begin S7 event handler declarations
////@end S7 event handler declarations
////@begin S7 member function declarations
/// Retrieves bitmap resources
wxBitmap GetBitmapResource( const wxString& name );
/// Retrieves icon resources
wxIcon GetIconResource( const wxString& name );
////@end S7 member function declarations
/// Should we show tooltips?
static bool ShowToolTips();
////@begin S7 member variables
wxPanel* panMain;
wxBoxSizer* szMain;
wxDirPickerCtrl* dpkDestination;
wxTextCtrl* txtBasename;
////@end S7 member variables
};
#endif
// _S7_H_

149
Resources/UI/S7/s7app.cpp Normal file
View File

@@ -0,0 +1,149 @@
/////////////////////////////////////////////////////////////////////////////
// Name: s7app.cpp
// Purpose:
// Author: Saleem EDAH-TALLY
// Modified by:
// Created: lun. 16 juin 2025 22:41:03
// 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 <globals.h>
#include "s7app.h"
#include <XS7.h>
////@begin XPM images
////@end XPM images
/*
* Application instance implementation
*/
////@begin implement app
IMPLEMENT_APP( S7App )
////@end implement app
/*
* S7App type definition
*/
IMPLEMENT_CLASS( S7App, wxApp )
/*
* S7App event table definition
*/
BEGIN_EVENT_TABLE( S7App, wxApp )
////@begin S7App event table entries
////@end S7App event table entries
END_EVENT_TABLE()
/*
* Constructor for S7App
*/
S7App::S7App()
{
Init();
}
/*
* Member initialisation
*/
void S7App::Init()
{
////@begin S7App member initialisation
////@end S7App member initialisation
}
/*
* Initialisation for S7App
*/
bool S7App::OnInit()
{
////@begin S7App initialisation
// Remove the comment markers above and below this block
// to make permanent changes to the code.
#if wxUSE_XPM
wxImage::AddHandler(new wxXPMHandler);
#endif
#if wxUSE_LIBPNG
wxImage::AddHandler(new wxPNGHandler);
#endif
#if wxUSE_LIBJPEG
wxImage::AddHandler(new wxJPEGHandler);
#endif
#if wxUSE_GIF
wxImage::AddHandler(new wxGIFHandler);
#endif
////@end S7App initialisation
#if wxUSE_LIBTIFF
wxImage::AddHandler(new wxTIFFHandler);
#endif
#if wxUSE_PNM
wxImage::AddHandler(new wxPNMHandler);
#endif
m_locale.Init ( wxLANGUAGE_DEFAULT );
m_locale.AddCatalog ( _APPNAME_ );
SetAppName(_APPNAME_);
wxTranslations * translations = wxTranslations::Get();
if (translations)
{
translations->AddStdCatalog();
translations->AddCatalog(_APPNAME_);
}
XS7 * appWindow = new XS7(nullptr);
SetTopWindow(appWindow);
appWindow->Show ( false );
bool res = appWindow->ParseCmdLine();
if ( res )
{
appWindow->Setup();
appWindow->Show();
}
return res;
}
/*
* Cleanup for S7App
*/
int S7App::OnExit()
{
////@begin S7App cleanup
return wxApp::OnExit();
////@end S7App cleanup
}

80
Resources/UI/S7/s7app.h Normal file
View File

@@ -0,0 +1,80 @@
/////////////////////////////////////////////////////////////////////////////
// Name: s7app.h
// Purpose:
// Author: Saleem EDAH-TALLY
// Modified by:
// Created: lun. 16 juin 2025 22:41:03
// RCS-ID:
// Copyright: Copyright Saleem EDAH-TALLY. All rights reserved.
// Licence:
/////////////////////////////////////////////////////////////////////////////
#ifndef _S7APP_H_
#define _S7APP_H_
/*!
* Includes
*/
////@begin includes
#include "wx/image.h"
////@end includes
/*!
* Forward declarations
*/
////@begin forward declarations
////@end forward declarations
/*!
* Control identifiers
*/
////@begin control identifiers
////@end control identifiers
/*!
* S7App class declaration
*/
class S7App: public wxApp
{
DECLARE_CLASS( S7App )
DECLARE_EVENT_TABLE()
public:
/// Constructor
S7App();
void Init();
/// Initialises the application
virtual bool OnInit();
/// Called on exit
virtual int OnExit();
////@begin S7App event handler declarations
////@end S7App event handler declarations
////@begin S7App member function declarations
////@end S7App member function declarations
////@begin S7App member variables
////@end S7App member variables
private:
wxLocale m_locale;
};
/*!
* Application instance declaration
*/
////@begin declare app
DECLARE_APP(S7App)
////@end declare app
#endif
// _S7APP_H_

View File

@@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.5)
project(MinimalUtilities)
find_package(wxWidgets COMPONENTS base core CONFIG REQUIRED)
add_library(minutils STATIC
ConfigEditorPopup.cpp
MiscTools.cpp
PopupTransientWindow.cpp
TimeredStatusBar.cpp
XClientData.hpp
)
target_link_libraries(minutils
${wxWidgets_LIBRARIES}
)

View File

@@ -0,0 +1,123 @@
/*
* File: ConfigEditorPopup.cpp
* Author: Saleem EDAH-TALLY - nmset@yandex.com
* License: CeCILL-C
* Copyright Saleem EDAH-TALLY - © 2017
*
* Created on 4 mars 2017, 19:06
*/
#include "ConfigEditorPopup.h"
#include "MiscTools.h"
ConfigEditorPopup::ConfigEditorPopup ( wxWindow * parent, wxFileConfig* config )
{
m_owner = parent;
m_config = config;
}
ConfigEditorPopup::~ConfigEditorPopup() = default;
PopupTransientWindow* ConfigEditorPopup::CreatePopup()
{
wxASSERT_MSG ( ( m_config != nullptr ),_T("CONFIG IS nullptr") );
m_popup = new PopupTransientWindow ( m_owner );
m_pan = new wxPanel ( m_popup );
m_flxsz = new wxFlexGridSizer ( 0, 2, 0, 0 );
m_pan->SetSizer ( m_flxsz );
m_flxsz->AddGrowableCol ( 1 );
m_popup->Bind ( wxEVT_DESTROY, &ConfigEditorPopup::OnPopupDestroy, this );
return m_popup;
}
wxCheckBox* ConfigEditorPopup::AddCheckBox ( const wxString& label, const wxString& configPath )
{
wxASSERT_MSG ( ( m_config != nullptr ),_T("CONFIG IS nullptr") );
wxString * cPath = new wxString ( configPath );
wxStaticText * lbl = new wxStaticText ( m_pan, wxID_ANY, label );
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->SetClientData ( cPath );
cb->Bind ( wxEVT_DESTROY, &ConfigEditorPopup::OnControlDestroy, this );
return cb;
}
wxSpinCtrl* ConfigEditorPopup::AddSpinCtrl ( const wxString& label, const wxString& configPath )
{
wxASSERT_MSG ( ( m_config != nullptr ),_T("CONFIG IS nullptr") );
wxString * cPath = new wxString ( configPath );
wxStaticText * lbl = new wxStaticText ( m_pan, wxID_ANY, label );
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->SetClientData ( cPath );
spn->Bind ( wxEVT_DESTROY, &ConfigEditorPopup::OnControlDestroy, this );
return spn;
}
wxTextCtrl* ConfigEditorPopup::AddTextCtrl ( const wxString& label, const wxString& configPath )
{
wxASSERT_MSG ( ( m_config != nullptr ),_T("CONFIG IS nullptr") );
wxString * cPath = new wxString ( configPath );
wxStaticText * lbl = new wxStaticText ( m_pan, wxID_ANY, label );
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->SetClientData ( cPath );
txt->Bind ( wxEVT_DESTROY, &ConfigEditorPopup::OnControlDestroy, this );
return txt;
}
void ConfigEditorPopup::OnControlDestroy ( wxWindowDestroyEvent& evt )
{
//cout << "ConfigEditorPopup::OnControlDestroy" << endl;
evt.Skip();
wxControl * ctrl = static_cast<wxControl*> ( evt.GetEventObject() );
if ( !ctrl ) return;
const wxString className ( ctrl->GetClassInfo()->GetClassName() );
const wxString * cPath = static_cast<wxString*> ( ctrl->GetClientData() );
if ( className ==_T("wxCheckBox") )
{
wxCheckBox * cb = static_cast<wxCheckBox*> ( evt.GetEventObject() );
m_config->Write ( *cPath, cb->GetValue() );
}
if ( className ==_T("wxSpinCtrl") )
{
wxSpinCtrl * spn = static_cast<wxSpinCtrl*> ( evt.GetEventObject() );
m_config->Write ( *cPath, spn->GetValue() );
}
if ( className ==_T("wxTextCtrl") )
{
wxTextCtrl * txt = static_cast<wxTextCtrl*> ( evt.GetEventObject() );
if ( txt->IsEmpty() )
{
m_config->DeleteEntry ( *cPath, true );
}
else
{
m_config->Write ( *cPath, txt->GetValue() );
}
}
m_config->Flush();
delete cPath;
}
void ConfigEditorPopup::OnPopupDestroy ( wxWindowDestroyEvent& evt )
{
//cout << "ConfigEditorPopup::OnPopupDestroy" << endl;
evt.Skip();
}
void ConfigEditorPopup::ShowPopup()
{
m_pan->GetSizer()->SetSizeHints ( m_pan );
MiscTools::ShowTransientPopup ( m_popup, m_pan, m_pan->GetSize().GetWidth(), m_pan->GetSize().GetHeight() );
}

View File

@@ -0,0 +1,46 @@
/*
* File: ConfigEditorPopup.h
* Author: Saleem EDAH-TALLY - nmset@yandex.com
* License: CeCILL-C
* Copyright Saleem EDAH-TALLY - © 2017
*
* Created on 4 mars 2017, 19:06
*/
#ifndef CONFIGEDITORPOPUP_H
#define CONFIGEDITORPOPUP_H
#include <wx/wx.h>
#include <wx/config.h>
#include "PopupTransientWindow.h"
#include <wx/spinctrl.h>
/**
* Edits wxConfig keys using check boxes, text and spin controls, with supplied
* paths. When a control is created, it reads the values. When it is destroyed,
* it saves back the value.
*/
class ConfigEditorPopup
{
public:
ConfigEditorPopup ( wxWindow * parent, wxConfig * config );
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 );
private:
wxConfig * m_config = nullptr;
wxWeakRef<wxWindow> m_owner = nullptr;
wxFlexGridSizer * m_flxsz = nullptr;
PopupTransientWindow * m_popup = nullptr;
wxPanel * m_pan = nullptr;
void OnControlDestroy ( wxWindowDestroyEvent& evt );
void OnPopupDestroy ( wxWindowDestroyEvent& evt );
};
#endif /* CONFIGEDITORPOPUP_H */

View File

@@ -0,0 +1,132 @@
/*
* File: MiscTools.cpp
* Author: Saleem EDAH-TALLY - nmset@yandex.com
* License: CeCILL-C
* Copyright Saleem EDAH-TALLY - © 2017
*
* Created on 4 mars 2017, 11:40
*/
#include "MiscTools.h"
#include <wx/notifmsg.h>
#include <wx/msgdlg.h>
MiscTools::MiscTools()
{
}
MiscTools::~MiscTools()
{
}
/*
* Under this... thing called Wayland, GetPosition() returns (0, 0)!
* Use "GDK_BACKEND=x11".
*/
void MiscTools::SaveSizePos(wxConfig* config, wxWindow* wind, const wxString& pathPrefix)
{
wxASSERT_MSG ( ( config != nullptr ),_T("config IS nullptr") );
wxASSERT_MSG ( ( wind != nullptr ),_T("wind IS nullptr") );
config->Write ( pathPrefix +_T("/Width"), wind->GetSize().GetWidth() );
config->Write ( pathPrefix +_T("/Height"), wind->GetSize().GetHeight() );
config->Write ( pathPrefix +_T("/X"), wind->GetPosition().x );
config->Write ( pathPrefix +_T("/Y"), wind->GetPosition().y );
config->Flush();
}
void MiscTools::RestoreSizePos ( wxConfig* config, wxWindow* wind, const wxString& pathPrefix )
{
wxASSERT_MSG ( ( config != nullptr ),_T("config IS nullptr") );
wxASSERT_MSG ( ( wind != nullptr ),_T("wind IS nullptr") );
long w = 0, h = 0;
config->Read ( pathPrefix +_T("/Width"), &w );
config->Read ( pathPrefix +_T("/Height"), &h );
if ( w && h )
{
wind->SetSize ( w, h );
}
wxScreenDC dc;
const int screenWidth = dc.GetSize().GetWidth();
const int screenHeight = dc.GetSize().GetHeight();
if (screenWidth && screenHeight)
{
long x = 0, y = 0;
bool res = config->Read ( pathPrefix +_T("/X"), &x );
res = config->Read ( pathPrefix +_T("/Y"), &y );
if ( !res )
{
wind->Centre();
return;
}
wind->SetPosition ( wxPoint ( x, y ) );
}
}
void MiscTools::ShowTransientPopup ( wxPopupTransientWindow* p, wxWindow* content,
const int width, const int height )
{
wxASSERT_MSG ( ( p != nullptr ),_T("p IS nullptr") );
wxASSERT_MSG ( ( content != nullptr ),_T("content IS nullptr") );
wxBoxSizer * sz = new wxBoxSizer ( wxVERTICAL );
p->SetSizer ( sz );
int popupWidth = width;
int popupHeight = height;
if ( width < 1 )
popupWidth = content->GetSize().GetWidth();
if ( height < 1 )
popupHeight = content->GetSize().GetHeight();
p->SetSize ( wxSize ( popupWidth, popupHeight ) );
sz->Add ( content, 0, wxGROW | wxALL );
sz->Layout();
/*
* Under Wayland, screen size is invalid (0, 0) with wxScreenDC.
* wxVideoMode obtained from wxApp doesn't help, it's even worse.
* Using GDK_BACKEND="x11" fixes the placement issue with wxScreenDC,
* but not with wxVideoMode.
*/
wxScreenDC dc;
const int screenWidth = dc.GetSize().GetWidth();
const int screenHeight = dc.GetSize().GetHeight();
const wxPoint mousePos = wxGetMousePosition();
wxPoint dest ( mousePos.x, mousePos.y );
if (screenWidth && screenHeight)
{
if ( mousePos.x > ( screenWidth - popupWidth ) ) dest.x = ( screenWidth - popupWidth );
if ( mousePos.y > ( screenHeight - popupHeight ) ) dest.y = ( screenHeight - popupHeight );
if ( popupWidth > screenWidth ) dest.x = 0;
if ( popupHeight > screenHeight ) dest.y = 0;
}
p->SetPosition ( dest );
p->Popup();
}
void MiscTools::MessageBox ( const wxString& msg, const bool notify )
{
wxApp * app = static_cast<wxApp*> ( wxApp::GetInstance() );
if ( !notify )
{
wxMessageBox ( msg, app->GetAppName(), wxOK, app->GetTopWindow() );
}
else
{
wxNotificationMessage notifMsg ( app->GetAppName(), msg, app->GetTopWindow() );
notifMsg.Show();
}
}
wxTextValidator* MiscTools::MakeFileNameValidator ( bool excludeSpace )
{
wxTextValidator * tval = new wxTextValidator ( wxFILTER_EXCLUDE_CHAR_LIST );
const wxString xcl = wxFileName::GetForbiddenChars();
wxArrayString arr;
for ( unsigned short i = 0; i < xcl.Length(); i++ )
{
arr.Add ( xcl.GetChar ( i ) );
}
if ( excludeSpace ) arr.Add (_T(" ") );
arr.Add ( wxFileName::GetPathSeparator() );
tval->SetExcludes ( arr );
return tval;
}

View File

@@ -0,0 +1,60 @@
/*
* File: MiscTools.h
* Author: Saleem EDAH-TALLY - nmset@yandex.com
* License: CeCILL-C
* Copyright Saleem EDAH-TALLY - © 2017
*
* Created on 4 mars 2017, 11:40
*/
#ifndef MISCTOOLS_H
#define MISCTOOLS_H
#include <wx/wx.h>
#include <wx/config.h>
#include <wx/popupwin.h>
/**
* Miscellaneous static functions.
*/
class MiscTools
{
public:
MiscTools();
virtual ~MiscTools();
/**
* Saves size and position of a wxWindow.
*/
static void SaveSizePos ( wxConfig * config, wxWindow * wind, const wxString& pathPrefix );
/**
* Restores size and position of a wxWindow.
*/
static void RestoreSizePos ( wxConfig * config, wxWindow * wind, const wxString& pathPrefix );
/**
* Shows a transient popup at mouse cursor.
* Position the popup relative to screen edges and popup dimensions,
* if possible.
*/
static void ShowTransientPopup ( wxPopupTransientWindow * p, wxWindow* content,
const int width = 0, const int height = 0 );
/**
* Shows a message as a modal dialog or as a notification.
*/
static void MessageBox ( const wxString& msg, const bool notify = false );
/**
* Creates a validator excluding file name forbidden characters, path
* separator and optionally space character.
*/
static wxTextValidator* MakeFileNameValidator ( bool excludeSpace = true );
private:
};
#endif /* MISCTOOLS_H */

View File

@@ -0,0 +1,29 @@
/*
* File: PopupTransientWindow.cpp
* Author: Saleem EDAH-TALLY - nmset@yandex.com
* License: CeCILL-C
* Copyright Saleem EDAH-TALLY - © 2017
*
* Created on 3 mars 2017, 19:45
*/
#include "PopupTransientWindow.h"
IMPLEMENT_CLASS ( PopupTransientWindow, wxPopupTransientWindow )
PopupTransientWindow::PopupTransientWindow()
: wxPopupTransientWindow()
{}
PopupTransientWindow::PopupTransientWindow ( wxWindow* parent, int flags )
: wxPopupTransientWindow ( parent, flags )
{}
PopupTransientWindow::~PopupTransientWindow()
{}
void PopupTransientWindow::Dismiss()
{
Destroy();
}

View File

@@ -0,0 +1,33 @@
/*
* File: PopupTransientWindow.h
* Author: Saleem EDAH-TALLY - nmset@yandex.com
* License: CeCILL-C
* Copyright Saleem EDAH-TALLY - © 2017
*
* Created on 3 mars 2017, 19:45
*/
#ifndef POPUPTRANSIENTWINDOW_H
#define POPUPTRANSIENTWINDOW_H
#include <wx/wx.h>
#include <wx/popupwin.h>
/** No further functionality provided here on top of wxPopupTransientWindow.\n
* Just calls Destroy() on dismiss.
*/
class PopupTransientWindow : public wxPopupTransientWindow
{
DECLARE_CLASS ( PopupTransientWindow )
public:
PopupTransientWindow();
PopupTransientWindow ( wxWindow *parent, int flags = wxBORDER_NONE );
virtual ~PopupTransientWindow();
void Dismiss() override;
private:
};
#endif /* POPUPTRANSIENTWINDOW_H */

View File

@@ -0,0 +1,58 @@
/*
* File: TimeredStatusBar.cpp
* Author: Saleem EDAH-TALLY - nmset@yandex.com
* License: CeCILL-C
* Copyright Saleem EDAH-TALLY - © 2017
*
* Created on April 22, 2018, 3:55 PM
*/
#include "TimeredStatusBar.h"
TimeredStatusBar::TimeredStatusBar ( wxWindow * parent)
: wxStatusBar ( parent, wxID_ANY )
{
m_numFields = 3;
/* We use default reasonable widths for CAPS and NUM. */
const int widths[3] = {-1, 70, 70};
SetFieldsCount ( m_numFields, widths );
m_timer.Stop();
m_timer.SetOwner ( this );
Bind ( wxEVT_TIMER, &TimeredStatusBar::OnTimer, this );
Bind ( wxEVT_IDLE, &TimeredStatusBar::OnIdle, this );
}
TimeredStatusBar::~TimeredStatusBar()
{
}
void TimeredStatusBar::SetTransientText ( const wxString& text, int duration )
{
wxStatusBar::SetStatusText ( text, 0 );
m_timer.Start ( duration, wxTIMER_ONE_SHOT );
}
void TimeredStatusBar::OnTimer ( wxTimerEvent& evt )
{
wxStatusBar::SetStatusText ( wxEmptyString, 0 );
}
void TimeredStatusBar::OnIdle ( wxIdleEvent& evt )
{
if ( wxGetKeyState ( WXK_NUMLOCK ) )
{
SetStatusText (_("NUM"), m_numFields - 2 );
}
else
{
SetStatusText ( wxEmptyString, m_numFields - 2 );
}
if ( wxGetKeyState ( WXK_CAPITAL ) )
{
SetStatusText (_("CAPS"), m_numFields - 1 );
}
else
{
SetStatusText ( wxEmptyString, m_numFields - 1 );
}
}

View File

@@ -0,0 +1,38 @@
/*
* File: TimeredStatusBar.h
* Author: Saleem EDAH-TALLY - nmset@yandex.com
* License: CeCILL-C
* Copyright Saleem EDAH-TALLY - © 2017
*
* Created on April 22, 2018, 3:55 PM
*/
#ifndef TIMEREDSTATUSBAR_H
#define TIMEREDSTATUSBAR_H
#include <wx/wx.h>
#include <wx/timer.h>
/**
* We want to show a transient message.\n
* The widget show also CAPS and NUM status.\n
*/
class TimeredStatusBar : public wxStatusBar
{
public:
TimeredStatusBar ( wxWindow * parent );
virtual ~TimeredStatusBar();
/**
* Called by the application to display 'text' transiently.
*/
void SetTransientText ( const wxString& text, int duration = 3000);
void OnIdle ( wxIdleEvent& evt );
private:
uint m_numFields = 3;
wxTimer m_timer;
void OnTimer ( wxTimerEvent& evt );
};
#endif /* TIMEREDSTATUSBAR_H */

View File

@@ -0,0 +1,35 @@
/*
* File: XClientData.hpp
* Author: Saleem Edah-Tally - nmset@yandex.com
* License: CeCILL-C
* Copyright Saleem Edah-Tally - © 2024
*
* Created on ?
*/
#ifndef XCLIENTDATA_H
#define XCLIENTDATA_H
#include <wx/wx.h>
/**
* For the available scanner list.
*/
class XClientData : public wxClientData
{
public :
XClientData ( const wxVariant& data ) : wxClientData()
{
m_data = data;
}
virtual ~XClientData() {};
wxVariant GetData() const
{
return m_data;
}
private:
wxVariant m_data;
};
#endif // XCLIENTDATA_H

BIN
S7_01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

163
XS7.cpp Normal file
View File

@@ -0,0 +1,163 @@
// /*
// * File: XS7.cpp
// * Author: Saleem Edah-Tally - nmset@yandex.com
// * License : CeCILL
// * Copyright Saleem Edah-Tally - © 2023
// *
// * Created on 18 06 2025, 22:42
// */
#include "XS7.h"
#include "globals.h"
#include "TimeredStatusBar.h"
#include "MiscTools.h"
#include "page.xpm"
#include <wx/stdpaths.h>
#include <wx/cmdline.h>
using namespace std;
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()
{
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);
TimeredStatusBar * sb = new TimeredStatusBar(this);
SetStatusBar(sb);
m_insaneWidget = new XInsaneWidget(panMain, sb, m_config.get());
szMain->Add(m_insaneWidget, 0, wxGROW | wxALL);
dpkDestination->Bind ( wxEVT_DIRPICKER_CHANGED, &XS7::OnDpkRepositoryChange, this );
dpkDestination->GetTextCtrl()->Bind ( wxEVT_LEFT_DCLICK, &XS7::OnDpkDoubleClick, this );
Bind(wxEVT_CHAR_HOOK, &XS7::OnAppKeyPressed, this);
m_insaneWidget->lblNewDoc->Bind ( wxEVT_LEFT_UP, &XS7::OnNewDocLeftClick, this );
dpkDestination->SetPath(m_config->Read("/Docroot"));
txtBasename->SetValidator(*MiscTools::MakeFileNameValidator(false));
txtBasename->Bind(wxEVT_LEFT_UP, &XS7::OnAbout, this);
MiscTools::RestoreSizePos(m_config.get(), this, wxString("/" + wxString(_APPNAME_)));
S7::SetTitle(wxString(_APPNAME_) + " - version " + to_string(_APPVERSION_));
SetIcon(wxICON(page));
wxString fixedTip = _("'Shift + left' click to generate a new destination file name.");
fixedTip += _T("\n") + m_insaneWidget->lblNewDoc->GetToolTipText();
m_insaneWidget->lblNewDoc->SetToolTip(fixedTip);
}
XS7::~XS7()
{
if (m_config)
MiscTools::SaveSizePos(m_config.get(), this, wxString("/") + _APPNAME_);
}
void XS7::OnDpkRepositoryChange ( wxFileDirPickerEvent& evt )
{
m_config->Write ( _T ( "/DocRoot" ), dpkDestination->GetPath() );
m_config->Flush();
evt.Skip();
}
void XS7::OnDpkDoubleClick ( wxMouseEvent& evt )
{
const wxString command = _T ( "xdg-open " ) + dpkDestination->GetPath();
if ( wxExecute ( command ) == 0 )
{
const wxString msg = _( "Could not launch default file manager" );
MiscTools::MessageBox( msg, true );
cerr << msg << endl;
}
evt.Skip();
}
void XS7::OnAppKeyPressed(wxKeyEvent& evt)
{
if (evt.GetKeyCode() == WXK_ESCAPE)
if (m_insaneWidget)
m_insaneWidget->CancelScanning();
evt.Skip();
}
void XS7::OnNewDocLeftClick ( wxMouseEvent& evt )
{
if ( !evt.ShiftDown() )
{
evt.Skip();
return;
}
if ( !dpkDestination->GetDirName().Exists() )
{
const wxString msg = _( "Invalid folder name." );
MiscTools::MessageBox ( msg, true );
cerr << msg << endl;
evt.Skip();
return;
}
if ( txtBasename->GetValue().IsEmpty() )
{
const wxString msg = _( "Invalid file basename." );
MiscTools::MessageBox ( msg, true );
cerr << msg << endl;
evt.Skip();
return;
}
const wxString filepath = dpkDestination->GetPath() + wxFileName::GetPathSeparator() + txtBasename->GetValue();
m_insaneWidget->txtNewDoc->SetValue ( filepath );
m_insaneWidget->ResetScanProject();
evt.Skip();
}
void XS7::OnAbout(wxMouseEvent& evt)
{
if (!evt.ControlDown())
{
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." );
MiscTools::MessageBox(msg);
evt.Skip();
}

44
XS7.h Normal file
View File

@@ -0,0 +1,44 @@
// /*
// * File: XS7.h
// * Author: Saleem Edah-Tally - nmset@yandex.com
// * License : CeCILL
// * Copyright Saleem Edah-Tally - © 2023
// *
// * Created on 18 06 2025, 22:42
// */
#ifndef XS7_H
#define XS7_H
#include "s7.h"
#include <wx/wx.h>
#include <XInsaneWidget.h>
#include <fstream>
class XS7 : public S7
{
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();
private:
std::unique_ptr<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 OnAbout(wxMouseEvent& evt);
};
#endif // XS7_H

18
globals.h Normal file
View File

@@ -0,0 +1,18 @@
/*
* File: globals.h
* Author: Saleem EDAH-TALLY - nmset@yandex.com
* License : CeCILL
* Copyright Saleem EDAH-TALLY - © 2017
*
* Created on 27 février 2017, 22:28
*/
#ifndef GLOBALS_H
#define GLOBALS_H
#define _APPNAME_ "S7"
#define _APPVERSION_ 1
#define _INSANEWIDGET_NAME "InsaneWidget"
#endif /* GLOBALS_H */

154
page.xpm Normal file
View File

@@ -0,0 +1,154 @@
/* XPM */
static char * page_xpm[] = {
"128 128 23 1",
" c None",
". c #5C889D",
"+ c #5C869E",
"@ c #5C889C",
"# c #5E889E",
"$ c #5E879D",
"% c #5D889C",
"& c #5E869D",
"* c #5E889C",
"= c #5E879C",
"- c #5D879D",
"; c #5E869C",
"> c #5C869C",
", c #5E889D",
"' c #5C889E",
") c #5E879E",
"! c #5D869C",
"~ c #5E869E",
"{ c #5D889D",
"] c #5D889E",
"^ c #5D869E",
"/ c #5D879E",
"( c #5C879C",
" .+@+ ",
" #######. ",
" #########+ ",
" #### $### ",
" %##& *##$ ",
" =## ##% ",
" -@############ ############; ",
" ##############> ;############## ",
" #################*,'###############* ",
" #################################### ",
" ##################) #################################### @################## ",
" ####################) #################################### @###################! ",
" #####################) #################################### @####################@ ",
" ######################) #################################### @#####################~ ",
" ######################) #################################### @###################### ",
" ;######################) #################################### @######################{ ",
" '######################) #################################### @####################### ",
" #######################) #################################### @####################### ",
" #########; #################################### ~######### ",
" ########+ ;##################################+ '######## ",
" ########) '######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) )$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$, {######## ",
" ########) #################### #############################* {######## ",
" ########) #################### #############################] {######## ",
" ########) #################### {######## ",
" ########) #################### {######## ",
" ########) #################### {######## ",
" ########) #################### {######## ",
" ########) #################### {######## ",
" ########) #################### {######## ",
" ########) #################### *~~~~~~~~~~~~~~~~~~~~~~~~~~~# {######## ",
" ########) #################### #############################& {######## ",
" ########) #################### '############################# {######## ",
" ########) #################### {######## ",
" ########) #################### {######## ",
" ########) #################### {######## ",
" ########) #################### {######## ",
" ########) #################### {######## ",
" ########) #################### {######## ",
" ########) #################### ^############################# {######## ",
" ########) #################### ############################## {######## ",
" ########) ,##################! ~###########################; {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) ######################################################) {######## ",
" ########) ########################################################, {######## ",
" ########) ########################################################* {######## ",
" ########) ))))))))))))))))))))))))))))))))))))))))))))))))))))))- {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######################################################; {######## ",
" ########) ########################################################' {######## ",
" ########) ########################################################/ {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) (######################################################' {######## ",
" ########) ######################################################### {######## ",
" ########) ;####################################################### {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) ############################ {######## ",
" ########) ############################ {######## ",
" ########) ;~~~~~~~~~~~~~~~~~~~~~~~~~~, {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) {######## ",
" ########) )######## ",
" ########) /'###############^ (######## ",
" ########) $################^ (######### ",
" ########) ,###############^ (########## ",
" ########) )##############^ (########### ",
" ########) )#############^ (############ ",
" ########) )############^ (############# ",
" ########) )###########^ (############## ",
" ########) )##########^ (############### ",
" ########) )#########^ (################ ",
" ########) )########^ (################# ",
" ########) )#######^ (################## ",
" ########) )######^ (################### ",
" ########) )#####^ (#################### ",
" ########) )####^ (##################### ",
" ########) )###^ (###################### ",
" ########) )##^ (####################### ",
" ########) )#^ (######################## ",
" ########) )^ (######################### ",
" ########) # (########################## ",
" ########) (########################### ",
" ########{ (############################ ",
" ######### (############################# ",
" #########) (############################## ",
" ##########~;..............................................*############################### ",
" ########################################################################################## ",
" ~######################################################################################### ",
" ^########################################################################################{ ",
" +######################################################################################; ",
" ###################################################################################### ",
" $####################################################################################{ ",
" +################################################################################~ ",
" #;+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;*#& "};