From ef6f25ef272ec451b47d319f3345553d1949520f Mon Sep 17 00:00:00 2001 From: Saleem Edah-Tally Date: Sat, 28 Jun 2025 17:40:42 +0200 Subject: [PATCH] Initial commit. --- CMakeLists.txt | 30 + README.md | 55 + Resources/CeCILL-C_V1-en.txt | 517 +++++ Resources/CeCILL_V2.1-en.txt | 519 ++++++ .../InsaneWidget/CMake/FindLibInsane.cmake | 29 + Resources/InsaneWidget/CMake/FindPaper.cmake | 23 + Resources/InsaneWidget/CMake/FindPoDoFo.cmake | 27 + Resources/InsaneWidget/CMakeLists.txt | 33 + Resources/InsaneWidget/Common.h | 30 + Resources/InsaneWidget/InsaneWorker.cpp | 582 ++++++ Resources/InsaneWidget/InsaneWorker.h | 128 ++ Resources/InsaneWidget/PixelToImageWriter.cpp | 61 + Resources/InsaneWidget/PixelToImageWriter.h | 28 + Resources/InsaneWidget/PixelToPdfWriter.cpp | 83 + Resources/InsaneWidget/PixelToPdfWriter.h | 49 + Resources/InsaneWidget/UI/InsaneWidget.cpp | 180 ++ Resources/InsaneWidget/UI/InsaneWidget.h | 97 + Resources/InsaneWidget/UI/InsaneWidget.pjd | 1659 +++++++++++++++++ Resources/InsaneWidget/UI/InsaneWidget.rc | 1 + Resources/InsaneWidget/UI/ScannerWidget.cpp | 237 +++ Resources/InsaneWidget/UI/ScannerWidget.h | 116 ++ Resources/InsaneWidget/XInsaneWidget.cpp | 412 ++++ Resources/InsaneWidget/XInsaneWidget.h | 81 + Resources/InsaneWidget/XScannerWidget.cpp | 318 ++++ Resources/InsaneWidget/XScannerWidget.h | 120 ++ Resources/Lokalize/0_getstrings.sh | 21 + Resources/Lokalize/2_makemo.sh | 14 + Resources/Lokalize/fr/CB7.mo | Bin 0 -> 8899 bytes Resources/Lokalize/fr/S7.po | 302 +++ .../Lokalize/fr/S7.po.bak-2025-06-26-22:47:15 | 298 +++ Resources/PKGBUILD | 31 + Resources/UI/S7/S7.pjd | 622 ++++++ Resources/UI/S7/S7.rc | 1 + Resources/UI/S7/s7.cpp | 172 ++ Resources/UI/S7/s7.h | 101 + Resources/UI/S7/s7app.cpp | 149 ++ Resources/UI/S7/s7app.h | 80 + Resources/Utilities/CMakeLists.txt | 17 + Resources/Utilities/ConfigEditorPopup.cpp | 123 ++ Resources/Utilities/ConfigEditorPopup.h | 46 + Resources/Utilities/MiscTools.cpp | 132 ++ Resources/Utilities/MiscTools.h | 60 + Resources/Utilities/PopupTransientWindow.cpp | 29 + Resources/Utilities/PopupTransientWindow.h | 33 + Resources/Utilities/TimeredStatusBar.cpp | 58 + Resources/Utilities/TimeredStatusBar.h | 38 + Resources/Utilities/XClientData.hpp | 35 + S7_01.png | Bin 0 -> 45191 bytes XS7.cpp | 163 ++ XS7.h | 44 + globals.h | 18 + page.xpm | 154 ++ 52 files changed, 8156 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 Resources/CeCILL-C_V1-en.txt create mode 100644 Resources/CeCILL_V2.1-en.txt create mode 100644 Resources/InsaneWidget/CMake/FindLibInsane.cmake create mode 100644 Resources/InsaneWidget/CMake/FindPaper.cmake create mode 100644 Resources/InsaneWidget/CMake/FindPoDoFo.cmake create mode 100644 Resources/InsaneWidget/CMakeLists.txt create mode 100644 Resources/InsaneWidget/Common.h create mode 100644 Resources/InsaneWidget/InsaneWorker.cpp create mode 100644 Resources/InsaneWidget/InsaneWorker.h create mode 100644 Resources/InsaneWidget/PixelToImageWriter.cpp create mode 100644 Resources/InsaneWidget/PixelToImageWriter.h create mode 100644 Resources/InsaneWidget/PixelToPdfWriter.cpp create mode 100644 Resources/InsaneWidget/PixelToPdfWriter.h create mode 100644 Resources/InsaneWidget/UI/InsaneWidget.cpp create mode 100644 Resources/InsaneWidget/UI/InsaneWidget.h create mode 100644 Resources/InsaneWidget/UI/InsaneWidget.pjd create mode 100644 Resources/InsaneWidget/UI/InsaneWidget.rc create mode 100644 Resources/InsaneWidget/UI/ScannerWidget.cpp create mode 100644 Resources/InsaneWidget/UI/ScannerWidget.h create mode 100644 Resources/InsaneWidget/XInsaneWidget.cpp create mode 100644 Resources/InsaneWidget/XInsaneWidget.h create mode 100644 Resources/InsaneWidget/XScannerWidget.cpp create mode 100644 Resources/InsaneWidget/XScannerWidget.h create mode 100755 Resources/Lokalize/0_getstrings.sh create mode 100755 Resources/Lokalize/2_makemo.sh create mode 100644 Resources/Lokalize/fr/CB7.mo create mode 100644 Resources/Lokalize/fr/S7.po create mode 100644 Resources/Lokalize/fr/S7.po.bak-2025-06-26-22:47:15 create mode 100644 Resources/PKGBUILD create mode 100644 Resources/UI/S7/S7.pjd create mode 100644 Resources/UI/S7/S7.rc create mode 100644 Resources/UI/S7/s7.cpp create mode 100644 Resources/UI/S7/s7.h create mode 100644 Resources/UI/S7/s7app.cpp create mode 100644 Resources/UI/S7/s7app.h create mode 100644 Resources/Utilities/CMakeLists.txt create mode 100644 Resources/Utilities/ConfigEditorPopup.cpp create mode 100644 Resources/Utilities/ConfigEditorPopup.h create mode 100644 Resources/Utilities/MiscTools.cpp create mode 100644 Resources/Utilities/MiscTools.h create mode 100644 Resources/Utilities/PopupTransientWindow.cpp create mode 100644 Resources/Utilities/PopupTransientWindow.h create mode 100644 Resources/Utilities/TimeredStatusBar.cpp create mode 100644 Resources/Utilities/TimeredStatusBar.h create mode 100644 Resources/Utilities/XClientData.hpp create mode 100644 S7_01.png create mode 100644 XS7.cpp create mode 100644 XS7.h create mode 100644 globals.h create mode 100644 page.xpm diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1ec30cb --- /dev/null +++ b/CMakeLists.txt @@ -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} + ) diff --git a/README.md b/README.md new file mode 100644 index 0000000..c62aab7 --- /dev/null +++ b/README.md @@ -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. + + diff --git a/Resources/CeCILL-C_V1-en.txt b/Resources/CeCILL-C_V1-en.txt new file mode 100644 index 0000000..d6eb151 --- /dev/null +++ b/Resources/CeCILL-C_V1-en.txt @@ -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. diff --git a/Resources/CeCILL_V2.1-en.txt b/Resources/CeCILL_V2.1-en.txt new file mode 100644 index 0000000..e7c9f89 --- /dev/null +++ b/Resources/CeCILL_V2.1-en.txt @@ -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. + diff --git a/Resources/InsaneWidget/CMake/FindLibInsane.cmake b/Resources/InsaneWidget/CMake/FindLibInsane.cmake new file mode 100644 index 0000000..5afd758 --- /dev/null +++ b/Resources/InsaneWidget/CMake/FindLibInsane.cmake @@ -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 ) diff --git a/Resources/InsaneWidget/CMake/FindPaper.cmake b/Resources/InsaneWidget/CMake/FindPaper.cmake new file mode 100644 index 0000000..2a21b01 --- /dev/null +++ b/Resources/InsaneWidget/CMake/FindPaper.cmake @@ -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 ) diff --git a/Resources/InsaneWidget/CMake/FindPoDoFo.cmake b/Resources/InsaneWidget/CMake/FindPoDoFo.cmake new file mode 100644 index 0000000..94d2ac8 --- /dev/null +++ b/Resources/InsaneWidget/CMake/FindPoDoFo.cmake @@ -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 ) diff --git a/Resources/InsaneWidget/CMakeLists.txt b/Resources/InsaneWidget/CMakeLists.txt new file mode 100644 index 0000000..31fff91 --- /dev/null +++ b/Resources/InsaneWidget/CMakeLists.txt @@ -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}) diff --git a/Resources/InsaneWidget/Common.h b/Resources/InsaneWidget/Common.h new file mode 100644 index 0000000..d5f1ee8 --- /dev/null +++ b/Resources/InsaneWidget/Common.h @@ -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 +#include + +enum {PDF, PNG, JPEG, TIFF, PNM}; +typedef std::map 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 diff --git a/Resources/InsaneWidget/InsaneWorker.cpp b/Resources/InsaneWidget/InsaneWorker.cpp new file mode 100644 index 0000000..4c66c83 --- /dev/null +++ b/Resources/InsaneWidget/InsaneWorker.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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& 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 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, ¶meters); + 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& 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 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 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) +{} diff --git a/Resources/InsaneWidget/InsaneWorker.h b/Resources/InsaneWidget/InsaneWorker.h new file mode 100644 index 0000000..cc7ed71 --- /dev/null +++ b/Resources/InsaneWidget/InsaneWorker.h @@ -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 +#include +#include + +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 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& 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 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 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& 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 diff --git a/Resources/InsaneWidget/PixelToImageWriter.cpp b/Resources/InsaneWidget/PixelToImageWriter.cpp new file mode 100644 index 0000000..852ef9f --- /dev/null +++ b/Resources/InsaneWidget/PixelToImageWriter.cpp @@ -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 +#include + +using namespace std; + +bool PixelToImageWriter::Convert(const std::string& pixelFilePath, + int imageWidth, int imageHeight, + int outputFormat, wxImage * image) +{ + UpdateExtensionsMap(); + wxImage * outImage = image; + unique_ptr tmpImage; + if (!image) + { + tmpImage = make_unique(); + 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(ifs), istreambuf_iterator()); + + 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; +} diff --git a/Resources/InsaneWidget/PixelToImageWriter.h b/Resources/InsaneWidget/PixelToImageWriter.h new file mode 100644 index 0000000..406f9b5 --- /dev/null +++ b/Resources/InsaneWidget/PixelToImageWriter.h @@ -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 +#include + +/** + * 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 diff --git a/Resources/InsaneWidget/PixelToPdfWriter.cpp b/Resources/InsaneWidget/PixelToPdfWriter.cpp new file mode 100644 index 0000000..f92c2b8 --- /dev/null +++ b/Resources/InsaneWidget/PixelToPdfWriter.cpp @@ -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 +#include + +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(ifs), istreambuf_iterator()); + bufferview bv(content); + + const uint pageNumber = m_doc.GetPages().GetCount(); + std::unique_ptr 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); +} diff --git a/Resources/InsaneWidget/PixelToPdfWriter.h b/Resources/InsaneWidget/PixelToPdfWriter.h new file mode 100644 index 0000000..faa5413 --- /dev/null +++ b/Resources/InsaneWidget/PixelToPdfWriter.h @@ -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 +#include +#include + +/** + * 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 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 diff --git a/Resources/InsaneWidget/UI/InsaneWidget.cpp b/Resources/InsaneWidget/UI/InsaneWidget.cpp new file mode 100644 index 0000000..6d68ed1 --- /dev/null +++ b/Resources/InsaneWidget/UI/InsaneWidget.cpp @@ -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 +} diff --git a/Resources/InsaneWidget/UI/InsaneWidget.h b/Resources/InsaneWidget/UI/InsaneWidget.h new file mode 100644 index 0000000..46c6b02 --- /dev/null +++ b/Resources/InsaneWidget/UI/InsaneWidget.h @@ -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_ diff --git a/Resources/InsaneWidget/UI/InsaneWidget.pjd b/Resources/InsaneWidget/UI/InsaneWidget.pjd new file mode 100644 index 0000000..5fb9654 --- /dev/null +++ b/Resources/InsaneWidget/UI/InsaneWidget.pjd @@ -0,0 +1,1659 @@ + + +
+ 1 + "Application" + "app.h" + "app.cpp" + "Standard" + 0 + 1 + 1 + "" + "<None>" + "Copyright Saleem EDAH-TALLY. All rights reserved." + " /// %BODY% +" + " +/* + * %BODY% + */ + +" + "///////////////////////////////////////////////////////////////////////////// +// Name: %HEADER-FILENAME% +// Purpose: +// Author: %AUTHOR% +// Modified by: +// Created: %DATE% +// RCS-ID: +// Copyright: %COPYRIGHT% +// Licence: +///////////////////////////////////////////////////////////////////////////// + +" + "" + "///////////////////////////////////////////////////////////////////////////// +// Name: %SOURCE-FILENAME% +// Purpose: +// Author: %AUTHOR% +// Modified by: +// Created: %DATE% +// RCS-ID: +// Copyright: %COPYRIGHT% +// 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 + +" + "///////////////////////////////////////////////////////////////////////////// +// Name: %SYMBOLS-FILENAME% +// Purpose: Symbols file +// Author: %AUTHOR% +// Modified by: +// Created: %DATE% +// RCS-ID: +// Copyright: %COPYRIGHT% +// Licence: +///////////////////////////////////////////////////////////////////////////// + +" + "<All platforms>" + "" + "" + 0 + 0 + 0 + 0 + 0 + "" + 0 + 4 + 0 + 1 + 0 + "utf-8" + "" + "AppResources" + "app_resources.h" + "app_resources.cpp" + "" + "" + "utf-8" + "2.9.2" + 0 + "" + 1 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + "Saleem EDAH-TALLY" + " " + 1 + "utf-8" + "" + 0 + 1 + 0 +
+ + + "" + "data-document" + "" + "" + 0 + 1 + 0 + 0 + + "Debug" + "%AUTO%" + "%AUTO%" + "" + "%AUTO%" + "%AUTO%" + "%AUTO%" + "%AUTO%" + "" + "%AUTO%" + 1 + 1 + "%EXECUTABLE%" + "%AUTO%" + "%AUTO%" + "" + "GUI" + "" + "%AUTO%" + 0 + "%AUTO%" + "%AUTO%" + "%AUTO%" + "%AUTO%" + 0 + "%AUTO%" + 4286447616 + "Modular" + "%AUTO%" + "%AUTO%" + "%AUTO%" + 1 + "%AUTO%" + "%AUTO%" + "Default" + "" + "%AUTO%" + "%AUTO%" + "%AUTO%" + "%AUTO%" + "Dynamic" + "Static" + 0 + "" + "Configurations" + 0 + "wxGTK+2" + "config-data-document" + "Unicode" + "Yes" + "No" + "No" + "Yes" + "Yes" + "No" + "Yes" + "builtin" + "Yes" + "Yes" + "Yes" + "Yes" + "%AUTO%" + "%AUTO%" + "%AUTO%" + "%AUTO%" + "%AUTO%" + "%AUTO%" + "%AUTO%" + "%WXVERSION%" + + + + + + + "Projects" + "root-document" + "" + "project" + 1 + 1 + 0 + 1 + + "Windows" + "html-document" + "" + "dialogsfolder" + 1 + 1 + 0 + 1 + + "wbDialogProxy" + "Standard" + 10000 + "" + "dialog" + "" + "" + "itemPanel1" + 0 + 0 + 1 + 0 + "" + "wxPanel" + 1 + "InsaneWidget" + "" + "" + "" + "" + "" + "" + 0 + 1 + "" + 1 + "" + "" + "InsaneWidget.h" + 300 + "" + 0 + "" + "ID_INSANEWIDGET" + 10000 + "InsaneWidget" + "<Any platform>" + "" + "Tiled" + "InsaneWidget" + "" + 400 + "wxPanel" + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + -1 + "" + -1 + "InsaneWidget" + 0 + "dialog-document" + 0 + 0 + 0 + 0 + + "wbBoxSizerProxy" + "" + "sizer" + "itemBoxSizer2" + 0 + 0 + 1 + "" + "Vertical" + "<Any platform>" + 0 + 0 + 0 + 0 + "wxBoxSizer V" + 0 + "dialog-control-document" + + "wbBoxSizerProxy" + "" + "sizer" + "itemBoxSizer1" + 0 + 0 + 1 + "Expand" + "Centre" + 5 + "" + "Horizontal" + "<Any platform>" + 0 + 0 + 1 + 0 + 1 + 0 + 1 + 0 + 1 + "wxBoxSizer H" + 0 + "dialog-control-document" + + "wbStaticTextProxy" + "" + "statictext" + "itemStaticText2" + 0 + 0 + 1 + "Centre" + "Centre" + "" + "wxStaticText" + 5 + "wxStaticText" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + 1 + 1 + "" + "" + "" + -1 + "" + 0 + "ID_NewDoc_LBL" + 10014 + "" + "New" + "lblNewDoc" + "" + "<Any platform>" + 0 + 0 + "'Right' click to define a scan project." + -1 + -1 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + -1 + -1 + "wxStaticText: ID_NewDoc_LBL" + 0 + "dialog-control-document" + + + "wbTextCtrlProxy" + "" + "textctrl" + "itemTextCtrl3" + 0 + 0 + 1 + "Centre" + "Expand" + "" + "wxTextCtrl" + 5 + "wxTextCtrl" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + 1 + 1 + "" + "" + "" + -1 + "" + 0 + "ID_NewDoc_TXT" + 10005 + "" + "" + 0 + "txtNewDoc" + "" + "<Any platform>" + 0 + 1 + "Full path to destination file without the extension; it is determined by the output type." + -1 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + -1 + -1 + "wxTextCtrl: ID_NewDoc_TXT" + 0 + "dialog-control-document" + + + "wbButtonProxy" + "" + "dialogcontrol" + "itemButton4" + 0 + 0 + 1 + "Centre" + "Centre" + "" + "wxButton" + 5 + "wxButton" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + 0 + 1 + 1 + "" + "" + "" + -1 + "" + 0 + "ID_Scan_BTN" + 10006 + "" + "Scan" + "btnScan" + "" + "<Any platform>" + 0 + 0 + "'Left click' to start the scan project. +'Right click' to show the scanner widget." + -1 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + 0 + 1 + 0 + -1 + -1 + "wxButton: ID_Scan_BTN" + 0 + "dialog-control-document" + + + + + + "wbDialogProxy" + "Standard" + 10000 + "" + "dialog" + "" + "" + "itemPanel1" + 0 + 0 + 1 + 0 + "" + "wxPanel" + 1 + "ScannerWidget" + "" + "" + "" + "" + "" + "" + 0 + 1 + "" + 1 + "" + "" + "ScannerWidget.h" + 300 + "" + 0 + "" + "ID_SCANNERWIDGET" + 10000 + "ScannerWidget.cpp" + "<Any platform>" + "" + "Tiled" + "ScannerWidget" + "" + 400 + "wxPanel" + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + -1 + "" + -1 + "ScannerWidget" + 0 + "dialog-document" + 0 + 0 + 0 + 0 + + "wbBoxSizerProxy" + "" + "sizer" + "itemBoxSizer1" + 0 + 0 + 1 + "" + "Vertical" + "<Any platform>" + 0 + 0 + 0 + 0 + "wxBoxSizer V" + 0 + "dialog-control-document" + + "wbFlexGridSizerProxy" + "" + "sizer" + "itemFlexGridSizer2" + 0 + 0 + 1 + "Centre" + "Centre" + 5 + 2 + 0 + "1" + "" + "" + "<Any platform>" + 0 + 0 + 0 + 0 + 1 + 0 + 1 + 0 + 1 + 0 + 1 + "wxFlexGridSizer" + 0 + "dialog-control-document" + + "wbStaticTextProxy" + "" + "statictext" + "itemStaticText3" + 0 + 0 + 1 + "Right" + "Centre" + "" + "wxStaticText" + 5 + "wxStaticText" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + 1 + 1 + "" + "" + "" + -1 + "" + 0 + "wxID_STATIC_IMAGE_TYPE" + 10031 + "" + "Format:" + "lblImageType" + "" + "<Any platform>" + 0 + 0 + "" + -1 + -1 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + -1 + -1 + "wxStaticText: wxID_STATIC_IMAGE_TYPE" + 0 + "dialog-control-document" + + + "wbComboBoxProxy" + "" + "combobox" + "itemComboBox4" + 0 + 0 + 1 + "Left" + "Centre" + "" + "wxComboBox" + 5 + "wxComboBox" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + 1 + 1 + "" + "" + "" + -1 + "" + 0 + "ID_COMBOBOX_IMAGE_TYPE" + 10007 + "" + "" + "cmbOutputType" + "" + "<Any platform>" + 0 + 0 + "" + "Output document format" + -1 + 0 + 1 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 1 + 0 + 0 + 1 + 0 + -1 + -1 + "wxComboBox: ID_COMBOBOX_IMAGE_TYPE" + 0 + "dialog-control-document" + + + "wbBoxSizerProxy" + "" + "sizer" + "itemBoxSizer5" + 0 + 0 + 1 + "Centre" + "Centre" + 5 + "" + "Horizontal" + "<Any platform>" + 0 + 0 + 1 + 0 + 1 + 0 + 1 + 0 + 1 + "wxBoxSizer H" + 0 + "dialog-control-document" + + "wbButtonProxy" + "" + "dialogcontrol" + "itemButton6" + 0 + 0 + 1 + "Centre" + "Centre" + "" + "wxButton" + 5 + "wxButton" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + 0 + 1 + 1 + "" + "" + "" + -1 + "" + 0 + "ID_BUTTON_REFRESH_DEVICES" + 10006 + "" + "↻" + "btnRefreshDevices" + "" + "<Any platform>" + 0 + 0 + "Refresh available devices" + -1 + 0 + 1 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + 0 + 1 + 0 + -1 + -1 + "wxButton: ID_BUTTON_REFRESH_DEVICES" + 0 + "dialog-control-document" + + + "wbStaticTextProxy" + "" + "statictext" + "itemStaticText7" + 0 + 0 + 1 + "Right" + "Centre" + "" + "wxStaticText" + 5 + "wxStaticText" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + 1 + 1 + "" + "" + "" + -1 + "" + 0 + "wxID_STATIC_DEVICES" + 10029 + "" + "Devices:" + "lblDevices" + "" + "<Any platform>" + 0 + 0 + "" + -1 + -1 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + -1 + -1 + "wxStaticText: wxID_STATIC_DEVICES" + 0 + "dialog-control-document" + + + + "wbComboBoxProxy" + "" + "combobox" + "itemComboBox8" + 0 + 0 + 1 + "Expand" + "Centre" + "" + "wxComboBox" + 5 + "wxComboBox" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + 1 + 1 + "" + "" + "" + -1 + "" + 0 + "ID_COMBOBOX_Devices" + 10001 + "" + "" + "cmbDevices" + "" + "<Any platform>" + 0 + 0 + "" + "Available devices" + -1 + 0 + 1 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 1 + 0 + 0 + 1 + 0 + -1 + -1 + "wxComboBox: ID_COMBOBOX_Devices" + 0 + "dialog-control-document" + + + "wbStaticTextProxy" + "" + "statictext" + "itemStaticText9" + 0 + 0 + 1 + "Right" + "Centre" + "" + "wxStaticText" + 5 + "wxStaticText" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + 1 + 1 + "" + "" + "" + -1 + "" + 0 + "wxID_STATIC_SOURCE" + 10028 + "" + "Source:" + "lblSource" + "" + "<Any platform>" + 0 + 0 + "" + -1 + -1 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + -1 + -1 + "wxStaticText: wxID_STATIC_SOURCE" + 0 + "dialog-control-document" + + + "wbComboBoxProxy" + "" + "combobox" + "itemComboBox10" + 0 + 0 + 1 + "Expand" + "Centre" + "" + "wxComboBox" + 5 + "wxComboBox" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + 1 + 1 + "" + "" + "" + -1 + "" + 0 + "ID_COMBOBOX_SOURCE" + 10002 + "" + "" + "cmbSource" + "" + "<Any platform>" + 0 + 0 + "" + "Scan source" + -1 + 0 + 1 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 1 + 0 + 0 + 1 + 0 + -1 + -1 + "wxComboBox: ID_COMBOBOX_SOURCE" + 0 + "dialog-control-document" + + + "wbStaticTextProxy" + "" + "statictext" + "itemStaticText11" + 0 + 0 + 1 + "Right" + "Centre" + "" + "wxStaticText" + 5 + "wxStaticText" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + 1 + 1 + "" + "" + "" + -1 + "" + 0 + "wxID_STATIC_MODE" + 10027 + "" + "Mode:" + "lblMode" + "" + "<Any platform>" + 0 + 0 + "" + -1 + -1 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + -1 + -1 + "wxStaticText: wxID_STATIC_MODE" + 0 + "dialog-control-document" + + + "wbComboBoxProxy" + "" + "combobox" + "itemComboBox12" + 0 + 0 + 1 + "Expand" + "Centre" + "" + "wxComboBox" + 5 + "wxComboBox" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + 1 + 1 + "" + "" + "" + -1 + "" + 0 + "ID_COMBOBOX_MODE" + 10003 + "" + "" + "cmbMode" + "" + "<Any platform>" + 0 + 0 + "" + "Scan mode" + -1 + 0 + 1 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 1 + 0 + 0 + 1 + 0 + -1 + -1 + "wxComboBox: ID_COMBOBOX_MODE" + 0 + "dialog-control-document" + + + "wbStaticTextProxy" + "" + "statictext" + "itemStaticText13" + 0 + 0 + 1 + "Right" + "Centre" + "" + "wxStaticText" + 5 + "wxStaticText" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + 1 + 1 + "" + "" + "" + -1 + "" + 0 + "wxID_STATIC_RESOLUTION" + 10030 + "" + "Resolution:" + "lblResolution" + "" + "<Any platform>" + 0 + 0 + "" + -1 + -1 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + -1 + -1 + "wxStaticText: wxID_STATIC_RESOLUTION" + 0 + "dialog-control-document" + + + "wbComboBoxProxy" + "" + "combobox" + "itemComboBox14" + 0 + 0 + 1 + "Left" + "Centre" + "" + "wxComboBox" + 5 + "wxComboBox" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + 1 + 1 + "" + "" + "" + -1 + "" + 0 + "ID_COMBOBOX_RESOLUTION" + 10004 + "" + "" + "cmbResolution" + "" + "<Any platform>" + 0 + 0 + "" + "Scan resolution" + -1 + 0 + 1 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 1 + 0 + 0 + 1 + 0 + -1 + -1 + "wxComboBox: ID_COMBOBOX_RESOLUTION" + 0 + "dialog-control-document" + + + "wbStaticTextProxy" + "" + "statictext" + "itemStaticText1" + 0 + 0 + 1 + "Centre" + "Centre" + "" + "wxStaticText" + 5 + "wxStaticText" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + 1 + 1 + "" + "" + "" + -1 + "" + 0 + "wxID_STATIC" + 5105 + "" + "Paper size:" + "lblPaperSize" + "" + "<Any platform>" + 0 + 0 + "" + -1 + -1 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + -1 + -1 + "wxStaticText: wxID_STATIC" + 0 + "dialog-control-document" + + + "wbComboBoxProxy" + "" + "combobox" + "itemComboBox2" + 0 + 0 + 1 + "Left" + "Centre" + "" + "wxComboBox" + 5 + "wxComboBox" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + 1 + 1 + "" + "" + "" + -1 + "" + 0 + "ID_COMBOBOX" + 10005 + "" + "" + "cmbPaperSize" + "" + "<Any platform>" + 0 + 0 + "" + "" + -1 + 0 + 1 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 1 + 0 + 0 + 1 + 0 + -1 + -1 + "wxComboBox: ID_COMBOBOX" + 0 + "dialog-control-document" + + + + + + + "Sources" + "html-document" + "" + "sourcesfolder" + 1 + 1 + 0 + 1 + + "InsaneWidget.rc" + "source-editor-document" + "InsaneWidget.rc" + "source-editor" + 0 + 0 + 1 + 0 + "15/6/2025" + "" + + + + "Images" + "html-document" + "" + "bitmapsfolder" + 1 + 1 + 0 + 1 + + + + +
diff --git a/Resources/InsaneWidget/UI/InsaneWidget.rc b/Resources/InsaneWidget/UI/InsaneWidget.rc new file mode 100644 index 0000000..b86c4e2 --- /dev/null +++ b/Resources/InsaneWidget/UI/InsaneWidget.rc @@ -0,0 +1 @@ +#include "wx/msw/wx.rc" diff --git a/Resources/InsaneWidget/UI/ScannerWidget.cpp b/Resources/InsaneWidget/UI/ScannerWidget.cpp new file mode 100644 index 0000000..f90e0c2 --- /dev/null +++ b/Resources/InsaneWidget/UI/ScannerWidget.cpp @@ -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 +} diff --git a/Resources/InsaneWidget/UI/ScannerWidget.h b/Resources/InsaneWidget/UI/ScannerWidget.h new file mode 100644 index 0000000..73b4b80 --- /dev/null +++ b/Resources/InsaneWidget/UI/ScannerWidget.h @@ -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_ diff --git a/Resources/InsaneWidget/XInsaneWidget.cpp b/Resources/InsaneWidget/XInsaneWidget.cpp new file mode 100644 index 0000000..08af79b --- /dev/null +++ b/Resources/InsaneWidget/XInsaneWidget.cpp @@ -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 +#include +#include "PixelToImageWriter.h" +#include "PixelToPdfWriter.h" +#include + +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 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 GetStartAndIncrement(InsaneWorker * insaneWorker) + { + wxASSERT_MSG(insaneWorker != nullptr, "insaneWorker is NULL."); + std::pair 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 (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 m_owner = nullptr; + wxWeakRef m_sb = nullptr; + std::unique_ptr 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 (new wxPopupTransientWindow ( wxApp::GetGUIInstance()->GetTopWindow() )); + m_ptwScannerWidget->Show ( false ); + m_scanProject = std::make_unique(this, m_sb); + m_insaneWorker = std::make_unique(m_scanProject.get()); + m_scannerWidget = std::make_unique ( 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(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 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 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); +} diff --git a/Resources/InsaneWidget/XInsaneWidget.h b/Resources/InsaneWidget/XInsaneWidget.h new file mode 100644 index 0000000..4c81dac --- /dev/null +++ b/Resources/InsaneWidget/XInsaneWidget.h @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +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> 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 m_sb; + + // Contains a popup to define the number of pages and double-sided scanning. + std::unique_ptr m_pageStack; + // Contains the scanner widget. + std::unique_ptr m_ptwScannerWidget; + // Available devices and minimal options. + std::unique_ptr m_scannerWidget; + std::unique_ptr m_insaneWorker; + std::unique_ptr m_backgroundScannerDiscoveryEvh; + std::unique_ptr m_scanProject; + + void OnLblNewDocRightClick ( wxMouseEvent& evt ); + void OnTxtNewDocKeyPressed ( wxKeyEvent& evt ); + void OnBtnScanRightClick ( wxMouseEvent& evt ); + void OnBtnScanClick ( wxMouseEvent& evt ); +}; + +#endif // XINSANEWIDGET_H diff --git a/Resources/InsaneWidget/XScannerWidget.cpp b/Resources/InsaneWidget/XScannerWidget.cpp new file mode 100644 index 0000000..aba5634 --- /dev/null +++ b/Resources/InsaneWidget/XScannerWidget.cpp @@ -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 +#include +#include + +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 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 ( 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 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 ( 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 ( 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() +{} + diff --git a/Resources/InsaneWidget/XScannerWidget.h b/Resources/InsaneWidget/XScannerWidget.h new file mode 100644 index 0000000..a9023ec --- /dev/null +++ b/Resources/InsaneWidget/XScannerWidget.h @@ -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 +#include +#include +#include +#include "TimeredStatusBar.h" +#include "InsaneWorker.h" +#include "Common.h" +#include + +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 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 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 diff --git a/Resources/Lokalize/0_getstrings.sh b/Resources/Lokalize/0_getstrings.sh new file mode 100755 index 0000000..6b20ab7 --- /dev/null +++ b/Resources/Lokalize/0_getstrings.sh @@ -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 diff --git a/Resources/Lokalize/2_makemo.sh b/Resources/Lokalize/2_makemo.sh new file mode 100755 index 0000000..8d1a642 --- /dev/null +++ b/Resources/Lokalize/2_makemo.sh @@ -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 + diff --git a/Resources/Lokalize/fr/CB7.mo b/Resources/Lokalize/fr/CB7.mo new file mode 100644 index 0000000000000000000000000000000000000000..e1fdefc30d3f74ff3d213a0e0046d55ce56106fd GIT binary patch literal 8899 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 fv9v4^G#HzbnF|?z1hT6HWsFK{=`( literal 0 HcmV?d00001 diff --git a/Resources/Lokalize/fr/S7.po b/Resources/Lokalize/fr/S7.po new file mode 100644 index 0000000..dedab92 --- /dev/null +++ b/Resources/Lokalize/fr/S7.po @@ -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 +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 \n" +"Language-Team: French \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." diff --git a/Resources/Lokalize/fr/S7.po.bak-2025-06-26-22:47:15 b/Resources/Lokalize/fr/S7.po.bak-2025-06-26-22:47:15 new file mode 100644 index 0000000..6397003 --- /dev/null +++ b/Resources/Lokalize/fr/S7.po.bak-2025-06-26-22:47:15 @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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." diff --git a/Resources/PKGBUILD b/Resources/PKGBUILD new file mode 100644 index 0000000..01a00e7 --- /dev/null +++ b/Resources/PKGBUILD @@ -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 +} diff --git a/Resources/UI/S7/S7.pjd b/Resources/UI/S7/S7.pjd new file mode 100644 index 0000000..09633ce --- /dev/null +++ b/Resources/UI/S7/S7.pjd @@ -0,0 +1,622 @@ + + +
+ 1 + "Application" + "app.h" + "app.cpp" + "Standard" + 0 + 1 + 1 + "" + "<None>" + "Copyright Saleem EDAH-TALLY. All rights reserved." + " /// %BODY% +" + " +/* + * %BODY% + */ + +" + "///////////////////////////////////////////////////////////////////////////// +// Name: %HEADER-FILENAME% +// Purpose: +// Author: %AUTHOR% +// Modified by: +// Created: %DATE% +// RCS-ID: +// Copyright: %COPYRIGHT% +// Licence: +///////////////////////////////////////////////////////////////////////////// + +" + "" + "///////////////////////////////////////////////////////////////////////////// +// Name: %SOURCE-FILENAME% +// Purpose: +// Author: %AUTHOR% +// Modified by: +// Created: %DATE% +// RCS-ID: +// Copyright: %COPYRIGHT% +// 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 + +" + "///////////////////////////////////////////////////////////////////////////// +// Name: %SYMBOLS-FILENAME% +// Purpose: Symbols file +// Author: %AUTHOR% +// Modified by: +// Created: %DATE% +// RCS-ID: +// Copyright: %COPYRIGHT% +// Licence: +///////////////////////////////////////////////////////////////////////////// + +" + "<All platforms>" + "" + "" + 0 + 0 + 0 + 0 + 0 + "" + 0 + 4 + 0 + 1 + 0 + "utf-8" + "" + "AppResources" + "app_resources.h" + "app_resources.cpp" + "" + "" + "utf-8" + "2.9.2" + 0 + "" + 1 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + "Saleem EDAH-TALLY" + " " + 1 + "utf-8" + "" + 0 + 1 + 0 +
+ + + "" + "data-document" + "" + "" + 0 + 1 + 0 + 0 + + "Debug" + "%AUTO%" + "%AUTO%" + "" + "%AUTO%" + "%AUTO%" + "%AUTO%" + "%AUTO%" + "" + "%AUTO%" + 1 + 1 + "%EXECUTABLE%" + "%AUTO%" + "%AUTO%" + "" + "GUI" + "" + "%AUTO%" + 0 + "%AUTO%" + "%AUTO%" + "%AUTO%" + "%AUTO%" + 0 + "%AUTO%" + 4286447616 + "Modular" + "%AUTO%" + "%AUTO%" + "%AUTO%" + 1 + "%AUTO%" + "%AUTO%" + "Default" + "" + "%AUTO%" + "%AUTO%" + "%AUTO%" + "%AUTO%" + "Dynamic" + "Static" + 0 + "" + "Configurations" + 0 + "wxGTK+2" + "config-data-document" + "Unicode" + "Yes" + "No" + "No" + "Yes" + "Yes" + "No" + "Yes" + "builtin" + "Yes" + "Yes" + "Yes" + "Yes" + "%AUTO%" + "%AUTO%" + "%AUTO%" + "%AUTO%" + "%AUTO%" + "%AUTO%" + "%AUTO%" + "%WXVERSION%" + + + + + + + "Projects" + "root-document" + "" + "project" + 1 + 1 + 0 + 1 + + "Windows" + "html-document" + "" + "dialogsfolder" + 1 + 1 + 0 + 1 + + "wbAppProxy" + "Standard" + 10000 + "" + "app" + "" + "" + 0 + 0 + 1 + "wxApp" + "S7App" + "" + "s7app.h" + "" + "s7app.cpp" + "" + "S7App" + 0 + "dialog-document" + 0 + 0 + 0 + 0 + + + "wbFrameProxy" + "Standard" + 10000 + "" + "frame" + "" + "" + "itemFrame1" + 0 + 0 + 1 + 0 + "" + "wxFrame" + 1 + "S7" + "" + "" + "" + "" + "" + "" + 0 + 1 + "" + 1 + "" + "" + "s7.h" + 300 + "" + 0 + "" + "ID_S7" + 10000 + "s7.cpp" + "<Any platform>" + "S7" + "" + 400 + 0 + 1 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + -1 + "" + -1 + "S7" + 0 + "dialog-document" + 0 + 0 + 0 + 0 + + "wbPanelProxy" + "" + "panel" + "itemPanel1" + 0 + 0 + 1 + "Centre" + "Centre" + 0 + "" + "wxPanel" + 5 + "wxPanel" + "" + "" + "" + "" + "" + "" + "" + "" + 1 + "" + 1 + 1 + "" + "" + "" + -1 + "" + 0 + "ID_PANEL" + 10001 + "" + "panMain" + "" + "<Any platform>" + 0 + 0 + "" + "Tiled" + "" + -1 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + 1 + 1 + 0 + 0 + 0 + 1 + -1 + -1 + "wxPanel: ID_PANEL" + 0 + "dialog-control-document" + + "wbBoxSizerProxy" + "" + "sizer" + "itemBoxSizer2" + 0 + 0 + 1 + "Centre" + "Centre" + 5 + "szMain" + "Vertical" + "<Any platform>" + 0 + 0 + 1 + 0 + 1 + 0 + 1 + 0 + 1 + "wxBoxSizer V" + 0 + "dialog-control-document" + + "wbDirPickerCtrlProxy" + "" + "dialogcontrol" + "itemDirPickerCtrl3" + 0 + 0 + 1 + "Expand" + "Centre" + "" + "wxDirPickerCtrl" + 5 + "wxDirPickerCtrl" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + 1 + 1 + "" + "" + "" + -1 + "" + 0 + "ID_DIRPICKERCTRL" + 10002 + "" + "dpkDestination" + "" + "" + "<Any platform>" + 0 + 0 + "Select a destination directory. +Double-click to go to the selected directory." + -1 + 0 + 0 + 1 + 0 + 0 + 1 + 0 + 1 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + -1 + -1 + "wxDirPickerCtrl: ID_DIRPICKERCTRL" + 0 + "dialog-control-document" + + + "wbTextCtrlProxy" + "" + "textctrl" + "itemTextCtrl4" + 0 + 0 + 1 + "Expand" + "Centre" + "" + "wxTextCtrl" + 5 + "wxTextCtrl" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + 1 + 1 + "" + "" + "" + -1 + "Basename" + 0 + "ID_TEXTCTRL" + 10003 + "" + "" + 0 + "txtBasename" + "" + "<Any platform>" + 0 + 0 + "Specify a destination file basename (without extension). + +'CTRL + click' for about information." + -1 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + -1 + -1 + "wxTextCtrl: ID_TEXTCTRL" + 0 + "dialog-control-document" + + + + + + + "Sources" + "html-document" + "" + "sourcesfolder" + 1 + 1 + 0 + 1 + + "S7.rc" + "source-editor-document" + "S7.rc" + "source-editor" + 0 + 0 + 1 + 0 + "16/6/2025" + "" + + + + "Images" + "html-document" + "" + "bitmapsfolder" + 1 + 1 + 0 + 1 + + + + +
diff --git a/Resources/UI/S7/S7.rc b/Resources/UI/S7/S7.rc new file mode 100644 index 0000000..b86c4e2 --- /dev/null +++ b/Resources/UI/S7/S7.rc @@ -0,0 +1 @@ +#include "wx/msw/wx.rc" diff --git a/Resources/UI/S7/s7.cpp b/Resources/UI/S7/s7.cpp new file mode 100644 index 0000000..1bf37a3 --- /dev/null +++ b/Resources/UI/S7/s7.cpp @@ -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 +} diff --git a/Resources/UI/S7/s7.h b/Resources/UI/S7/s7.h new file mode 100644 index 0000000..f69647a --- /dev/null +++ b/Resources/UI/S7/s7.h @@ -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_ diff --git a/Resources/UI/S7/s7app.cpp b/Resources/UI/S7/s7app.cpp new file mode 100644 index 0000000..4a7ba6b --- /dev/null +++ b/Resources/UI/S7/s7app.cpp @@ -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 +#include "s7app.h" +#include + +////@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 +} + diff --git a/Resources/UI/S7/s7app.h b/Resources/UI/S7/s7app.h new file mode 100644 index 0000000..1fa54ab --- /dev/null +++ b/Resources/UI/S7/s7app.h @@ -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_ diff --git a/Resources/Utilities/CMakeLists.txt b/Resources/Utilities/CMakeLists.txt new file mode 100644 index 0000000..8392905 --- /dev/null +++ b/Resources/Utilities/CMakeLists.txt @@ -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} + ) diff --git a/Resources/Utilities/ConfigEditorPopup.cpp b/Resources/Utilities/ConfigEditorPopup.cpp new file mode 100644 index 0000000..c4899a6 --- /dev/null +++ b/Resources/Utilities/ConfigEditorPopup.cpp @@ -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 ( evt.GetEventObject() ); + if ( !ctrl ) return; + + const wxString className ( ctrl->GetClassInfo()->GetClassName() ); + const wxString * cPath = static_cast ( ctrl->GetClientData() ); + if ( className ==_T("wxCheckBox") ) + { + wxCheckBox * cb = static_cast ( evt.GetEventObject() ); + m_config->Write ( *cPath, cb->GetValue() ); + } + if ( className ==_T("wxSpinCtrl") ) + { + wxSpinCtrl * spn = static_cast ( evt.GetEventObject() ); + m_config->Write ( *cPath, spn->GetValue() ); + } + if ( className ==_T("wxTextCtrl") ) + { + wxTextCtrl * txt = static_cast ( 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() ); +} + + diff --git a/Resources/Utilities/ConfigEditorPopup.h b/Resources/Utilities/ConfigEditorPopup.h new file mode 100644 index 0000000..41a83f2 --- /dev/null +++ b/Resources/Utilities/ConfigEditorPopup.h @@ -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 +#include +#include "PopupTransientWindow.h" +#include + +/** + * 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 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 */ + diff --git a/Resources/Utilities/MiscTools.cpp b/Resources/Utilities/MiscTools.cpp new file mode 100644 index 0000000..60f91fc --- /dev/null +++ b/Resources/Utilities/MiscTools.cpp @@ -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 +#include + +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::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; +} diff --git a/Resources/Utilities/MiscTools.h b/Resources/Utilities/MiscTools.h new file mode 100644 index 0000000..b5a7bb6 --- /dev/null +++ b/Resources/Utilities/MiscTools.h @@ -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 +#include +#include + +/** + * 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 */ + diff --git a/Resources/Utilities/PopupTransientWindow.cpp b/Resources/Utilities/PopupTransientWindow.cpp new file mode 100644 index 0000000..f07cd9c --- /dev/null +++ b/Resources/Utilities/PopupTransientWindow.cpp @@ -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(); +} + diff --git a/Resources/Utilities/PopupTransientWindow.h b/Resources/Utilities/PopupTransientWindow.h new file mode 100644 index 0000000..579f530 --- /dev/null +++ b/Resources/Utilities/PopupTransientWindow.h @@ -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 +#include + +/** 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 */ + diff --git a/Resources/Utilities/TimeredStatusBar.cpp b/Resources/Utilities/TimeredStatusBar.cpp new file mode 100644 index 0000000..eb1b324 --- /dev/null +++ b/Resources/Utilities/TimeredStatusBar.cpp @@ -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 ); + } +} diff --git a/Resources/Utilities/TimeredStatusBar.h b/Resources/Utilities/TimeredStatusBar.h new file mode 100644 index 0000000..449806b --- /dev/null +++ b/Resources/Utilities/TimeredStatusBar.h @@ -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 +#include + +/** + * 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 */ + diff --git a/Resources/Utilities/XClientData.hpp b/Resources/Utilities/XClientData.hpp new file mode 100644 index 0000000..a1ac1a6 --- /dev/null +++ b/Resources/Utilities/XClientData.hpp @@ -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 + +/** + * 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 diff --git a/S7_01.png b/S7_01.png new file mode 100644 index 0000000000000000000000000000000000000000..d5640860f827d58a13240101378b4a93f05c6709 GIT binary patch literal 45191 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 +#include + +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(_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(); +} diff --git a/XS7.h b/XS7.h new file mode 100644 index 0000000..35f71c8 --- /dev/null +++ b/XS7.h @@ -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 +#include +#include + +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 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 diff --git a/globals.h b/globals.h new file mode 100644 index 0000000..7dd7423 --- /dev/null +++ b/globals.h @@ -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 */ + diff --git a/page.xpm b/page.xpm new file mode 100644 index 0000000..385184a --- /dev/null +++ b/page.xpm @@ -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", +" .+@+ ", +" #######. ", +" #########+ ", +" #### $### ", +" %##& *##$ ", +" =## ##% ", +" -@############ ############; ", +" ##############> ;############## ", +" #################*,'###############* ", +" #################################### ", +" ##################) #################################### @################## ", +" ####################) #################################### @###################! ", +" #####################) #################################### @####################@ ", +" ######################) #################################### @#####################~ ", +" ######################) #################################### @###################### ", +" ;######################) #################################### @######################{ ", +" '######################) #################################### @####################### ", +" #######################) #################################### @####################### ", +" #########; #################################### ~######### ", +" ########+ ;##################################+ '######## ", +" ########) '######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) )$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$, {######## ", +" ########) #################### #############################* {######## ", +" ########) #################### #############################] {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### *~~~~~~~~~~~~~~~~~~~~~~~~~~~# {######## ", +" ########) #################### #############################& {######## ", +" ########) #################### '############################# {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### ^############################# {######## ", +" ########) #################### ############################## {######## ", +" ########) ,##################! ~###########################; {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) ######################################################) {######## ", +" ########) ########################################################, {######## ", +" ########) ########################################################* {######## ", +" ########) ))))))))))))))))))))))))))))))))))))))))))))))))))))))- {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######################################################; {######## ", +" ########) ########################################################' {######## ", +" ########) ########################################################/ {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) (######################################################' {######## ", +" ########) ######################################################### {######## ", +" ########) ;####################################################### {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) ############################ {######## ", +" ########) ############################ {######## ", +" ########) ;~~~~~~~~~~~~~~~~~~~~~~~~~~, {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) )######## ", +" ########) /'###############^ (######## ", +" ########) $################^ (######### ", +" ########) ,###############^ (########## ", +" ########) )##############^ (########### ", +" ########) )#############^ (############ ", +" ########) )############^ (############# ", +" ########) )###########^ (############## ", +" ########) )##########^ (############### ", +" ########) )#########^ (################ ", +" ########) )########^ (################# ", +" ########) )#######^ (################## ", +" ########) )######^ (################### ", +" ########) )#####^ (#################### ", +" ########) )####^ (##################### ", +" ########) )###^ (###################### ", +" ########) )##^ (####################### ", +" ########) )#^ (######################## ", +" ########) )^ (######################### ", +" ########) # (########################## ", +" ########) (########################### ", +" ########{ (############################ ", +" ######### (############################# ", +" #########) (############################## ", +" ##########~;..............................................*############################### ", +" ########################################################################################## ", +" ~######################################################################################### ", +" ^########################################################################################{ ", +" +######################################################################################; ", +" ###################################################################################### ", +" $####################################################################################{ ", +" +################################################################################~ ", +" #;+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;*#& "};