commit ef6f25ef272ec451b47d319f3345553d1949520f Author: Saleem Edah-Tally Date: Sat Jun 28 17:40:42 2025 +0200 Initial commit. 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 0000000..e1fdefc Binary files /dev/null and b/Resources/Lokalize/fr/CB7.mo differ 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 0000000..d564086 Binary files /dev/null and b/S7_01.png differ diff --git a/XS7.cpp b/XS7.cpp new file mode 100644 index 0000000..d055a43 --- /dev/null +++ b/XS7.cpp @@ -0,0 +1,163 @@ +// /* +// * File: XS7.cpp +// * Author: Saleem Edah-Tally - nmset@yandex.com +// * License : CeCILL +// * Copyright Saleem Edah-Tally - © 2023 +// * +// * Created on 18 06 2025, 22:42 +// */ + +#include "XS7.h" +#include "globals.h" +#include "TimeredStatusBar.h" +#include "MiscTools.h" +#include "page.xpm" +#include +#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", +" .+@+ ", +" #######. ", +" #########+ ", +" #### $### ", +" %##& *##$ ", +" =## ##% ", +" -@############ ############; ", +" ##############> ;############## ", +" #################*,'###############* ", +" #################################### ", +" ##################) #################################### @################## ", +" ####################) #################################### @###################! ", +" #####################) #################################### @####################@ ", +" ######################) #################################### @#####################~ ", +" ######################) #################################### @###################### ", +" ;######################) #################################### @######################{ ", +" '######################) #################################### @####################### ", +" #######################) #################################### @####################### ", +" #########; #################################### ~######### ", +" ########+ ;##################################+ '######## ", +" ########) '######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) )$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$, {######## ", +" ########) #################### #############################* {######## ", +" ########) #################### #############################] {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### *~~~~~~~~~~~~~~~~~~~~~~~~~~~# {######## ", +" ########) #################### #############################& {######## ", +" ########) #################### '############################# {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### {######## ", +" ########) #################### ^############################# {######## ", +" ########) #################### ############################## {######## ", +" ########) ,##################! ~###########################; {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) ######################################################) {######## ", +" ########) ########################################################, {######## ", +" ########) ########################################################* {######## ", +" ########) ))))))))))))))))))))))))))))))))))))))))))))))))))))))- {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######################################################; {######## ", +" ########) ########################################################' {######## ", +" ########) ########################################################/ {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) (######################################################' {######## ", +" ########) ######################################################### {######## ", +" ########) ;####################################################### {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) ############################ {######## ", +" ########) ############################ {######## ", +" ########) ;~~~~~~~~~~~~~~~~~~~~~~~~~~, {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) {######## ", +" ########) )######## ", +" ########) /'###############^ (######## ", +" ########) $################^ (######### ", +" ########) ,###############^ (########## ", +" ########) )##############^ (########### ", +" ########) )#############^ (############ ", +" ########) )############^ (############# ", +" ########) )###########^ (############## ", +" ########) )##########^ (############### ", +" ########) )#########^ (################ ", +" ########) )########^ (################# ", +" ########) )#######^ (################## ", +" ########) )######^ (################### ", +" ########) )#####^ (#################### ", +" ########) )####^ (##################### ", +" ########) )###^ (###################### ", +" ########) )##^ (####################### ", +" ########) )#^ (######################## ", +" ########) )^ (######################### ", +" ########) # (########################## ", +" ########) (########################### ", +" ########{ (############################ ", +" ######### (############################# ", +" #########) (############################## ", +" ##########~;..............................................*############################### ", +" ########################################################################################## ", +" ~######################################################################################### ", +" ^########################################################################################{ ", +" +######################################################################################; ", +" ###################################################################################### ", +" $####################################################################################{ ", +" +################################################################################~ ", +" #;+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;*#& "};