Repository: naihe2010/apvlv Branch: master Commit: 621b81ea3844 Files: 106 Total size: 488.9 KB Directory structure: gitextract_tk5z3yo1/ ├── .clang-format ├── .dir-locals.el ├── .github/ │ └── workflows/ │ └── cmake-multi-platform.yml ├── .gitignore ├── AUTHORS ├── CMakeLists.txt ├── COPYING ├── NEWS ├── README.md ├── THANKS ├── TODO ├── apvlv.1 ├── apvlvrc.example ├── cmake/ │ ├── CompileFlags.cmake │ ├── Dependencies.cmake │ └── Options.cmake ├── scripts/ │ ├── build.ps1 │ └── build.sh ├── share/ │ ├── applications/ │ │ └── apvlv.desktop │ └── scripts/ │ └── internal.js ├── src/ │ ├── ApvlvCmds.cc │ ├── ApvlvCmds.h │ ├── ApvlvCompletion.cc │ ├── ApvlvCompletion.h │ ├── ApvlvDirectory.cc │ ├── ApvlvDirectory.h │ ├── ApvlvDired.cc │ ├── ApvlvDired.h │ ├── ApvlvEditor.cc │ ├── ApvlvEditor.h │ ├── ApvlvFile.cc │ ├── ApvlvFile.h │ ├── ApvlvFileIndex.cc │ ├── ApvlvFileIndex.h │ ├── ApvlvFileWidget.cc │ ├── ApvlvFileWidget.h │ ├── ApvlvFrame.cc │ ├── ApvlvFrame.h │ ├── ApvlvImageWidget.cc │ ├── ApvlvImageWidget.h │ ├── ApvlvInfo.cc │ ├── ApvlvInfo.h │ ├── ApvlvLab.cc │ ├── ApvlvLab.h │ ├── ApvlvLog.cc │ ├── ApvlvLog.h │ ├── ApvlvMarkdown.cc │ ├── ApvlvMarkdown.h │ ├── ApvlvNote.cc │ ├── ApvlvNote.h │ ├── ApvlvNoteWidget.cc │ ├── ApvlvNoteWidget.h │ ├── ApvlvOCR.cc │ ├── ApvlvOCR.h │ ├── ApvlvParams.cc │ ├── ApvlvParams.h │ ├── ApvlvQueue.cc │ ├── ApvlvQueue.h │ ├── ApvlvSearch.cc │ ├── ApvlvSearch.h │ ├── ApvlvSearchDialog.cc │ ├── ApvlvSearchDialog.h │ ├── ApvlvUtil.cc │ ├── ApvlvUtil.h │ ├── ApvlvView.cc │ ├── ApvlvView.h │ ├── ApvlvWeb.cc │ ├── ApvlvWeb.h │ ├── ApvlvWebViewWidget.cc │ ├── ApvlvWebViewWidget.h │ ├── ApvlvWidget.cc │ ├── ApvlvWidget.h │ ├── ApvlvWindow.cc │ ├── ApvlvWindow.h │ ├── CMakeLists.txt │ ├── Engines.cmake │ ├── Sources.cmake │ ├── WindowsEngines.cmake │ ├── config.h.in │ ├── file/ │ │ ├── ApvlvAxOffice.cc │ │ ├── ApvlvAxOffice.h │ │ ├── ApvlvDjvu.cc │ │ ├── ApvlvDjvu.h │ │ ├── ApvlvEpub.cc │ │ ├── ApvlvEpub.h │ │ ├── ApvlvFb2.cc │ │ ├── ApvlvFb2.h │ │ ├── ApvlvHtm.cc │ │ ├── ApvlvHtm.h │ │ ├── ApvlvImage.cc │ │ ├── ApvlvImage.h │ │ ├── ApvlvLibreOffice.cc │ │ ├── ApvlvLibreOffice.h │ │ ├── ApvlvMuPdf.cc │ │ ├── ApvlvMuPdf.h │ │ ├── ApvlvPopplerPdf.cc │ │ ├── ApvlvPopplerPdf.h │ │ ├── ApvlvQtPdf.cc │ │ ├── ApvlvQtPdf.h │ │ ├── ApvlvTxt.cc │ │ └── ApvlvTxt.h │ ├── main.cc │ └── testNote.cc ├── vcpkg-manifests/ │ └── system-qt/ │ └── vcpkg.json ├── vcpkg.json └── zh_CN.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ BasedOnStyle: GNU ColumnLimit: 78 AllowShortFunctionsOnASingleLine: Empty AllowShortLambdasOnASingleLine: Empty ================================================ FILE: .dir-locals.el ================================================ ((nil (eval . (setq-local flycheck-clang-include-path '("/usr/include/poppler/glib" "/usr/include/glib-2.0" "/usr/lib64/glib-2.0/include" "/usr/include/cairo" "/usr/include/pixman-1" "/usr/include/freetype2" "/usr/include/libxml2" "/usr/include/poppler" "/usr/include/pango-1.0" "/usr/include/glib-2.0" "/usr/include/gtk-3.0" "/usr/include/gdk-pixbuf-2.0" "/usr/include/gio-unix-2.0" "/usr/include/atk-1.0"))))) ================================================ FILE: .github/workflows/cmake-multi-platform.yml ================================================ # This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. # See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml name: CMake on multiple platforms on: push: branches: ["**"] pull_request: branches: ["master"] jobs: build-linux: runs-on: ${{ matrix.os }} container: ${{ matrix.container }} strategy: # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. fail-fast: false matrix: include: - os: ubuntu-latest container: fedora:43 - os: ubuntu-latest container: debian:13 - os: ubuntu-latest container: rockylinux:9 - os: ubuntu-latest container: archlinux:latest steps: - name: Checkout Code uses: actions/checkout@v4 - name: Prepare (Fedora 43) if: matrix.container == 'fedora:43' run: | dnf install -y cmake cmark gcc-c++ \ qt6-qtbase-devel qt6-qtwebengine-devel \ qt6-qtpdf-devel ps2pdf libreofficekit-devel \ quazip-qt6-devel poppler-qt6-devel \ cmark-devel djvulibre-devel \ mupdf-devel tesseract-devel \ man ghostscript qt6-linguist \ rpmbuild mkdir -p $GITHUB_WORKSPACE/share/doc/apvlv/translations - name: Prepare (Debian 13) if: matrix.container == 'debian:13' run: | apt-get update apt-get install -y cmake cmark g++ \ qt6-base-dev qt6-webengine-dev \ qt6-pdf-dev libreofficekit-dev \ libquazip1-qt6-dev libpoppler-qt6-dev \ libcmark-dev libdjvulibre-dev \ libharfbuzz-dev \ libmupdf-dev libtesseract-dev \ man groff ghostscript qt6-tools-dev qt6-l10n-tools \ pkg-config export PATH=/usr/lib/qt6/bin:$PATH mkdir -p $GITHUB_WORKSPACE/share/doc/apvlv/translations - name: Prepare (RockyLinux 9) if: matrix.container == 'rockylinux:9' run: | dnf install -y epel-release dnf config-manager --enable crb devel dnf install -y --nogpgcheck https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-$(rpm -E %rhel).noarch.rpm dnf install -y --nogpgcheck https://mirrors.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-$(rpm -E %rhel).noarch.rpm dnf install -y cmake cmark gcc-c++ \ qt6-qtbase-devel qt6-qtwebengine-devel \ qt6-qtpdf-devel libreofficekit-devel \ cmark-devel djvulibre-devel \ mupdf-devel tesseract-devel \ man ghostscript qt6-linguist # quazip-qt6-devel poppler-qt6-devel mkdir -p $GITHUB_WORKSPACE/share/doc/apvlv/translations - name: Prepare (Arch Linux) if: matrix.container == 'archlinux:latest' run: | pacman -Syu --noconfirm pacman -S --noconfirm base-devel cmake make gcc \ qt6-base qt6-webengine \ libreoffice-fresh-sdk jdk-openjdk \ quazip-qt6 poppler-qt6 \ pkg-config \ cmark djvulibre \ mupdf mujs tesseract \ man-db groff ghostscript qt6-tools \ gtk3 mkdir -p $GITHUB_WORKSPACE/share/doc/apvlv/translations - name: Build (Linux) run: | test -d /usr/lib/qt6/bin && export PATH=/usr/lib/qt6/bin:$PATH cmake -B build -S . -DCMAKE_BUILD_TYPE=Release cmake --build build build-windows: name: Build (Windows) runs-on: windows-latest steps: - name: Checkout Code uses: actions/checkout@v4 - name: Build with script run: ./scripts/build.ps1 ================================================ FILE: .gitignore ================================================ /build *~ cscope.files cscope.out /.idea /.vscode /.cache share/doc/apvlv/translations/* share/doc/apvlv/Startup.pdf ================================================ FILE: AUTHORS ================================================ Alf ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) project(apvlv VERSION 0.7.0) # vcpkg integration if(DEFINED CMAKE_TOOLCHAIN_FILE) message(STATUS "Using vcpkg toolchain: ${CMAKE_TOOLCHAIN_FILE}") endif() # Include cmake modules list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") include(Options) include(CompileFlags) include(Dependencies) # Version info find_package(Git) if(Git_FOUND) execute_process( COMMAND ${GIT_EXECUTABLE} log -1 --format=%h WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE VERSION_HASH OUTPUT_STRIP_TRAILING_WHITESPACE ) if(VERSION_HASH) message(STATUS "Git hash: ${VERSION_HASH}") set(PROJECT_PATCH ${VERSION_HASH}) endif() endif() # Package definitions if(WIN32) # On Windows, use AppData\Local or install directory set(SYSCONFDIR "$ENV{LOCALAPPDATA}/apvlv" CACHE PATH "System config directory") else() set(SYSCONFDIR "/etc" CACHE PATH "System config directory") endif() add_definitions(-DSYSCONFDIR="${SYSCONFDIR}") add_definitions(-DPACKAGE_NAME="apvlv" -DPACKAGE_VERSION="${PROJECT_VERSION}" -DPACKAGE_BUGREPORT="Alf " -DRELEASE="rel" ) ADD_SUBDIRECTORY(src) IF (NOT WIN32) ADD_CUSTOM_TARGET(CopyCompileCommands ALL COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_BINARY_DIR}/compile_commands.json" "${CMAKE_SOURCE_DIR}/compile_commands.json" COMMENT "Copying compile_commands.json to ${CMAKE_SOURCE_DIR}" DEPENDS ${CMAKE_BINARY_DIR}/compile_commands.json ) ENDIF () INSTALL(DIRECTORY share DESTINATION ".") IF (WIN32) INSTALL(FILES apvlvrc.example DESTINATION ".") ELSE (WIN32) ADD_CUSTOM_TARGET(Startup.pdf ALL COMMAND "man" "-t" "${CMAKE_SOURCE_DIR}/apvlv.1" "|" "ps2pdf" "-" "${CMAKE_SOURCE_DIR}/share/doc/apvlv/Startup.pdf" DEPENDS apvlv.1) INSTALL(FILES apvlvrc.example DESTINATION ${SYSCONFDIR}) INSTALL(FILES apvlv.1 TYPE MAN) ENDIF (WIN32) SET(CPACK_PACKAGE_VENDOR "Alf") SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "apvlv - Alf's PDF/DJVU/EPUB Viewer like Vim") SET(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_MAJOR}) SET(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_MINOR}) SET(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_PATCH}) IF (UNIX) SET(CPACK_SET_DESTDIR ON) SET(CPACK_PACKAGE_CONTACT "Alf ") SET(CPACK_GENERATOR DEB) SET(CPACK_DEBIAN_PACKAGE_DEPENDS "qt6 quazip cmark") IF (EXISTS "/etc/redhat-release") SET(CPACK_GENERATOR RPM) SET(CPACK_RPM_PACKAGE_REQUIRES "qt6-qtwebengine quazip-qt6 cmark") ENDIF () SET(CPACK_SOURCE_GENERATOR TGZ) SET(CPACK_SOURCE_IGNORE_FILES ${CMAKE_BINARY_DIR} ".git" ".gitignore" "win32" "~$" ) ELSE (UNIX) SET(CPACK_GENERATOR NSIS) SET(CPACK_NSIS_CONTACT "Alf ") SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "CreateShortCut '\$DESKTOP\\\\apvlv.lnk' '\$INSTDIR\\\\bin\\\\apvlv.exe'") SET(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "Delete '\$DESKTOP\\\\apvlv.lnk'") ENDIF (UNIX) INCLUDE(CPack) ================================================ FILE: COPYING ================================================ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ================================================ FILE: NEWS ================================================ apvlv 0.5.0 ========== apvlv 0.4.0 ========== Add a 'F' command for forwarding to a word in page when enable visual mode. use mouse motion to select text, which support Ctrl mask. Use mouse select and popup menu to annotate text, underline or comment. When using shortcut 'v' or 'C-v' to selected content, shortcut 'A' to annotate text, shortcut 'U' to underline and shortcut 'C' to comment. apvlv 0.3.0 =========== inner content toc display shortcut 'c' or command ':content' to toggle content shortcut '.' to repeat last action add background option auto set background=black when set inverted=yes add 'content_follow_mode' option for control follow mode of content view apvlv 0.2.0 =========== Add EPUB format support apvlv 0.1.4 =========== Add TXT format support apvlv 0.1.3 =========== Add HTML format support apvlv 0.1.2 =========== Just swapped source from svn to git apvlv 0.1.1 =========== News features * add '[int]s' to support skip some pages for some document * sorted in dir mode by filename * using 't' to open file in new tab window * using 'T' to open directory in new tab window apvlv 0.1.0 =========== News features * UMD file support * using CMake for auto build tool * add rpm and deb installation package =========== apvlv 0.0.9 News features * More like vim's info file scheme * Add `guioptions = "mT"` to support menu and tool bar * Add command bar response when command is failed or command is not valid * Add double click action on pdf pages for select a word, a line or a page of text * Mouse Copy Support: add left drag to select area, and right click to popup menu to copy to clipboard * Key Copy Support: add select area by pressing 'v' or and copy area by press 'y' * add 'wrapscan' options in $HOME/.apvlvrc * add search support of dir and content view * add Startup.tex to source tar * use \ to support space in file name when :o[pen] or :doc * use 'G' to go to the last page * keep search direction when search with 'n' or 'N' after / or ? * add autoreload parameter to support auto reload document or directory view * add reverted pdf page feature Bug Fix * display '*' when input password to open pdf docs * return false when search next but no first string was be searched. * return true when sub child dir has pdf files * fix a segfault when yank text * fix a memory leak when destroy a apvlv doc * fix the correct select mark when scroll up or down the page * fix the k or j 's length in continuous mode apvlv 0.0.8 =========== News features * add DJVU format document viewing support * add poppler-data support in Windows version * add :w[rite] filename to save document * add zw to fitwidth and zh to fitheight * made the scrollbar as a option in $HOME/.apvlvrc Bug Fix * If a pdf file is not encrypted, not ask password when can't open it * highlight correctly after zoom in or zoom out * :z[oom] fitwidth or :z[oom] fitheight works apvlv 0.0.7 =========== Changes for WARNING * apvlv don't support negative integer now Because the '-' cause many bugs in in - to smaller a window, apvlv don't support negative integer from 0.0.7.4. News features * add shell command line options, -h, -v and -c * add options to disable ~/.apvlvinfo * add a option "set autoscrolldoc=yes/no" to toggle auto scroll a doc from end to 1st * add a option "set pdfcache=[number]" to set the cache size of pdf object * set pdfcache >= 2 limit for apvlv works correctly * set no autoscroll and not continuous when pdf is a single page * add global /etc/apvlvrc * don't create ~/.apvlvrc automatically * made fullscreen really works under some WM * add key 'n' and 'N' to search and back search Bug Fix * fix file is not exist segment fault * fix the command history segment fault * add document of / and ? key which appear from 0.0.3 * fix lots of spelling errors in Startup.pdf apvlv 0.0.6 ============ News features * Warning !!! change 'goto' command's 'g' to 'G' * add continuous view of pdf page set in .apvlvrc "set continuous=yes/no" * add argument to support weather auto scroll page when k,j to page's end or head set in .apvlvrc "set autoscrollpage=yes/no" * add Up and Down key to get previous and next command in Command Prompt * add tab view add :tabnew and 'gt' 'gg' 'gT' 'g[n]' command to support tab switch, change goto page command from 'g' to 'G' * add content view * k, j to select up, down * h, l to expand, collapse * open selected page * 'o' open selected page in split window * 't' open selected page in new tab * add directory view by command 'O' * k, j to select up, down * h, l to expand, collapse * open selected file * 'o' open selected file in split window * 't' open selected file in new tab * add goto a hyperlink and to come back * add open encrypted pdf with password support * add open last directory * add mouse wheel scrollup and down support * add a padding argument to continuous page * add :number to goto page Bug fix * fix the windows open bug * fix the keys can't work on 64bit platform * fix some segment fault * change the width, height of apvlv can resize apvlv 0.0.5 ============ News features * add cache module to make the display faster. * add ':set [no]cache' to tell apvlv if use cache module. apvlv 0.0.4 ============= News features * remove the status bar from the bottom, and add it to every window. * make the active window highlight the status bar. * add 'r' command to rotate page. * add ':TOtext' to translate pdf doc page to a text file. * add ':pr[int]' to print the document. * add ':open file' to open a PDF document. * add ':doc file' to set the doc to current window. * support open multiple PDF files once, and load them in cache. can be set by ':doc file'. Bug Fix * fix the absolutepath () bug. apvlv 0.0.3 ============== News features * Replace word which like 'C-a' to like '' at every necessary place. * Support 'H', 'M' and 'L' to scroll one PDF page. * Support multiple window view and support below command to control them. 0. ':sp', ':vsp', 'q', 'quit' 1. ' ', ' q 2. ' k' 3. ' j' 4. ' h' 5. ' l' 6. ' ' 7. ' -' 8. ' +' * Support multiple PDF Doc to be opened at the memory. * Support map command mode like 'map a :vsp' ================================================ FILE: README.md ================================================ # apvlv apvlv is a PDF/EPUB/TXT/FB2/MOBI/CBZ/HTML ... Viewer Under Linux/WIN32 and its behaviour like Vim. Apvlv is an open source software, was created by Alf and is hosted on [github](https://github.com/naihe2010/apvlv). Now, it is still growing. Like Vim, Apvlv makes people reading their PDF/EPUB files just like using Vim. So, Apvlv bindings lots of Vim command and its behaviour is like Vim. For example, < Ctrl-f > to forward page, < Ctrl-b > to previous page, 'k','j','h','l' to scrolling a page up, down, left or right, and so on. And, Apvlv can understand that how many times you want to run the command. The only thing you need to do is typing the number before the command. For example, typing '50' and < Ctrl-f > will go forward 50 pages, typing '30' and < Ctrl-b > will go previous 30 pages. What's more import is apvlv can support view a directory as content of a pdf/epub document. Pressing 'k' or 'j' to move selected up or down, 'h' or 'l' to collapse or expand a dir, and press 't' will open the selected document in a new tab. # Dependencies + Qt6 ( http://www.qt.org/ ) + Quazip ( https://github.com/stachenov/quazip ) # Optional Dependencies + Poppler-qt6 ( https://www.qt.io/ ) + MuPDF ( https://www.mupdf.com/ ) + Teeseract ( https://github.com/tesseract-ocr/tessdoc ) # Download + Releases (https://github.com/naihe2010/apvlv/releases) # Build 1. Using cmake to generate Makefile. ``` cmake . ``` 2. Execute make. ``` make ``` # Install + Make a package and install it. ``` make package ``` + Or install it directly. ``` sudo make install ``` # License apvlv is licensed under the GNU General Public License (GPL). # Contact + Email: Alf [naihe2010@126.com](mailto:naihe2010@126.com) # Develop Tools + Vim (https://www.vim.org/) + Emacs (https://www.gnu.org/software/emacs/) + CLion (https://www.jetbrains.com/clion/) Thanks these great tools. # Github Actions ![CI Status](https://github.com/naihe2010/apvlv/actions/workflows/cmake-multi-platform.yml/badge.svg) ================================================ FILE: THANKS ================================================ I would like to give thanks to the following for their support and contributions: - Ian Munsie , for he fixing some memory leaks in ApvlvPDF::pagesearch - Nico , for his inverted feature code contribution. And, the code is copy from evince, and the author is Juanjo Marín. Thanks !!! - Daniel Friesel , for his more beautiful man page - Andrew Kudryashov , for his help about Startup.pdf - Adam , for his test and bug fix. - Stefan Ritter , for his man page of apvlv, his careful test and pack on Debian - Robert Smolinski , for his vimlike tabs code contribution. And, he is a developer of apvlv then. - Robby Workman , for his bug fix about doc directory - grandpa , for his test on windows - Ali Gholami Rudi , for his bug fixes - tocer , for his test on linux and pack on ArchLinux. - pk , for his advices ================================================ FILE: TODO ================================================ . Bug fix. ================================================ FILE: apvlv.1 ================================================ .Dd December 24, 2009 .Dt apvlv 1 .Os .Sh NAME .Nm apvlv .Nd PDF/DJVU/EPUB/HTML/TXT/FB2/CZW viewer with vim-like behaviour .Sh SYNOPSIS .Nm .Op options .Op file .Sh DESCRIPTION apvlv is a PDF/DJVU/EPUB/HTML/TXT/FB2/CZW viewer, which behaves like vim. .Sh OPTIONS .Bl -tag -width "v" .It Fl c Ar file Load configuration from .Ar file instead of the default .Pa ~/.apvlvrc .It Fl h Show help message and exit .It Fl v Show version and exit .El .Sh COMMANDS The following command keys can be used inside apvlv. Some of them may be prefixed by a number (as in pressing "13G"), this is indicated by a .Ar count in their description. Unless noted otherwise, the default value for the number is 1. .Bl -tag -width "indent" .It o Display file chooser to open a PDF/DJVU/EPUB/HTML/TXT file .It O Select a directory to display .It t Display file chooser to open a PDF/DJVU/EPUB/HTML/TXT file in a new tab .It T Select a directory to display in a new tab .It R Reload the current file .It r Rotate the document clockwise by 90 degrees .It G Show page number .Ar count .It gt Show next tab .It gT Show previous tab .It PageDown, C-f Go forward .Ar count pages .It PageUp, C-b Go backward .Ar count pages .It C-d Go forward .Ar count half pages .It C-u Go backward .Ar count half pages .It H Scroll to page head .It M Scroll to page middle .It L Scroll to page bottom .It s skip some pages .Ar count .It C-p, Up, k Scroll up .Ar count units .It C-n, Down, j Scroll down .Ar count units .It Backspace, Left, h Scroll left .Ar count units .It Space, Right, l Scroll right .Ar count units .It / Ar string Search forwards for .Ar string .It ? Ar string Search backwards for .Ar string .It f Toggle between fullscreen and window mode .It zi Zoom in .It zo Zoom out .It zw Zoom to fit window width .It zh Zoom to fit window height .It m Ar char Mark the current position to .Ar char , so that it can be recalled by pressing .Ar char .It ' Ar char Return to the mark position .Ar char .It '' Return to the last position .It q Close the current window .It ZZ quit the viewer with ZZ, like Vim .It c toggle directory display .El .Sh SETTINGS These can be set in ~/.apvlvrc with .Qq set Ar setting Op = Ar value . .Bl -tag -width "indent" .It fullscreen = yes/no Enable/Disable fullscreen .It width = Ar int Default window width .It height = Ar int Default window height .It fix_width = Ar int fixed width .It fix_height = Ar int fixed height .It defaultdir = Ar path Default directory for the open dialogue .It zoom = Ar mode Set default zoom level .Bl -tag -width "indent" .It normal The application sets the default zoom value .It fitwidth Fit pages to window width .It fitheight Fit pages to window height .It Ar float 1.0 for 100%, 2.0 for 200%, etc. .El .It continuous = yes/no Show PDF/DJVU/EPUB/HTML/TXT pages continuously or not. .It autoscrollpage = yes/no Enable/Disable scrolling the pages when hitting a page tail/head .It noinfo = yes/no Disable/Enable the usage of ~/.apvlvinfo .It max_info = Ar int Max file information will be saved, default is 100 .It scrollbar = yes/no Set show scrollbar or not .It wrapscan = yes/no Set wrapscan to search text or not .It doubleclick = Ar action Set default double click action .Bl -tag -width "indent" .It none Selection nothing .It word Selection a word under the cursor to clipboard .It line Selection a line under the cursor to clipboard .It page Selection a page under the cursor to clipboard .El .It guioptions = mTsS Weather display menu, toolbar, status and status tool. .It .pdf:engine = MuPDF/Poppler/QtPdf Which engine to render .pdf file .It .epub:engine = MuPDF/Web Which engine to render .epub file .It .fb2:engine = MuPDF/Web Which engine to render .fb2 file .It ocr:lang = eng+chi_sim Pretrained languages which tesseract load to process .It notes:dir = Ar dir Directory to save ebook notes .It autoreload = Ar int If auto reload document after some seconds .El .Bl -tag -width "indent" .It inverted = yes/no If use inverted mode for pdf page .It background = Ar color Set background color .El .Sh PROMPT Like the COMMANDS, but prefixed with a colon: .Bl -tag -width "indent" .It :h[elp] Display the help document .It :q[uit] Close the current window .It :o[pen] Ar file Open .Ar file .It :doc Ar file Load .Ar file into the current window .It :TOtext Op Ar file Translate .Ar file (or the current page) to a text file .It :pr[int] Print the current document .It :tabnew Create a new tab .It :sp Horizontally split the current window .It :vsp Vertically split the current window .It :fp, :forwardpage Op Ar int Go forward .Ar int pages (1 by default) .It :bp, :prewardpage Op Ar int Go backward .Ar int pages (1 by default) .It :g, :goto Ar int Go to page .Ar int .It :z[oom] Ar mode Set zoom to .Ar mode (see "set zoom" in SETTINGS) .It : Ns Ar int Go to page .Ar int .It :directory toggle directory display .El .Sh AUTHORS apvlv was written by Alf . .Pp This manual page was originally written by Stefan Ritter for the Debian project (but may be used by others), and was rewritten more beautifully by Daniel Friesel . ================================================ FILE: apvlvrc.example ================================================ " some map " map n to to goto next page "map n " and p to prepage "map p " map I to zi, and O to zo "map I zi "map O zo " if start apvlv as fullscreen mode, default is no "set fullscreen=no " zoom value, default is fitwidth " zoom has 4 styles " a float type number " fitwidth " fitheight " normal "set zoom=fitwidth " set window size "set width=800 "set height=600 " set command timeout between two key press "set commandtimeout=2000 " set default dir "set defaultdir=C:\ " set weather use continuous view " make sure the autoscrollpage is set to "yes" if you want to set this to yes "set continuous=yes " set a pad to continuous page "set continuouspad=2 " set if auto scroll page when at the end or begin of one pdf page "set autoscrollpage=yes " set if auto scroll doc from 1st page when goto the last page "set autoscrolldoc=yes " set if disable ~/.apvlvinfo, default is no "set noinfo=no " set if wrapscan text "set wrapscan = yes " set double click action " option value is 'word', 'line' or 'page' "set doubleclick = page " set GUI options " m means menu, T means toolbar "set guioptions = mT " set if reverted pdf page "set reverted = no ================================================ FILE: cmake/CompileFlags.cmake ================================================ # Compiler flags configuration # C++ standard set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Common flags if(NOT WIN32) set(CMAKE_CXX_FLAGS "-Wall -fno-strict-aliasing") if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(CMAKE_CXX_FLAGS "-D_DEBUG -g ${CMAKE_CXX_FLAGS}") else() set(CMAKE_CXX_FLAGS "-O2 ${CMAKE_CXX_FLAGS}") endif() else() add_definitions(-D_CRT_SECURE_NO_WARNINGS) endif() # Export compile commands set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Qt definitions add_definitions(-DQT_MESSAGELOGCONTEXT) ================================================ FILE: cmake/Dependencies.cmake ================================================ # Dependency management for apvlv # Platform-specific dependency handling if(WIN32) # Windows - use vcpkg via find_package (vcpkg provides CMake config files) # Required dependencies find_package(cmark REQUIRED) find_package(quazip REQUIRED) find_package(djvulibre REQUIRED) # Setup variables for compatibility with Unix code if(TARGET cmark::cmark) set(CMARK_FOUND TRUE) get_target_property(CMARK_INCLUDE_DIRS cmark::cmark INTERFACE_INCLUDE_DIRECTORIES) if(NOT CMARK_INCLUDE_DIRS) get_target_property(CMARK_INCLUDE_DIRS cmark::cmark INCLUDE_DIRECTORIES) endif() set(CMARK_LIBRARIES cmark::cmark) elseif(cmark_FOUND) set(CMARK_FOUND TRUE) if(cmark_INCLUDE_DIR) set(CMARK_INCLUDE_DIRS ${cmark_INCLUDE_DIR}) endif() if(cmark_LIBRARY) set(CMARK_LIBRARIES ${cmark_LIBRARY}) endif() endif() if(TARGET quazip::quazip) set(QUAZIP_FOUND TRUE) get_target_property(QUAZIP_INCLUDE_DIRS quazip::quazip INTERFACE_INCLUDE_DIRECTORIES) if(NOT QUAZIP_INCLUDE_DIRS) get_target_property(QUAZIP_INCLUDE_DIRS quazip::quazip INCLUDE_DIRECTORIES) endif() set(QUAZIP_LIBRARIES quazip::quazip) elseif(quazip_FOUND) set(QUAZIP_FOUND TRUE) if(quazip_INCLUDE_DIR) set(QUAZIP_INCLUDE_DIRS ${quazip_INCLUDE_DIR}) endif() if(quazip_LIBRARY) set(QUAZIP_LIBRARIES ${quazip_LIBRARY}) endif() endif() # DjVu setup for Windows if(TARGET djvulibre::djvulibre) set(DJVULIBRE_FOUND TRUE) get_target_property(DJVULIBRE_INCLUDE_DIR djvulibre::djvulibre INTERFACE_INCLUDE_DIRECTORIES) if(DJVULIBRE_INCLUDE_DIR) get_filename_component(DJVULIBRE_DIR ${DJVULIBRE_INCLUDE_DIR} DIRECTORY) endif() elseif(djvulibre_FOUND) set(DJVULIBRE_FOUND TRUE) if(djvulibre_INCLUDE_DIR) set(DJVULIBRE_INCLUDE_DIR ${djvulibre_INCLUDE_DIR}) get_filename_component(DJVULIBRE_DIR ${djvulibre_INCLUDE_DIR} DIRECTORY) endif() if(djvulibre_LIBRARY) get_filename_component(DJVULIBRE_LIB_DIR ${djvulibre_LIBRARY} DIRECTORY) if(NOT DJVULIBRE_DIR) set(DJVULIBRE_DIR ${DJVULIBRE_LIB_DIR}) endif() endif() endif() # Optional dependencies for Windows if(APVLV_WITH_POPPLER) find_package(Poppler QUIET) if(Poppler_FOUND) set(POPPLER_FOUND TRUE) if(TARGET Poppler::poppler) set(POPPLER_LIBRARIES Poppler::poppler) elseif(poppler_LIBRARIES) set(POPPLER_LIBRARIES ${poppler_LIBRARIES}) endif() endif() endif() if(APVLV_WITH_MUPDF) find_package(mupdf QUIET) if(mupdf_FOUND) set(MUPDF_FOUND TRUE) if(TARGET mupdf::mupdf) set(MUPDF_STATIC_LIBRARIES mupdf::mupdf) elseif(mupdf_LIBRARIES) set(MUPDF_STATIC_LIBRARIES ${mupdf_LIBRARIES}) endif() endif() endif() if(APVLV_WITH_OCR) find_package(tesseract QUIET) if(tesseract_FOUND) set(TESSERACT_FOUND TRUE) if(TARGET tesseract::tesseract) set(TESSERACT_LIBRARIES tesseract::tesseract) elseif(tesseract_LIBRARIES) set(TESSERACT_LIBRARIES ${tesseract_LIBRARIES}) endif() endif() endif() else() # Unix/Linux - use pkg-config find_package(PkgConfig REQUIRED) pkg_check_modules(CMARK libcmark REQUIRED) pkg_check_modules(QUAZIP quazip1-qt6 REQUIRED) # Optional dependencies for Unix if(APVLV_WITH_POPPLER) pkg_check_modules(POPPLER poppler-qt6) endif() if(APVLV_WITH_MUPDF) pkg_check_modules(MUPDF mupdf) endif() if(APVLV_WITH_OCR) pkg_check_modules(TESSERACT tesseract) endif() endif() # Find Qt6 components find_package(Qt6 NAMES Qt6 COMPONENTS Core Gui Widgets WebEngineWidgets Pdf PdfWidgets Xml PrintSupport REQUIRED ) # Setup Qt variables set(Qt_INCLUDE_DIRS ${Qt6Core_INCLUDE_DIRS} ${Qt6Gui_INCLUDE_DIRS} ${Qt6Widgets_INCLUDE_DIRS} ${Qt6WebEngineWidgets_INCLUDE_DIRS} ${Qt6Pdf_INCLUDE_DIRS} ${Qt6PdfWidgets_INCLUDE_DIRS} ${Qt6Xml_INCLUDE_DIRS} ${Qt6PrintSupport_INCLUDE_DIRS} ) set(Qt_LIBRARIES Qt6::Core Qt6::Gui Qt6::Widgets Qt6::WebEngineWidgets Qt6::Pdf Qt6::PdfWidgets Qt6::Xml Qt6::PrintSupport ) ================================================ FILE: cmake/Options.cmake ================================================ # Build options for apvlv option(APVLV_WITH_MUPDF "Enable MuPDF PDF engine" ON) option(APVLV_WITH_POPPLER "Enable Poppler PDF engine" OFF) option(APVLV_WITH_DJVU "Enable DjVu support" ON) option(APVLV_WITH_OFFICE "Enable Office document support" ON) option(APVLV_WITH_OCR "Enable OCR support" ON) # Platform-specific defaults if(WIN32) set(APVLV_WITH_POPPLER ON CACHE BOOL "Enable Poppler PDF engine" FORCE) endif() ================================================ FILE: scripts/build.ps1 ================================================ param( [string]$BuildDir = "", [string]$BuildType = "Release", [string]$VisualStudioVersion = "", [string]$VcpkgDir = "", [string]$VcpkgBuildTrees = "" ) $ErrorActionPreference = "Stop" function Write-Error-Exit { param([string]$Message) Write-Host "ERROR: $Message" -ForegroundColor Red exit 1 } function Test-Command { param([string]$Command) return [bool](Get-Command $Command -ErrorAction SilentlyContinue) } function Get-VisualStudio { $vsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" $instances = @() if (Test-Path $vsWhere) { try { $oldEncoding = [Console]::OutputEncoding [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 $json = & $vsWhere -all -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -format json -utf8 [Console]::OutputEncoding = $oldEncoding if ($json) { $rawInstances = $json | ConvertFrom-Json if ($rawInstances -is [System.Management.Automation.PSCustomObject]) { $rawInstances = @($rawInstances) } foreach ($item in $rawInstances) { $majorVersion = $item.installationVersion.Split('.')[0] $generator = "" switch ($majorVersion) { "18" { $generator = "Visual Studio 18 2026" } "17" { $generator = "Visual Studio 17 2022" } "16" { $generator = "Visual Studio 16 2019" } "15" { $generator = "Visual Studio 15 2017" } Default { $generator = "Visual Studio $majorVersion" } } $instances += @{ Path = $item.installationPath Version = $item.installationVersion Generator = $generator } } } } catch { [Console]::OutputEncoding = $oldEncoding } } if ($instances.Count -eq 0) { $fallback = @( @{Path = "${env:ProgramFiles}\Microsoft Visual Studio\2022\Community"; Version = "17.0"; Generator = "Visual Studio 17 2022"}, @{Path = "${env:ProgramFiles}\Microsoft Visual Studio\2019\Community"; Version = "16.0"; Generator = "Visual Studio 16 2019"} ) foreach ($item in $fallback) { if (Test-Path $item.Path) { $instances += $item } } } if ($instances.Count -eq 0) { return $null } if ($VisualStudioVersion) { $selected = $instances | Where-Object { $_.Version.StartsWith($VisualStudioVersion) } | Sort-Object Version -Descending | Select-Object -First 1 if ($selected) { return $selected } Write-Host "Warning: VS version '$VisualStudioVersion' not found, using latest" -ForegroundColor Yellow } return $instances | Sort-Object Version -Descending | Select-Object -First 1 } # Initialize paths $SrcDir = Split-Path -Parent $PSScriptRoot if (-not $BuildDir) { $BuildDir = Join-Path $env:USERPROFILE "build\apvlv" } $BuildDir = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($BuildDir) if ($VcpkgDir) { $VcpkgRoot = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($VcpkgDir) } else { $VcpkgRoot = Join-Path $BuildDir "vcpkg" } $VcpkgInstalledDir = Join-Path $BuildDir "vcpkg_installed" $BuildDirPath = Join-Path $BuildDir "build" if ($VcpkgBuildTrees) { $VcpkgBuildTreesPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($VcpkgBuildTrees) } else { $VcpkgBuildTreesPath = "" } Write-Host "Build Configuration:" -ForegroundColor Cyan Write-Host " Build Directory: $BuildDir" Write-Host " Build Type: $BuildType" Write-Host " Source Directory: $SrcDir" # Check dependencies Write-Host "Checking dependencies..." -ForegroundColor Green if (-not (Test-Command "git")) { Write-Error-Exit "Git not found" } if (-not (Test-Command "cmake")) { Write-Error-Exit "CMake not found" } $vs = Get-VisualStudio if (-not $vs) { Write-Error-Exit "Visual Studio with C++ tools not found" } Write-Host "Using: $($vs.Generator)" -ForegroundColor Green # Create build directory if (!(Test-Path $BuildDir)) { Write-Host "Creating build directory..." -ForegroundColor Green New-Item -ItemType Directory -Path $BuildDir | Out-Null } # Setup vcpkg if (!(Test-Path $VcpkgRoot) -or !(Test-Path "$VcpkgRoot\.git")) { Write-Host "Cloning vcpkg..." -ForegroundColor Green if (Test-Path $VcpkgRoot) { Remove-Item -Recurse -Force $VcpkgRoot } Push-Location $BuildDir try { git clone https://github.com/microsoft/vcpkg vcpkg if ($LASTEXITCODE -ne 0) { Write-Error-Exit "Failed to clone vcpkg" } } finally { Pop-Location } } else { Write-Host "Updating vcpkg..." -ForegroundColor Green Push-Location "$VcpkgRoot" try { $isShallow = (& git rev-parse --is-shallow-repository).Trim() if ($isShallow -eq "true") { git fetch --unshallow if ($LASTEXITCODE -ne 0) { Write-Error-Exit "Failed to unshallow vcpkg repository" } } git pull --ff-only if ($LASTEXITCODE -ne 0) { Write-Error-Exit "Failed to update vcpkg" } } finally { Pop-Location } } if (!(Test-Path "$VcpkgRoot\vcpkg.exe")) { Write-Host "Bootstrapping vcpkg..." -ForegroundColor Green Push-Location "$VcpkgRoot" try { .\bootstrap-vcpkg.bat if ($LASTEXITCODE -ne 0) { Write-Error-Exit "Failed to bootstrap vcpkg" } } finally { Pop-Location } } Write-Host "Installing vcpkg dependencies..." -ForegroundColor Green $env:VCPKG_INSTALLED_DIR = $VcpkgInstalledDir Push-Location "$SrcDir" try { $installArgs = @("install", "--triplet=x64-windows", "--clean-after-build", "--x-install-root=$VcpkgInstalledDir") if ($VcpkgBuildTreesPath) { $installArgs += "--x-buildtrees-root=$VcpkgBuildTreesPath" } & "$VcpkgRoot\vcpkg.exe" $installArgs if ($LASTEXITCODE -ne 0) { Write-Error-Exit "Failed to install dependencies" } } finally { Pop-Location } Remove-Item Env:VCPKG_INSTALLED_DIR -ErrorAction SilentlyContinue # Configure and build if (Test-Path $BuildDirPath) { Write-Host "Cleaning build directory..." -ForegroundColor Green Remove-Item -Recurse -Force $BuildDirPath } New-Item -ItemType Directory -Path $BuildDirPath | Out-Null Write-Host "Configuring with CMake..." -ForegroundColor Green Push-Location "$BuildDirPath" try { cmake "$SrcDir" ` -DCMAKE_TOOLCHAIN_FILE="$VcpkgRoot\scripts\buildsystems\vcpkg.cmake" ` -DVCPKG_TARGET_TRIPLET=x64-windows ` -DCMAKE_BUILD_TYPE=$BuildType ` -G "$($vs.Generator)" ` -A x64 if ($LASTEXITCODE -ne 0) { Write-Error-Exit "CMake configuration failed" } Write-Host "Building project..." -ForegroundColor Green $ParallelJobs = [Math]::Min([Environment]::ProcessorCount, 8) cmake --build . --config $BuildType --parallel $ParallelJobs if ($LASTEXITCODE -ne 0) { Write-Error-Exit "Build failed" } } finally { Pop-Location } Write-Host "Build completed successfully!" -ForegroundColor Green Write-Host "Output: $BuildDirPath\$BuildType" -ForegroundColor Cyan ================================================ FILE: scripts/build.sh ================================================ #!/bin/bash # Build script for apvlv using bash # Usage: ./build.sh [debug clean test package deps help] [build_path] set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color print_status() { echo -e "${GREEN}[INFO]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARN]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SRC_DIR="$(dirname "$SCRIPT_DIR")" cleanup_vcpkg_locks() { if [ -n "$VCPKG_ROOT" ] && [ -d "$VCPKG_ROOT" ]; then print_status "Cleaning up vcpkg lock files..." rm -f "$VCPKG_ROOT/.vcpkg-root.lock" 2>/dev/null || true rm -f "$VCPKG_ROOT/vcpkg.lock" 2>/dev/null || true if [ -n "$VCPKG_DOWNLOADS" ] && [ -d "$VCPKG_DOWNLOADS" ]; then rm -f "$VCPKG_DOWNLOADS/.lock" 2>/dev/null || true fi fi } detect_system_qt() { SYSTEM_QT_FOUND=0 SYSTEM_QT_PREFIX="" if [ "${USE_VCPKG_QT:-0}" = "1" ]; then print_status "USE_VCPKG_QT=1 set, skipping system Qt detection" return fi print_status "Checking for system Qt6 installation..." if command -v qmake6 &>/dev/null; then SYSTEM_QT_PREFIX=$(qmake6 -query QT_INSTALL_PREFIX 2>/dev/null) if [ -n "$SYSTEM_QT_PREFIX" ] && [ -d "$SYSTEM_QT_PREFIX" ]; then print_status "Found system Qt6 via qmake6: $SYSTEM_QT_PREFIX" SYSTEM_QT_FOUND=1 return fi fi if command -v qmake &>/dev/null; then local qt_version=$(qmake -query QT_VERSION 2>/dev/null) if [[ "$qt_version" == 6.* ]]; then SYSTEM_QT_PREFIX=$(qmake -query QT_INSTALL_PREFIX 2>/dev/null) if [ -n "$SYSTEM_QT_PREFIX" ] && [ -d "$SYSTEM_QT_PREFIX" ]; then print_status "Found system Qt6 via qmake: $SYSTEM_QT_PREFIX" SYSTEM_QT_FOUND=1 return fi fi fi if command -v pkg-config &>/dev/null; then if pkg-config --exists Qt6Core 2>/dev/null; then local qt6_libdir=$(pkg-config --variable=libdir Qt6Core 2>/dev/null) if [ -n "$qt6_libdir" ]; then SYSTEM_QT_PREFIX=$(dirname "$qt6_libdir") print_status "Found system Qt6 via pkg-config: $SYSTEM_QT_PREFIX" SYSTEM_QT_FOUND=1 return fi fi fi local common_qt_paths=( "/usr/lib/qt6" "/usr/lib64/qt6" "/usr/share/qt6" "/usr" "/usr/local" ) for qt_path in "${common_qt_paths[@]}"; do if [ -f "$qt_path/lib/cmake/Qt6/Qt6Config.cmake" ] || [ -f "$qt_path/lib64/cmake/Qt6/Qt6Config.cmake" ] || [ -f "/usr/lib/cmake/Qt6/Qt6Config.cmake" ]; then SYSTEM_QT_PREFIX="$qt_path" print_status "Found system Qt6 in: $SYSTEM_QT_PREFIX" SYSTEM_QT_FOUND=1 return fi done if [ -f "/usr/lib/cmake/Qt6/Qt6Config.cmake" ] || [ -f "/usr/lib64/cmake/Qt6/Qt6Config.cmake" ]; then SYSTEM_QT_PREFIX="/usr" print_status "Found system Qt6 CMake config in /usr" SYSTEM_QT_FOUND=1 return fi print_warning "System Qt6 not found, will use vcpkg Qt" } check_system_qt_deps() { if ! command -v pkg-config &>/dev/null; then print_warning "pkg-config not found; cannot validate system Qt dependencies." return fi local missing=0 if ! pkg-config --exists Qt6Core 2>/dev/null; then print_warning "Missing system Qt6 pkg-config entry (Qt6Core)." missing=1 fi if ! pkg-config --exists quazip1-qt6 2>/dev/null; then print_warning "Missing system QuaZIP for Qt6 (pkg-config: quazip1-qt6)." missing=1 fi if ! pkg-config --exists libcmark 2>/dev/null; then print_warning "Missing system cmark (pkg-config: libcmark)." missing=1 fi if [ "$missing" = "1" ]; then print_warning "System Qt mode selected, but some required system packages are missing." print_warning "Please install distro packages providing: Qt6, quazip-qt6, cmark." print_warning "Or force vcpkg Qt by setting USE_VCPKG_QT=1." fi } execute_in_build_dir() { local cmd="$1" cd "$BUILD_DIR" if ! eval "$cmd"; then print_error "Command failed: $cmd" exit 1 fi cd "$SRC_DIR" } execute_cmake_command() { local cmake_args=("$@") if [ "$SINGLE_CONFIG" = "false" ]; then cmake_args+=("--config" "$BUILD_TYPE") fi print_status "Running CMake command" if [ ! -d "$BUILD_DIR" ]; then print_error "Build directory not found: $BUILD_DIR" exit 1 fi if ! (cd "$BUILD_DIR" && cmake "${cmake_args[@]}"); then print_error "CMake command failed" exit 1 fi } find_and_run_test() { find_file_in_configs "testNote" "$BUILD_DIR" if [ "$FILE_FOUND" = "1" ]; then for config in "$BUILD_TYPE" "Release" "Debug"; do if [ -f "$BUILD_DIR/$config/testNote" ]; then "$BUILD_DIR/$config/testNote" if [ $? -ne 0 ]; then print_error "Test execution failed." exit 1 fi return fi done if [ -f "$BUILD_DIR/testNote" ]; then "$BUILD_DIR/testNote" if [ $? -ne 0 ]; then print_error "Test execution failed." exit 1 fi fi fi } check_tools() { if ! command -v git &>/dev/null; then print_error "Git not found." exit 1 fi if ! command -v cmake &>/dev/null; then print_error "CMake not found." exit 1 fi } setup_vcpkg() { git config --global --add safe.directory "$VCPKG_ROOT" 2>/dev/null || true local VCPKG_CLONE_URL="https://github.com/microsoft/vcpkg" if [ ! -d "$VCPKG_ROOT/.git" ]; then print_status "Cloning vcpkg..." echo if ! git clone --depth 1 '$VCPKG_CLONE_URL' '$VCPKG_ROOT'; then print_error "Failed to clone vcpkg after multiple attempts." exit 1 fi else print_status "Updating vcpkg..." echo if ! git -C "$VCPKG_ROOT" pull --ff-only; then print_error "Failed to update vcpkg after multiple attempts." echo exit 1 fi fi if [ ! -f "$VCPKG_ROOT/vcpkg" ]; then print_status "Bootstrapping vcpkg..." echo cleanup_vcpkg_locks if ! cd "$VCPKG_ROOT" && ./bootstrap-vcpkg.sh; then print_error "Failed to bootstrap vcpkg after multiple attempts." exit 1 fi fi if [ ! -f "$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" ]; then print_error "vcpkg toolchain file not found." exit 1 fi } install_dependencies() { print_status "Installing vcpkg dependencies..." cleanup_vcpkg_locks detect_system_qt local vcpkg_args=( "install" "--triplet=$VCPKG_TRIPLET" "--clean-after-build" "--x-install-root=$VCPKG_INSTALLED_DIR" ) if [ "$APVLV_WITH_OCR" = "1" ]; then vcpkg_args+=("--x-feature=ocr") fi if [ "$APVLV_WITH_MUPDF" = "1" ]; then vcpkg_args+=("--x-feature=mupdf") fi local manifest_root="$SRC_DIR" if [ "$SYSTEM_QT_FOUND" = "1" ]; then manifest_root="$SRC_DIR/vcpkg-manifests/system-qt" check_system_qt_deps print_status "Using system Qt. vcpkg manifest root: $manifest_root" else print_status "Using vcpkg Qt. vcpkg manifest root: $manifest_root" fi export VCPKG_INSTALLED_DIR if ! cd "$SRC_DIR" && "$VCPKG_ROOT/vcpkg" ${vcpkg_args[*]} --x-manifest-root='$manifest_root'; then print_error "Failed to install vcpkg dependencies after multiple attempts." exit 1 fi } ensure_vcpkg_and_deps() { setup_vcpkg install_dependencies } clean_build() { print_status "Cleaning build directory: $BUILD_DIR" if [ -d "$BUILD_DIR" ]; then rm -rf "$BUILD_DIR" if [ $? -ne 0 ]; then print_error "Failed to remove build directory." exit 1 fi fi mkdir -p "$BUILD_DIR" || { print_error "Failed to create build directory." exit 1 } } configure() { local cmake_cmd="cmake \"$SRC_DIR\"" cmake_cmd+=" -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" cmake_cmd+=" -DVCPKG_TARGET_TRIPLET=$VCPKG_TRIPLET" if [ "$SYSTEM_QT_FOUND" = "1" ]; then print_status "Configuring CMake to use system Qt from: $SYSTEM_QT_PREFIX" cmake_cmd+=" -DCMAKE_PREFIX_PATH=\"$SYSTEM_QT_PREFIX;$VCPKG_INSTALLED_DIR/$VCPKG_TRIPLET\"" cmake_cmd+=" -DVCPKG_MANIFEST_MODE=OFF" fi local vcpkg_manifest_features=() if [ "$APVLV_WITH_MUPDF" = "1" ]; then cmake_cmd+=" -DAPVLV_WITH_MUPDF=ON" vcpkg_manifest_features+=("mupdf") fi if [ "$APVLV_WITH_OCR" = "1" ]; then cmake_cmd+=" -DAPVLV_WITH_OCR=ON" vcpkg_manifest_features+=("ocr") fi if [ ${#vcpkg_manifest_features[@]} -gt 0 ] && [ "$SYSTEM_QT_FOUND" != "1" ]; then cmake_cmd+=" -DVCPKG_MANIFEST_FEATURES=$( IFS=';' echo "${vcpkg_manifest_features[*]}" )" fi if command -v ninja &>/dev/null; then cmake_cmd+=' -G "Ninja"' else cmake_cmd+=' -G "Unix Makefiles"' fi execute_in_build_dir "$cmake_cmd" } build() { execute_cmake_command "--build" "." "--parallel" } create_package() { execute_cmake_command "--build" "." "--target" "package" "--parallel" } run_tests() { find_and_run_test } find_executable() { find_file_in_configs "apvlv" "$BUILD_DIR" } find_file_in_configs() { local filename="$1" local search_base="$2" FILE_FOUND=0 for config in "$BUILD_TYPE" "Release" "Debug"; do if [ -f "$search_base/$config/$filename" ]; then print_status "Found: $search_base/$config/$filename" FILE_FOUND=1 return fi done if [ -f "$search_base/$filename" ]; then print_status "Found: $search_base/$filename" FILE_FOUND=1 fi if [ "$FILE_FOUND" = "0" ]; then print_warning "Could not locate $filename." fi } execute_build() { case "${ACTION,,}" in "clean") print_status "Cleaning build directory..." clean_build exit 0 ;; "test") print_status "Running tests..." run_tests exit 0 ;; "deps") print_status "Setting up vcpkg and installing dependencies..." ensure_vcpkg_and_deps print_status "Dependencies installed successfully." exit 0 ;; esac check_tools print_status "Ensuring vcpkg and dependencies are installed..." ensure_vcpkg_and_deps print_status "Creating build directory if it doesn't exist: $BUILD_DIR" mkdir -p "$BUILD_DIR" || print_warning "Failed to create build directory, continuing..." clean_build configure print_status "Building $BUILD_TYPE version..." build if [ "${ACTION,,}" = "package" ]; then print_status "Creating package..." create_package fi find_executable print_status "Build completed successfully!" } show_help() { echo "Usage: build.sh [debug clean test package deps help] [build_path]" echo echo "Options:" echo " debug - Build in debug mode" echo " clean - Clean build directory" echo " test - Run tests" echo " package - Create installer package" echo " deps - Install vcpkg dependencies only" echo " help - Show this help message" echo " build_path - Optional path for global build directory (default: ~/build/apvlv)" echo echo "Environment Variables:" echo " GLOBAL_BUILD_DIR - Path to global build directory" echo " VCPKG_ROOT - Path to vcpkg installation (default: \$GLOBAL_BUILD_DIR/vcpkg)" echo " VCPKG_TARGET_TRIPLET - Target triplet for vcpkg" echo " APVLV_WITH_MUPDF - Set to 1 to enable MuPDF support" echo " APVLV_WITH_OCR - Set to 1 to enable OCR support" echo " USE_VCPKG_QT - Set to 1 to force using vcpkg Qt instead of system Qt" echo " " echo "vcpkg Manifests:" echo " Default: uses $SRC_DIR/vcpkg.json (includes qtbase)" echo " System Qt mode: uses $SRC_DIR/vcpkg-manifests/system-qt/vcpkg.json (no qtbase)" echo echo "Qt Detection:" echo " The script will automatically detect system Qt6 installation." echo " If found, system Qt will be used instead of vcpkg Qt." echo " Set USE_VCPKG_QT=1 to force using vcpkg Qt." echo echo "Examples:" echo " build.sh - Build release version in default directory" echo " build.sh debug - Build debug version in default directory" echo " build.sh ~/apvlv_build - Build release version in specified directory" echo " build.sh debug ~/apvlv_build - Build debug version in specified directory" echo " build.sh deps - Install dependencies only" echo " build.sh package - Create installer" echo " USE_VCPKG_QT=1 build.sh - Force using vcpkg Qt" } #-------------------# MAIN SCRIPT# ------------------------------- # Set default values BUILD_TYPE="${BUILD_TYPE:-Release}" ACTION="${ACTION:-build}" GLOBAL_BUILD_DIR="${GLOBAL_BUILD_DIR:-$HOME/build/apvlv}" VCPKG_ROOT="${VCPKG_ROOT:-$GLOBAL_BUILD_DIR/vcpkg}" BUILD_DIR="${BUILD_DIR:-$GLOBAL_BUILD_DIR/build}" VCPKG_INSTALLED_DIR="${VCPKG_INSTALLED_DIR:-$GLOBAL_BUILD_DIR/vcpkg_installed}" VCPKG_TRIPLET="${VCPKG_TARGET_TRIPLET:-x64-linux}" # Environment variables for features USE_VCPKG_QT="${USE_VCPKG_QT:-0}" # System Qt detection results (will be set by detect_system_qt) SYSTEM_QT_FOUND=0 SYSTEM_QT_PREFIX="" # Determine if we're using a single-config generator SINGLE_CONFIG="true" if command -v ninja &>/dev/null; then SINGLE_CONFIG="false" fi # Parse arguments while [ $# -gt 0 ]; do case "${1,,}" in "debug") BUILD_TYPE="Debug" ;; "clean") ACTION="clean" ;; "test") ACTION="test" ;; "package") ACTION="package" ;; "deps") ACTION="deps" ;; "help") show_help exit 0 ;; *) # Assume it's a build path GLOBAL_BUILD_DIR="$1" VCPKG_ROOT="$GLOBAL_BUILD_DIR/vcpkg" BUILD_DIR="$GLOBAL_BUILD_DIR/build" VCPKG_INSTALLED_DIR="$GLOBAL_BUILD_DIR/vcpkg_installed" ;; esac shift done # Adjust triplet based on build type if [ "$BUILD_TYPE" = "Debug" ]; then if [[ ! "$VCPKG_TRIPLET" =~ -debug$ ]]; then VCPKG_TRIPLET="${VCPKG_TRIPLET}-debug" fi else if [[ "$VCPKG_TRIPLET" =~ -debug$ ]]; then VCPKG_TRIPLET="${VCPKG_TRIPLET%-debug}" fi fi print_status "Build type: $BUILD_TYPE" print_status "VCPKG triplet: $VCPKG_TRIPLET" print_status "Using build directory: $GLOBAL_BUILD_DIR" execute_build exit 0 ================================================ FILE: share/applications/apvlv.desktop ================================================ [Desktop Entry] Version=1.0 Type=Application Name=apvlv Comment=A minimalistic document viewer Comment[de]=Ein minimalistischer Dokumenten-Betrachter Comment[fr]=Un visionneur de document minimaliste Comment[ru]=Минималистичный просмотрщик документов Comment[tr]=Minimalist bir belge görüntüleyicisi Comment[es_CL]=Un visor de documentos minimalista Comment[uk_UA]=Легкий переглядач документів Comment[it]=Un visualizzatore di documenti minimalista Comment[pl]=Minimalistyczna przeglądarka dokumentów Exec=apvlv %f Terminal=false Categories=Office;Viewer; MimeType=application/pdf;application/epub+zip;image/vnd.djvu; Keywords=vim;pdf; Icon=x-office-document ================================================ FILE: share/scripts/internal.js ================================================ // internal.js function getSelectionOffset(index) { const selection = window.getSelection(); if (!selection.rangeCount || index >= selection.rangeCount) { return [null, null]; } const createOffsetRange = (container, offset) => { const range = document.createRange(); range.setStart(document.documentElement, 0); range.setEnd(container, offset); return range.toString().length; }; try { const range = selection.getRangeAt(index); return [createOffsetRange(range.startContainer, range.startOffset), createOffsetRange(range.endContainer, range.endOffset)]; } catch (error) { console.error('Error accessing selection range:', error); return [null, null]; } } function underlineTextNode(textNode, startOffset, endOffset, tooltipText) { if (!(textNode instanceof Text)) { throw new Error('Invalid text node provided'); } const textContent = textNode.nodeValue; const validEndOffset = endOffset === -1 ? textContent.length : endOffset; if (startOffset < 0 || validEndOffset > textContent.length || startOffset > validEndOffset) { throw new Error('Invalid offset values'); } const parent = textNode.parentNode; if (!parent) { throw new Error('Text node has no parent element'); } const beforeText = textContent.slice(0, startOffset); const underlinedText = textContent.slice(startOffset, validEndOffset); const afterText = textContent.slice(validEndOffset); const underlineElement = document.createElement('u'); underlineElement.textContent = underlinedText; if (tooltipText.length !== 0) { underlineElement.setAttribute('title', tooltipText); } const fragment = document.createDocumentFragment(); if (beforeText) fragment.appendChild(document.createTextNode(beforeText)); fragment.appendChild(underlineElement); if (afterText) fragment.appendChild(document.createTextNode(afterText)); parent.replaceChild(fragment, textNode); } function traverseTextNodes(root, callback) { const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null,); let node; while ((node = walker.nextNode())) { if (callback(node) === false) break; } } function underlineByOffset(startOffset, endOffset, tooltip_msg) { if (startOffset >= endOffset || startOffset < 0) { throw new Error('Invalid offset range'); } let currentOffset = 0; const nodesInfo = { start: {node: null, offset: 0}, end: {node: null, offset: 0}, between: [] }; traverseTextNodes(document.documentElement, (textNode) => { const nodeLength = textNode.nodeValue.length; const nodeEnd = currentOffset + nodeLength; if (!nodesInfo.start.node && currentOffset <= startOffset && nodeEnd > startOffset) { nodesInfo.start.node = textNode; nodesInfo.start.offset = startOffset - currentOffset; } if (!nodesInfo.end.node && currentOffset <= endOffset && nodeEnd > endOffset) { nodesInfo.end.node = textNode; nodesInfo.end.offset = endOffset - currentOffset; return false; } if (nodesInfo.start.node && !nodesInfo.end.node && textNode !== nodesInfo.start.node) { nodesInfo.between.push(textNode); } currentOffset = nodeEnd; return true; }); if (nodesInfo.start.node && nodesInfo.end.node) { underlineTextNode(nodesInfo.start.node, nodesInfo.start.offset, nodesInfo.start.node === nodesInfo.end.node ? nodesInfo.end.offset : -1, tooltip_msg); nodesInfo.between.forEach(node => { underlineTextNode(node, 0, -1, tooltip_msg); }); if (nodesInfo.start.node !== nodesInfo.end.node) { underlineTextNode(nodesInfo.end.node, 0, nodesInfo.end.offset, tooltip_msg); } } } function scrollByTimes(times, h, v) { window.scrollBy(times * h, times * v); } function scrollToAnchor(anchor) { if (typeof anchor === 'string' && anchor.startsWith('#')) { const element = document.getElementById(anchor.substr(1)); if (element) { element.scrollIntoView(); } } } function scrollToPosition(xrate, yrate) { const x = window.screenX * xrate; const y = (document.body.offsetHeight - window.innerHeight) * yrate; window.scroll(x, y); } function dispatchKeydownEvent(keyCode) { const event = new KeyboardEvent('keydown', { keyCode: keyCode, bubbles: true, cancelable: true }); document.dispatchEvent(event); } ================================================ FILE: src/ApvlvCmds.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvCmds.cc * * Author: Alf */ #include #include #include #include "ApvlvCmds.h" #include "ApvlvView.h" namespace apvlv { using namespace std; using namespace Qt; StringKeyMap Command::mKeyMap = { { "", Key_Backspace }, { "", Key_Tab }, { "", Key_Return }, { "", Key_Escape }, { "", Key_Space }, { "", Key_Less }, { "", Key_Backslash }, { "", Key_Bar }, { "", Key_Delete }, { "", Key_Up }, { "", Key_Down }, { "", Key_Left }, { "", Key_Right }, { "", Key_Help }, { "", Key_Insert }, { "", Key_Home }, { "", Key_End }, { "", Key_PageUp }, { "", Key_PageDown }, { "", Key_Up }, { "", Key_Down }, { "", Key_Left }, { "", Key_Right }, { "", Key_MediaPrevious }, { "", Key_MediaNext }, { "", Key_Home }, { "", Key_End }, }; CommandMap ApvlvCmds::mMaps; static int keyToControlChar (QKeyEvent *key) { int char_key = key->key (); if (key->modifiers () & Qt::ShiftModifier) { char_key = toupper (char_key); } else { char_key = tolower (char_key); } if (key->modifiers () & ControlModifier) char_key = ctrlValue (char_key); return char_key; } constexpr static bool isModifierKey (uint k) { switch (k) { case Key_Shift: case Key_CapsLock: case Key_Meta: case Key_Alt: case Key_Super_L: case Key_Super_R: case Key_Hyper_L: case Key_Hyper_R: case Key_Control: return true; default: return false; } } Command::Command () : mType (CmdType::CT_CMD), mHasPreCount (false), mPreCount (1), mOrigin (nullptr) { } void Command::type (CmdType type) { mType = type; } CmdType Command::type () { return mType; } void Command::push (string_view sv, CmdType type) { mType = type; mHasPreCount = false; mPreCount = 1; auto s = sv.data (); if (isdigit (*s)) { mHasPreCount = true; mPreCount = (signed int)strtol (s, nullptr, 10); do { s++; } while (isdigit (*s)); } if (*s == ':' || *s == '/' || *s == '?') { mStrCommand = s; mType = CmdType::CT_STRING; size_t off = mStrCommand.find (""); if (off != string::npos) { mStrCommand.erase (off, mStrCommand.length () - off); mType = CmdType::CT_STRING_RETURN; mNext = make_unique (); mNext->push (s + off + 4); } qDebug () << "set string type command: [" << mStrCommand << "]"; return; } while (*s != '\0') { s = append (s); } } void Command::process (ApvlvView *view) { if (type () == CmdType::CT_STRING) { view->promptCommand (mStrCommand.c_str ()); } else if (type () == CmdType::CT_STRING_RETURN) { view->run (mStrCommand.c_str ()); } else { for (uint k = 0; k < keyVals ()->size (); ++k) { int key = keyval (k); if (key > 0) view->process (mHasPreCount, preCount (), keyval (k)); } } if (next () != nullptr) { next ()->process (view); } } bool Command::append (QKeyEvent *key) { if (isModifierKey (key->key ())) return false; auto char_key = keyToControlChar (key); mKeyVals.push_back (char_key); return true; } const char * Command::append (const char *s) { size_t len; const char *e = strchr (s, '>'); len = strlen (s); if (len >= 4 && *s == '<' && (*e != '\0' && *(s + 2) != '-')) { e++; for (const auto &it : mKeyMap) { if (it.first.compare (0, e - s, s) == 0) { mKeyVals.push_back (it.second); return e; } } } if (len >= 5 && s[0] == '<' && s[2] == '-' && s[4] == '>') { if (s[1] == 'C') { mKeyVals.push_back (ctrlValue (s[3])); } else { qWarning () << "Can't recognize the symbol: " << s; } return s + 5; } else { mKeyVals.push_back (s[0]); return s + 1; } } void Command::setPreCount (int precount) { mPreCount = precount; mHasPreCount = true; } int Command::preCount () const { return mPreCount; } void Command::origin (Command *ori) { mOrigin = ori; } Command * Command::origin () { return mOrigin; } CommandKeyList * Command::keyVals () { return &mKeyVals; } CommandKeyList Command::keyvalv () { return mKeyVals; } Command * Command::next () { return mNext.get (); } int Command::keyval (uint id) { return id >= mKeyVals.size () ? -1 : mKeyVals[id]; } void ApvlvCmds::buildCommandMap (string_view os, string_view ms) { Command fir; fir.push (os); auto *secp = new Command (); secp->push (ms); for (auto &mMap : mMaps) { if (mMap.first == fir.keyvalv ()) { delete mMap.second; mMap.second = secp; return; } } mMaps[fir.keyvalv ()] = secp; } ApvlvCmds::ApvlvCmds (ApvlvView *view) { mView = view; mState = CmdState::CMD_OK; mTimeoutTimer = make_unique (this); QObject::connect (mTimeoutTimer.get (), SIGNAL (timeout ()), this, SLOT (timeoutCallback ())); } ApvlvCmds::~ApvlvCmds () { if (mTimeoutTimer->isActive ()) { mTimeoutTimer->stop (); } } void ApvlvCmds::append (QKeyEvent *gev) { if (mTimeoutTimer->isActive ()) { mTimeoutTimer->stop (); } if (mState == CmdState::GETTING_CMD) { CommandKeyList v = mCmdHead->keyvalv (); v.push_back (keyToControlChar (gev)); CmdReturn r = isMapCommand (&v); if (r == CmdReturn::NO_MATCH) { process (mCmdHead.get ()); mCmdHead.release (); mState = CmdState::CMD_OK; } } if (mCmdHead == nullptr) mCmdHead = make_unique (); if (mState == CmdState::CMD_OK) { if (isdigit (int (gev->key ())) && gev->key () != '0') { auto c = char (gev->key ()); mCountString += c; mState = CmdState::GETTING_COUNT; mTimeoutTimer->start (3000); return; } } else if (mState == CmdState::GETTING_COUNT) { if (isdigit (int (gev->key ()))) { auto c = char (gev->key ()); mCountString += c; mTimeoutTimer->start (3000); return; } else { if (!mCountString.empty ()) { mCmdHead->setPreCount ( int (strtol (mCountString.c_str (), nullptr, 10))); mCountString = ""; } } } bool valid = mCmdHead->append (gev); if (!valid) { mTimeoutTimer->start (3000); return; } mState = CmdState::GETTING_CMD; CmdReturn ret = isMapCommand (mCmdHead->keyVals ()); if (ret == CmdReturn::NEED_MORE) { mTimeoutTimer->start (3000); return; } Command *pcmd; if (ret == CmdReturn::MATCH) { pcmd = getMapCommand (mCmdHead.get ()); pcmd->origin (mCmdHead.get ()); process (pcmd); pcmd->origin (nullptr); pcmd = nullptr; } else { pcmd = process (mCmdHead.get ()); } mCmdHead.reset (pcmd); mState = CmdState::CMD_OK; } Command * ApvlvCmds::process (Command *cmd) { uint times = 1; Command *orig = cmd->origin (); if (orig != nullptr) { times = orig->preCount (); } for (uint i = 0; i < times; ++i) { cmd->process (mView); } return orig; } CmdReturn ApvlvCmds::isMapCommand (CommandKeyList *ack) { for (auto &mMap : mMaps) { if (*ack == mMap.first) { return CmdReturn::MATCH; } else { uint i; for (i = 0; i < ack->size (); ++i) { if ((*ack)[i] != mMap.first[i]) break; } if (i == ack->size ()) { return CmdReturn::NEED_MORE; } } } return CmdReturn::NO_MATCH; } Command * ApvlvCmds::getMapCommand (Command *cmd) { auto it = mMaps.find (*cmd->keyVals ()); return it != mMaps.end () ? it->second : nullptr; } void ApvlvCmds::timeoutCallback () { if (mCmdHead != nullptr) { process (mCmdHead.get ()); mCmdHead.release (); } mState = CmdState::CMD_OK; } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/ApvlvCmds.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvCmds.h * * Author: Alf */ #ifndef _APVLV_CMDS_H_ #define _APVLV_CMDS_H_ #include #include #include #include namespace apvlv { enum class CmdType { CT_CMD, CT_STRING, CT_STRING_RETURN }; enum class CmdState { GETTING_COUNT, GETTING_CMD, CMD_OK, }; // command type enum class CmdStatusType { CMD_NONE, CMD_MESSAGE, CMD_CMD }; // function return type enum class CmdReturn { MATCH, NEED_MORE, NO_MATCH, }; // because every unsigned char is < 256, so use this marco to stand for // Ctrl+char, Shift+char constexpr int ctrlValue (int c) { return c + 256; } using StringKeyMap = std::map; class Command; using CommandKeyList = std::vector; using CommandMap = std::map; class ApvlvView; class Command final { public: Command (); ~Command () = default; void process (ApvlvView *view); void push (std::string_view s, CmdType type = CmdType::CT_CMD); bool append (QKeyEvent *key); const char *append (const char *s); void type (CmdType type); CmdType type (); CommandKeyList *keyVals (); CommandKeyList keyvalv (); void setPreCount (int precount); [[nodiscard]] int preCount () const; int keyval (uint id); Command *next (); void origin (Command *cmd); Command *origin (); private: static StringKeyMap mKeyMap; // command type CmdType mType; // if it has count bool mHasPreCount; // how to describe this command in .apvlvrc // like , s, or :run, :vsp, ... std::string mStrCommand; // key's value list CommandKeyList mKeyVals; // cmd's pre count int mPreCount; // next command std::unique_ptr mNext; // when a key is map to other, this is the origin cmd. // after a mapped key was processed, return to this cmds Command *mOrigin; }; class ApvlvCmds : public QObject { Q_OBJECT public: explicit ApvlvCmds (ApvlvView *view); ~ApvlvCmds () override; void append (QKeyEvent *gev); static void buildCommandMap (std::string_view os, std::string_view ms); private: Command *process (Command *cmd); static CmdReturn isMapCommand (CommandKeyList *ack); static Command *getMapCommand (Command *cmd); static CommandMap mMaps; std::unique_ptr mCmdHead; // command view ApvlvView *mView; CmdState mState; std::unique_ptr mTimeoutTimer; std::string mCountString; private slots: void timeoutCallback (); }; } #endif // Local Variables: // mode: c++ // End: ================================================ FILE: src/ApvlvCompletion.cc ================================================ /* * This file is part of the apvlv package * Copyright (C) <2008> Alf * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvCompletion.cc * * Author: Alf */ #include #include #include #include "ApvlvCompletion.h" #include "ApvlvUtil.h" namespace apvlv { using namespace std; string ApvlvCompletion::complete (const string &prefix) { auto iter = std::ranges::find_if (mItems, [prefix] (const string &item) { return item.find (prefix) == 0; }); if (iter != mItems.cend ()) return *iter; else return ""; } void ApvlvCompletion::addItems (const vector &items) { mItems.insert (mItems.end (), items.begin (), items.end ()); } void ApvlvCompletion::addPath (const string &path) { vector items; auto fspath = filesystem::path{ path }; auto filename = fspath.filename (); auto dirname = fspath.parent_path (); for (auto &entry : filesystem::directory_iterator (dirname)) { auto entry_filename = entry.path ().filename ().string (); if (filename.empty () || entry_filename.find (filename.string ()) == 0) { auto item = entry.path ().string () + (entry.is_directory () ? PATH_SEP_S : ""); qDebug () << "add a item: " << item; items.emplace_back (item); } } addItems (items); } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/ApvlvCompletion.h ================================================ /* * This file is part of the apvlv package * Copyright (C) <2008> Alf * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvCompletion.h * * Author: Alf */ #ifndef _APVLV_COMPLETION_H_ #define _APVLV_COMPLETION_H_ #include #include namespace apvlv { class ApvlvCompletion final { public: explicit ApvlvCompletion (const std::vector &items) : mItems (items) { } ApvlvCompletion () = default; ~ApvlvCompletion () = default; void addItems (const std::vector &items); void addPath (const std::string &path); std::string complete (const std::string &np); private: std::vector mItems; }; }; #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/ApvlvDirectory.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvFrame.cc * * Author: Alf */ #include #include #include #include #include #include #include #include #include "ApvlvDirectory.h" #include "ApvlvFrame.h" #include "ApvlvNote.h" #include "ApvlvNoteWidget.h" #include "ApvlvParams.h" #include "ApvlvUtil.h" namespace apvlv { using namespace std; std::vector Directory::ColumnString = { QT_TR_NOOP ("Title"), QT_TR_NOOP ("Modified Time"), QT_TR_NOOP ("File Size"), QT_TR_NOOP ("Tags"), QT_TR_NOOP ("Score"), }; std::vector Directory::SortByColumnString = { QT_TR_NOOP ("Sort By Title"), QT_TR_NOOP ("Sort By Modified Time"), QT_TR_NOOP ("Sort By File Size"), QT_TR_NOOP ("Sort By Tags"), QT_TR_NOOP ("Sort By Score"), }; std::vector Directory::FilterTypeString = { QT_TR_NOOP ("Filter Title"), QT_TR_NOOP ("Filter File Name"), QT_TR_NOOP ("Filter Modified Time >="), QT_TR_NOOP ("Filter Modified Time <="), QT_TR_NOOP ("Filter File Size >="), QT_TR_NOOP ("Filter File Size <="), QT_TR_NOOP ("Filter Tag"), QT_TR_NOOP ("Filter Score >="), QT_TR_NOOP ("Filter Score <="), }; void ContentTree::keyPressEvent (QKeyEvent *event) { event->ignore (); } Directory::Directory () { setLayout (&mLayout); mLayout.addWidget (&mToolBar, 0); mLayout.addWidget (&mTreeWidget); setupToolBar (); setupTree (); auto guioptions = ApvlvParams::instance ()->getStringOrDefault ("guioptions"); if (guioptions.find ('S') == string::npos) { mToolBar.hide (); } QTimer::singleShot (50, this, SLOT (selectFirstItem ())); } void Directory::setupToolBar () { mToolBar.addWidget (&mFilterText); QObject::connect (&mFilterText, SIGNAL (textEdited (const QString &)), this, SLOT (onFilter ())); mToolBar.addSeparator (); mToolBar.addWidget (&mFilterType); for (auto const &str : FilterTypeString) { mFilterType.addItem (tr (str)); } mToolBar.addSeparator (); QObject::connect (&mFilterType, SIGNAL (activated (int)), this, SLOT (onFilter ())); auto refresh = mToolBar.addAction (tr ("Refresh")); refresh->setIcon (QIcon::fromTheme (QIcon::ThemeIcon::ViewRefresh)); QObject::connect (refresh, SIGNAL (triggered (bool)), this, SLOT (onRefresh ())); mToolBar.addSeparator (); auto expand_all = mToolBar.addAction (tr ("Expand All")); expand_all->setIcon (QIcon::fromTheme (QIcon::ThemeIcon::ListAdd)); QObject::connect (expand_all, SIGNAL (triggered (bool)), &mTreeWidget, SLOT (expandAll ())); auto collapse_all = mToolBar.addAction (tr ("Collapse All")); collapse_all->setIcon (QIcon::fromTheme (QIcon::ThemeIcon::ListRemove)); QObject::connect (collapse_all, SIGNAL (triggered (bool)), &mTreeWidget, SLOT (collapseAll ())); mToolBar.addSeparator (); mToolBar.addWidget (&mSortType); for (auto const &str : SortByColumnString) { mSortType.addItem (tr (str)); } QObject::connect (&mSortType, SIGNAL (activated (int)), this, SLOT (sortBy (int))); } void Directory::setupTree () { mTreeWidget.setColumnCount (3); mTreeWidget.setColumnWidth (static_cast (Column::Title), 300); mTreeWidget.setColumnWidth (static_cast (Column::MTime), 150); mTreeWidget.setColumnWidth (static_cast (Column::FileSize), 50); mTreeWidget.setColumnWidth (static_cast (Column::Tags), 100); mTreeWidget.setColumnWidth (static_cast (Column::Score), 50); mTreeWidget.setSortingEnabled (false); mTreeWidget.setHeaderHidden (false); QStringList labels; for (auto const &str : ColumnString) { labels.append (tr (str)); } mTreeWidget.setHeaderLabels (labels); auto headerview = mTreeWidget.header (); headerview->setSectionsClickable (true); QObject::connect (headerview, SIGNAL (sectionClicked (int)), this, SLOT (sortBy (int))); mTreeWidget.setVerticalScrollMode ( QAbstractItemView::ScrollMode::ScrollPerItem); mTreeWidget.setSelectionBehavior ( QAbstractItemView::SelectionBehavior::SelectRows); mTreeWidget.setSelectionMode ( QAbstractItemView::SelectionMode::ExtendedSelection); mTypeIcons[FileIndexType::DIR] = QIcon (IconDir.c_str ()); mTypeIcons[FileIndexType::FILE] = QIcon (IconFile.c_str ()); mTypeIcons[FileIndexType::PAGE] = QIcon (IconPage.c_str ()); QObject::connect (&mTreeWidget, SIGNAL (itemActivated (QTreeWidgetItem *, int)), this, SLOT (onRowActivated (QTreeWidgetItem *, int))); QObject::connect (&mTreeWidget, SIGNAL (itemDoubleClicked (QTreeWidgetItem *, int)), this, SLOT (onRowDoubleClicked ())); mTreeWidget.setContextMenuPolicy (Qt::ContextMenuPolicy::CustomContextMenu); QObject::connect (&mTreeWidget, SIGNAL (customContextMenuRequested (const QPoint &)), this, SLOT (onContextMenuRequest (const QPoint &))); } bool Directory::isReady () { return (mTreeWidget.topLevelItemCount () > 0); } void Directory::setIndex (const FileIndex &index) { using enum FileIndexType; if (mTreeWidget.topLevelItemCount () == 0 || index.type == DIR) { refreshIndex (index); return; } auto cur_index = currentItemFileIndex (); if (mIndex.type == DIR && cur_index->type == FILE && index.type == FILE) { if (cur_index->mChildrenIndex.empty ()) { auto cur_item = selectedTreeItem (); cur_index->moveChildChildren (index); for (auto &child : cur_index->mChildrenIndex) { setIndex (child, cur_item); } } } } void Directory::preloadTags (const std::string &path) { if (path.empty ()) { return; } const auto &exts = FileFactory::supportFileExts (); for (const auto &entry : filesystem::recursive_directory_iterator (filesystem::path (path))) { if (!entry.is_regular_file ()) continue; const auto &p = entry.path (); if (std::find (exts.begin (), exts.end (), p.extension ()) == exts.end ()) continue; const auto &np = Note::notePathOfPath (p.string ()); const auto &n = make_unique (); if (n->load (np)) { const auto &tags = n->tag (); for (const auto &tag : tags) { mTags.append (QString::fromLocal8Bit (tag)); } } } } void Directory::setItemSelected (QTreeWidgetItem *item) { auto selitems = mTreeWidget.selectedItems (); for (auto selitem : selitems) { selitem->setSelected (false); } auto parent = item->parent (); while (parent) { mTreeWidget.expandItem (parent); parent = parent->parent (); } item->setSelected (true); if (item->isExpanded ()) mTreeWidget.collapseItem (item); mTreeWidget.scrollToItem (item); } void Directory::setIndex (FileIndex &index, QTreeWidgetItem *root_itr) { auto itr = new QTreeWidgetItem (); setFileIndexToTreeItem (itr, &index); root_itr->addChild (itr); for (auto &child : index.mChildrenIndex) { setIndex (child, itr); } } void Directory::refreshIndex (const FileIndex &index) { mTreeWidget.clear (); mIndex = index; for (auto &child : mIndex.mChildrenIndex) { setIndex (child, mTreeWidget.invisibleRootItem ()); } sortItems (mTreeWidget.invisibleRootItem ()); preloadTags (mIndex.path); QTimer::singleShot (50, this, SLOT (selectFirstItem ())); } void Directory::setFileIndexToTreeItem (QTreeWidgetItem *item, FileIndex *index) { using enum Column; auto variant = QVariant::fromValue (index); item->setData (static_cast (Title), Qt::UserRole, variant); item->setText (static_cast (Title), QString::fromLocal8Bit (index->title.c_str ())); item->setIcon (static_cast (Title), mTypeIcons[index->type]); item->setToolTip (static_cast (Title), QString::fromLocal8Bit (index->path)); using enum FileIndexType; if (index->type == FILE) { const auto date = QDateTime::fromSecsSinceEpoch ( index->mtime, QTimeZone::systemTimeZone ()); item->setText (static_cast (MTime), date.toString ("yyyy-MM-dd HH:mm:ss")); auto size = QLocale ().formattedDataSize (index->size); item->setText (static_cast (FileSize), size); item->setText (static_cast (Tags), QString::fromLocal8Bit (index->tags)); item->setToolTip (static_cast (Tags), QString::fromLocal8Bit (index->tags)); const auto score = index->score > 0 ? QString::number (index->score) : ""; item->setText (static_cast (Score), score); } } FileIndex * Directory::getFileIndexFromTreeItem (QTreeWidgetItem *item) { if (item == nullptr) return nullptr; auto varaint = item->data (static_cast (Column::Title), Qt::UserRole); auto index = varaint.value (); return index; } FileIndex * Directory::treeItemToFileIndex (QTreeWidgetItem *item) { while (item != nullptr) { auto index = getFileIndexFromTreeItem (item); if (index->type == FileIndexType::FILE) return index; else item = item->parent (); } return nullptr; } void Directory::filterItemBy (QTreeWidgetItem *root, const filterFunc &filter_func) { std::stack item_stack; for (auto i = 0; i < root->childCount (); ++i) { auto item = root->child (i); item_stack.push (item); } while (!item_stack.empty ()) { auto item = item_stack.top (); item_stack.pop (); auto index = getFileIndexFromTreeItem (item); auto [is_filter, same_as_file] = filter_func (index); if (is_filter) { item->setHidden (false); auto parent = item->parent (); while (parent) { parent->setHidden (false); parent = parent->parent (); } } else { item->setHidden (true); } if (index->type == FileIndexType::FILE && same_as_file) { setItemChildrenFilter (item, is_filter); } else { for (int i = 0; i < item->childCount (); ++i) { auto child_item = item->child (i); item_stack.push (child_item); } } } } void Directory::setItemChildrenFilter (QTreeWidgetItem *root, bool is_filter) { filterItemBy (root, [is_filter] (const FileIndex *a) -> filterFuncReturn { return { is_filter, false }; }); } QTreeWidgetItem * Directory::findTreeWidgetItem (QTreeWidgetItem *itr, FileIndexType type, const string &path, int pn, const string &anchor) { if (itr == nullptr) return nullptr; auto index = getFileIndexFromTreeItem (itr); if (index == nullptr || index->type != type) { for (auto ind = 0; ind < itr->childCount (); ++ind) { auto child_itr = itr->child (ind); auto citr = findTreeWidgetItem (child_itr, type, path, pn, anchor); if (citr) return citr; } return nullptr; } auto file_index = treeItemToFileIndex (itr); if (file_index == nullptr) { if (index->page == pn && (anchor.empty () || index->anchor == anchor)) { return itr; } return nullptr; } if (file_index->path == path && index->page == pn && (anchor.empty () || index->anchor == anchor)) { return itr; } return nullptr; } bool Directory::setCurrentIndex (const string &path, int pn, const string &anchor) { auto itr = selectedTreeItem (); auto fitr = findTreeWidgetItem (itr, FileIndexType::PAGE, path, pn, anchor); if (fitr == nullptr) fitr = findTreeWidgetItem (mTreeWidget.invisibleRootItem (), FileIndexType::PAGE, path, pn, anchor); if (fitr == nullptr) fitr = findTreeWidgetItem (mTreeWidget.invisibleRootItem (), FileIndexType::FILE, path, pn, anchor); if (fitr) { setItemSelected (fitr); return true; } return false; } void Directory::scrollUp (int times) { auto item = selectedTreeItem (); if (item == nullptr) return; auto parent = item->parent (); if (parent == nullptr) { parent = mTreeWidget.invisibleRootItem (); } auto index = parent->indexOfChild (item); if (index > 0) { auto new_index = index > times ? index - times : 0; auto itr = parent->child (new_index); setItemSelected (itr); } } void Directory::scrollDown (int times) { auto item = selectedTreeItem (); if (item == nullptr) return; auto parent = item->parent (); if (parent == nullptr) { parent = mTreeWidget.invisibleRootItem (); } auto index = parent->indexOfChild (item); auto count = parent->childCount (); auto new_index = index + times < count ? index + times : count - 1; auto itr = parent->child (new_index); setItemSelected (itr); } void Directory::scrollLeft (int times) { auto item = selectedTreeItem (); if (item == nullptr) return; if (item->parent () == nullptr) return; auto parent = item->parent (); while (--times > 0 && parent->parent ()) { parent = parent->parent (); } setItemSelected (parent); } void Directory::scrollRight (int times) { auto item = selectedTreeItem (); if (item == nullptr) return; if (item->childCount () == 0) return; item = item->child (0); while (--times > 0 && item->childCount () > 0) { item = item->child (0); } setItemSelected (item); } void Directory::tag () { auto item = selectedTreeItem (); auto cur = getFileIndexFromTreeItem (item); if (cur == nullptr) return; if (cur->type != FileIndexType::FILE) return; auto path = Note::notePathOfPath (cur->path); auto note = make_unique (); filesystem::path note_path (path); if (exists (note_path)) { if (note->load (path) == false) { auto msg = QString (tr ("load note of %s error")).arg (cur->path.c_str()); QMessageBox::warning (nullptr, tr ("error"), msg); return; } } filesystem::path file_path{ cur->path }; string filename = file_path.filename (); auto ans = NoteDialog::getTag (filename, note->tag (), mTags); if (ans.isEmpty ()) { return; } note->addTag (ans.toStdString ()); if (!exists (note_path)) { note->dump (path); } auto tags = note->tagString (); if (cur->tags != tags) { cur->tags = tags; item->setText (static_cast (Column::Tags), QString::fromLocal8Bit (cur->tags)); } } void Directory::onFileRename () { auto items = mTreeWidget.selectedItems (); if (items.isEmpty ()) return; auto item = items[0]; auto index = getFileIndexFromTreeItem (item); if (index->type != FileIndexType::FILE) return; auto qpath = QString::fromLocal8Bit (index->path); auto text = QString (tr ("Input new name of %1")).arg (qpath); auto user_text = QInputDialog::getText (this, tr ("Rename"), text, QLineEdit::Normal, qpath); auto nname = user_text.trimmed (); if (nname.isEmpty ()) return; if (QFile (qpath).rename (nname)) { index->path = nname.toStdString (); index->title = index->path.substr (index->path.rfind ('/') + 1); setFileIndexToTreeItem (item, index); } else { text = QString (tr ("Rename %1 to %2 failed")).arg (qpath).arg (nname); QMessageBox::warning (this, tr ("Warning"), text); } } void Directory::onFileDelete () { auto items = mTreeWidget.selectedItems (); if (items.isEmpty ()) return; auto msg_res = QMessageBox::No; for (auto item : items) { auto index = getFileIndexFromTreeItem (item); if (index->type != FileIndexType::FILE) continue; auto qpath = QString::fromLocal8Bit (index->path); auto text = QString (tr ("Will delete the \n%1, confirm ?")).arg (qpath); if (msg_res == QMessageBox::No || msg_res == QMessageBox::Yes) { auto buttons = QMessageBox::Yes | QMessageBox::No; if (items.size () > 1) buttons = QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll; msg_res = QMessageBox::question (this, tr ("Confirm"), text, buttons, QMessageBox::No); } if (msg_res == QMessageBox::NoToAll) return; else if (msg_res == QMessageBox::No) continue; auto parent = item->parent (); if (parent == nullptr) { auto offset = mTreeWidget.indexOfTopLevelItem (item); mIndex.removeChild (*index); item = mTreeWidget.takeTopLevelItem (offset); delete item; } else { auto pindex = getFileIndexFromTreeItem (parent); pindex->removeChild (*index); parent->removeChild (item); } qDebug () << "delete " << qpath; QFile::remove (qpath); } } void Directory::onRefresh () { refreshIndex (mIndex); } void Directory::onFilter () { auto root = mTreeWidget.invisibleRootItem (); auto cur = mFilterType.currentIndex (); auto type = static_cast (cur); auto text = mFilterText.text ().trimmed (); if (text.isEmpty ()) { setItemChildrenFilter (root, true); return; } QDateTime datetime; qint64 qsize; decltype (FileIndex::size) size; float score; switch (type) { case FilterType::Title: filterItemBy (root, [text] (const FileIndex *a) -> filterFuncReturn { return { a->title.find (text.toStdString ()) != string::npos, false }; }); break; case FilterType::FileName: filterItemBy (root, [text] (const FileIndex *a) -> filterFuncReturn { return { a->type == FileIndexType::FILE && a->title.find (text.toStdString ()) != string::npos, true }; }); break; case FilterType::MTimeBe: datetime = QDateTime::fromString (text); filterItemBy (root, [datetime] (const FileIndex *a) -> filterFuncReturn { return { a->type == FileIndexType::FILE && a->mtime >= datetime.toMSecsSinceEpoch (), true }; }); break; case FilterType::MTimeLe: datetime = QDateTime::fromString (text); filterItemBy (root, [datetime] (const FileIndex *a) -> filterFuncReturn { return { a->type == FileIndexType::FILE && a->mtime <= datetime.toMSecsSinceEpoch (), true }; }); break; case FilterType::FileSizeBe: qsize = parseFormattedDataSize (text); size = static_cast (qsize); filterItemBy (root, [size] (const FileIndex *a) -> filterFuncReturn { return { a->type == FileIndexType::FILE && a->size >= size, true }; }); break; case FilterType::FileSizeLe: qsize = parseFormattedDataSize (text); size = static_cast (qsize); filterItemBy (root, [size] (const FileIndex *a) -> filterFuncReturn { return { a->type == FileIndexType::FILE && a->size <= size, true }; }); break; case FilterType::Tags: filterItemBy (root, [text] (const FileIndex *a) -> filterFuncReturn { return { a->type == FileIndexType::FILE && a->tags.find (text.toStdString ()) != string::npos, true }; }); break; case FilterType::ScoreBe: score = QString (text).toFloat (); filterItemBy (root, [score] (const FileIndex *a) -> filterFuncReturn { return { a->type == FileIndexType::FILE && a->score >= score, true }; }); break; case FilterType::ScoreLe: score = QString (text).toFloat (); filterItemBy (root, [score] (const FileIndex *a) -> filterFuncReturn { return { a->type == FileIndexType::FILE && a->score <= score, true }; }); break; default: qWarning () << tr ("Filter Type is invalid"); break; } mTreeWidget.expandAll (); } void Directory::sortItems (QTreeWidgetItem *tree_iter) { stack needSort; needSort.push (tree_iter); using itemState = pair; while (!needSort.empty ()) { auto root = needSort.top (); needSort.pop (); vector item_list; for (auto i = 0; i < root->childCount (); ++i) { auto item = root->child (i); auto index = getFileIndexFromTreeItem (item); if (index->type != FileIndexType::PAGE) item_list.emplace_back (item, item->isExpanded ()); } std::ranges::for_each ( item_list, [this, &needSort] (itemState &p) { if (p.first->childCount () > 1) { auto index = getFileIndexFromTreeItem (p.first); if (index && index->type == FileIndexType::DIR) { needSort.push (p.first); } } }); if (mSortAscending) { switch (mSortColumn) { using enum Column; case Title: std::ranges::sort (item_list, std::ranges::greater (), [this] (itemState &p) { auto index = getFileIndexFromTreeItem (p.first); return index ? index->title : ""; }); break; case MTime: std::ranges::sort (item_list, std::ranges::greater (), [this] (itemState &p) { auto index = getFileIndexFromTreeItem (p.first); return index ? index->mtime : 0; }); break; case FileSize: std::ranges::sort (item_list, std::ranges::greater (), [this] (itemState &p) { auto index = getFileIndexFromTreeItem (p.first); return index ? index->size : 0; }); break; case Tags: std::ranges::sort (item_list, std::ranges::greater (), [this] (itemState &p) { auto index = getFileIndexFromTreeItem (p.first); return index ? index->tags : ""; }); break; case Score: std::ranges::sort (item_list, std::ranges::greater (), [this] (itemState &p) { auto index = getFileIndexFromTreeItem (p.first); return index ? index->score : 0.0f; }); break; } } else { switch (mSortColumn) { using enum Column; case Title: std::ranges::sort (item_list, std::ranges::less (), [this] (itemState &p) { auto index = getFileIndexFromTreeItem (p.first); return index ? index->title : ""; }); break; case MTime: std::ranges::sort (item_list, std::ranges::less (), [this] (itemState &p) { auto index = getFileIndexFromTreeItem (p.first); return index ? index->mtime : 0; }); break; case FileSize: std::ranges::sort (item_list, std::ranges::less (), [this] (itemState &p) { auto index = getFileIndexFromTreeItem (p.first); return index ? index->size : 0; }); break; case Tags: std::ranges::sort (item_list, std::ranges::less (), [this] (itemState &p) { auto index = getFileIndexFromTreeItem (p.first); return index ? index->tags : ""; }); break; case Score: std::ranges::sort (item_list, std::ranges::less (), [this] (itemState &p) { auto index = getFileIndexFromTreeItem (p.first); return index ? index->score : 0.0f; }); break; } } mTreeWidget.setUpdatesEnabled (false); for (auto i = 0; static_cast (i) < item_list.size (); ++i) { auto p = item_list[i]; auto oi = root->indexOfChild (p.first); if (oi != i) { root->takeChild (oi); root->insertChild (i, p.first); p.first->setExpanded (p.second); } } mTreeWidget.setUpdatesEnabled (true); } mTreeWidget.update (); } void Directory::onRowActivated ([[maybe_unused]] QTreeWidgetItem *item, [[maybe_unused]] int column) { mFrame->directoryShowPage (currentItemFileIndex (), true); mFrame->toggledControlDirectory (true); } void Directory::onRowDoubleClicked () { parentWidget ()->setFocus (); } void Directory::onContextMenuRequest (const QPoint &point) { auto items = mTreeWidget.selectedItems (); if (items.isEmpty ()) return; auto item = items[0]; auto index = getFileIndexFromTreeItem (item); if (index->type == FileIndexType::FILE) { mItemMenu.clear (); if (items.size () == 1) { auto rename_action = mItemMenu.addAction (tr ("Rename File")); QObject::connect (rename_action, SIGNAL (triggered (bool)), this, SLOT (onFileRename ())); } if (std::all_of (items.begin (), items.end (), [this] (QTreeWidgetItem *a) { auto i = getFileIndexFromTreeItem (a); return i && i->type == FileIndexType::FILE; })) { auto del_action = mItemMenu.addAction (tr ("Delete File")); del_action->setIcon ( QIcon::fromTheme (QIcon::ThemeIcon::EditDelete)); QObject::connect (del_action, SIGNAL (triggered (bool)), this, SLOT (onFileDelete ())); } mItemMenu.popup (mTreeWidget.mapToGlobal (point)); } } void Directory::selectFirstItem () { if (setCurrentIndex (mFrame->filename (), mFrame->pageNumber (), "")) return; if (mTreeWidget.topLevelItemCount () > 0) { auto itr = mTreeWidget.topLevelItem (0); setItemSelected (itr); } } FileIndex * Directory::currentItemFileIndex () { auto item = selectedTreeItem (); auto index = getFileIndexFromTreeItem (item); return index; } FileIndex * Directory::currentFileFileIndex () { auto item = selectedTreeItem (); auto index = treeItemToFileIndex (item); return index; } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/ApvlvDirectory.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvFrame.h * * Author: Alf */ #ifndef _APVLV_CONTENT_H_ #define _APVLV_CONTENT_H_ #include #include #include #include #include #include #include #include #include #include "ApvlvFile.h" #include "ApvlvUtil.h" #include "ApvlvWidget.h" namespace apvlv { class ContentTree : public QTreeWidget { protected: void keyPressEvent (QKeyEvent *event) override; }; class ApvlvFrame; class Directory final : public QFrame { Q_OBJECT public: Directory (); ~Directory () override = default; bool isReady (); enum class Column : int { Title = 0, MTime, FileSize, Tags, Score }; static std::vector ColumnString; static std::vector SortByColumnString; enum class FilterType : int { Title = 0, FileName, MTimeBe, MTimeLe, FileSizeBe, FileSizeLe, Tags, ScoreBe, ScoreLe }; static std::vector FilterTypeString; FileIndex *currentItemFileIndex (); FileIndex *currentFileFileIndex (); QTreeWidgetItem *findTreeWidgetItem (QTreeWidgetItem *itr, FileIndexType type, const std::string &path, int pn, const std::string &anchor); bool setCurrentIndex (const std::string &path, int pn, const std::string &anchor); void setFrame (ApvlvFrame *frame) { mFrame = frame; } void focusFilter () { QTimer::singleShot (50, &mFilterText, SLOT (setFocus ())); } void scrollUp (int times); void scrollDown (int times); void scrollLeft (int times); void scrollRight (int times); void tag (); void setActive (bool active) { if (active) { QTimer::singleShot (50, &mTreeWidget, SLOT (setFocus ())); } else { mTreeWidget.clearFocus (); } } bool isActive () { return mTreeWidget.hasFocus (); } private: QVBoxLayout mLayout; QToolBar mToolBar; ApvlvLineEdit mFilterText; QComboBox mFilterType; QComboBox mSortType; ContentTree mTreeWidget; QMenu mItemMenu; std::map mTypeIcons; FileIndex mIndex; QStringList mTags; Column mSortColumn{ Column::Title }; ApvlvFrame *mFrame{ nullptr }; bool mSortAscending{ true }; void setupToolBar (); void setupTree (); QTreeWidgetItem * selectedTreeItem () { auto selitems = mTreeWidget.selectedItems (); return selitems.isEmpty () ? nullptr : selitems[0]; } void setItemSelected (QTreeWidgetItem *item); void setIndex (FileIndex &index, QTreeWidgetItem *root_itr); void refreshIndex (const FileIndex &index); void setFileIndexToTreeItem (QTreeWidgetItem *item, FileIndex *index); FileIndex *getFileIndexFromTreeItem (QTreeWidgetItem *item); FileIndex *treeItemToFileIndex (QTreeWidgetItem *item); using filterFuncReturn = std::tuple; using filterFunc = std::function; void filterItemBy (QTreeWidgetItem *root, const filterFunc &filter_func); void setItemChildrenFilter (QTreeWidgetItem *root, bool is_filter); private slots: void onFileRename (); void onFileDelete (); void onRefresh (); void onFilter (); void sortBy (int method) { mSortAscending = !mSortAscending; mSortColumn = static_cast (method); sortItems (mTreeWidget.invisibleRootItem ()); } void sortItems (QTreeWidgetItem *root); void onRowActivated (QTreeWidgetItem *item, int column); void onRowDoubleClicked (); void onContextMenuRequest (const QPoint &point); void selectFirstItem (); void setIndex (const FileIndex &index); void preloadTags (const std::string &path); }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/ApvlvDired.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvDired.cc * * Author: Alf */ #include #include #include #include "ApvlvDired.h" namespace apvlv { using namespace std; DiredDialog::DiredDialog (QWidget *parent) : QDialog (parent) { setLayout (&mVboxLayout); } void DiredDialog::activateItem (QListWidgetItem *item) { auto words = item->data (Qt::UserRole).toStringList (); auto path = words[0]; auto pn = words[1].toInt (); emit loadFile (path.toStdString (), pn); accept (); } } ================================================ FILE: src/ApvlvDired.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvDired.h * * Author: Alf */ #ifndef _APVLV_DIRED_H_ #define _APVLV_DIRED_H_ #include #include #include #include #include #include #include namespace apvlv { class DiredDialog : public QDialog { Q_OBJECT public: explicit DiredDialog (QWidget *parent = nullptr); ~DiredDialog () override = default; signals: void loadFile (const std::string &path, int pn); private: QVBoxLayout mVboxLayout; private slots: void activateItem (QListWidgetItem *item); }; } #endif ================================================ FILE: src/ApvlvEditor.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvEditor.cc * * Author: Alf */ #include "ApvlvEditor.h" namespace apvlv { Editor::Editor (QWidget *parent) : QTextEdit (parent) { auto font = currentFont (); mFontFamily = font.family (); mFontPointSize = font.pointSizeF (); mFontWeight = font.weight (); mFontPixelSize = font.pixelSize (); } void Editor::setZoomrate (double zm) { auto font = currentFont (); auto updated = false; if (mFontPointSize != -1) { auto pointsize = mFontPointSize * zm; font.setPointSizeF (pointsize); updated = true; } else if (mFontPixelSize != -1) { auto pixelsize = mFontPixelSize * zm; font.setPixelSize (static_cast (pixelsize)); updated = true; } if (updated) { setFont (font); update (); } } } ================================================ FILE: src/ApvlvEditor.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvEditor.h * * Author: Alf */ #ifndef _APVLV_EDITOR_H_ #define _APVLV_EDITOR_H_ #include namespace apvlv { class Editor : public QTextEdit { Q_OBJECT public: explicit Editor (QWidget *parent = nullptr); ~Editor () override = default; void setZoomrate (double zm); private: QString mFontFamily; QFont::Weight mFontWeight; qreal mFontPointSize; int mFontPixelSize; }; } #endif ================================================ FILE: src/ApvlvFile.cc ================================================ /* * This file is part of the apvlv package * Copyright (C) <2008> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE File.cc * * Author: Alf */ #include #include #include #include #include #include #include #include #include #include #include #include "ApvlvFile.h" #include "ApvlvUtil.h" #include "ApvlvWebViewWidget.h" namespace apvlv { using namespace std; const string html_template = "\n" "\n" " \n" " \n" " \n" " \n" " \n" "
" " " "
" " \n" "\n"; map> FileFactory::mSupportMimeTypes; map FileFactory::mSupportClass; const map> & FileFactory::supportMimeTypes () { return mSupportMimeTypes; } vector FileFactory::supportFileExts () { unordered_set extSet; for (const auto &pair : mSupportMimeTypes) { extSet.insert (pair.second.begin (), pair.second.end ()); } vector exts (extSet.begin (), extSet.end ()); return exts; } ostream & FileFactory::typeEngineDescription (ostream &os) { os << "Engines: " << endl; for (auto &pair : mSupportClass) { string ext = pair.first; string engines; std::ranges::for_each (pair.second, [&engines] (ExtClass &cls) { engines.append (" "); engines.append (cls.first); }); os << "\t" << pair.first << ":\t\t" << engines << endl; } os << endl; return os; } int FileFactory::registerClass (const string &name, const function &fun, initializer_list exts) { mSupportMimeTypes.insert ({ name, exts }); std::ranges::for_each (exts, [=] (const string &t) { auto iter = mSupportClass.find (t); if (iter != mSupportClass.end ()) { auto &cls_list = iter->second; cls_list.emplace_back (name, fun); } else { auto cls_list = ExtClassList{ { name, fun } }; mSupportClass.insert ({ t, cls_list }); } }); return static_cast (mSupportMimeTypes.size ()); } optional FileFactory::findMatchClass (const std::string &filename) { auto ext = filenameExtension (filename); if (ext.empty ()) return nullopt; if (mSupportClass.find (ext) == mSupportClass.end ()) return nullopt; auto cls_list = mSupportClass[ext]; if (cls_list.size () == 1) return cls_list[0]; auto cls_name = ApvlvParams::instance ()->getGroupStringOrDefault (ext, "engine", ""); if (cls_name.empty ()) return cls_list[0]; for (auto const &cls : cls_list) { if (strcasecmp (cls.first.c_str (), cls_name.c_str ()) == 0) return cls; } return cls_list[0]; } unique_ptr FileFactory::loadFile (const string &filename) { auto cls = findMatchClass (filename); if (!cls.has_value ()) { qWarning () << "no engine to open " << filename; return nullptr; } auto file = unique_ptr (cls->second ()); if (file->setFilename (filename)) return file; else { qWarning () << "open " << QString::fromLocal8Bit (filename) << " error"; return nullptr; } } File::~File () { mPages.clear (); srcPages.clear (); srcMimeTypes.clear (); } unique_ptr File::grepFile (const string &seq, bool is_case, bool is_regex, atomic &is_abort) { vector page_matches; auto pageSum = sum (); for (auto pn = 0; pn < pageSum; ++pn) { if (is_abort.load () == true) { qDebug () << "grep " << QString::fromLocal8Bit (mFilename) << " abort before page " << pn; return nullptr; } auto size = pageSizeF (pn, 0); string content; if (pageText (pn, { 0, 0, size.width, size.height }, content) == false) continue; istringstream iss{ content }; string line; SearchMatchList matches; while (getline (iss, line)) { if (is_abort.load () == true) { qDebug () << "grep " << QString::fromLocal8Bit (mFilename) << " abort at page " << pn; return nullptr; } auto founds = apvlv::grep (line, seq, is_case, is_regex); for (auto const &found : founds) { SearchMatch match{ line.substr (found.first, found.second), line, found.first, found.second }; matches.push_back (match); } } if (!matches.empty ()) { page_matches.push_back ({ pn, matches }); } } if (page_matches.empty ()) return nullptr; auto file_match = make_unique (); file_match->filename = getFilename (); file_match->page_matches = std::move (page_matches); return file_match; } bool File::pageRenderToImage (int pn, double zm, int rot, QImage *img) { return false; } bool File::pageRenderToWebView (int pn, double zm, int rot, WebView *webview) { webview->setZoomFactor (zm); QUrl pdfuri = QString ("apvlv:///%1-%2-%3-%4.html") .arg (pn) .arg (zm) .arg (rot) .arg (rand ()); webview->load (pdfuri); return true; } string File::pathMimeType (const string &path) { if (srcMimeTypes.find (path) != srcMimeTypes.end ()) return srcMimeTypes[path]; else if (QString::fromLocal8Bit (path).endsWith (".png")) return "image/png"; else return "text/html"; } int File::pathPageNumber (const string &path) { if (srcPages.find (path) != srcPages.end ()) return srcPages[path]; return -1; } optional File::pathContent (const string &path) { auto words = QString::fromLocal8Bit (path).split ("-"); int pn = words[0].toInt (); double zm = words[1].toDouble (); int rot = words[2].toInt (); if (QString::fromLocal8Bit (path).endsWith (".html")) return pathContentHtml (pn, zm, rot); else return pathContentPng (pn, zm, rot); } optional File::pathContentHtml (int pn, double zm, int rot) { auto src = QString ("apvlv:///%1-%2-%3-%4.png") .arg (pn) .arg (zm) .arg (rot) .arg (rand ()); auto html = QString::asprintf (html_template.c_str (), src.toStdString ().c_str ()); return QByteArray::fromStdString (html.toStdString ()); } optional File::pathContentPng (int pn, double zm, int rot) { QImage image; if (pageRenderToImage (pn, zm, rot, &image) == false) return nullopt; QByteArray array; QBuffer buffer (&array); buffer.open (QIODevice::WriteOnly); image.save (&buffer, "PNG"); buffer.close (); return array; } bool File::print (int page) { QPrinter printer; QPrintDialog dialog (&printer); if (dialog.exec () != QDialog::Accepted) return false; QPainter painter (&printer); int startPage = (page >= 0) ? page : 0; int endPage = (page >= 0) ? page : sum () - 1; for (int pn = startPage; pn <= endPage; ++pn) { if (pn > startPage) printer.newPage (); QImage image; if (pageRenderToImage (pn, 1.0, 0, &image)) { QRect rect = painter.viewport (); QSize size = image.size (); size.scale (rect.size (), Qt::KeepAspectRatio); painter.drawImage (0, 0, image.scaled (size)); } } return true; } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/ApvlvFile.h ================================================ /* * This file is part of the apvlv package * Copyright (C) <2008> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE File.h * * Author: Alf */ #ifndef _APVLV_FILE_H_ #define _APVLV_FILE_H_ #include #include #include #include #include "ApvlvFileIndex.h" #include "ApvlvNote.h" #include "ApvlvParams.h" #include "ApvlvSearch.h" namespace apvlv { enum class DISPLAY_TYPE { IMAGE, HTML, CUSTOM, }; // // link to an url, or a page num // struct ApvlvLink { int mPage; }; struct ApvlvCover { std::string content; std::string mime_type; }; using ApvlvLinks = std::vector; struct ApvlvPoint { double x; double y; }; struct Size { int width; int height; }; struct SizeF { double width; double height; }; /* * position of a search result, or just an area */ struct Rectangle { double p1x; double p1y; double p2x; double p2y; }; using CharRectangle = Rectangle; struct WordRectangle { std::string word; std::vector rect_list; }; using WordListRectangle = std::vector; class WebView; class FileWidget; class File { public: virtual ~File (); virtual bool load (const std::string &filename) = 0; /* File methods */ [[nodiscard]] virtual DISPLAY_TYPE getDisplayType () const { return DISPLAY_TYPE::HTML; } virtual FileWidget * getWidget () { return nullptr; } const std::string & getFilename () { return mFilename; } bool setFilename (const std::string &filename) { if (load (filename)) { mFilename = filename; auto note_path = Note::notePathOfFile (this); mNote.load (note_path); return true; } return false; } const ApvlvCover & getCover () { return mCover; } Note * getNote () { return &mNote; } const FileIndex & getIndex () { return mIndex; } virtual std::unique_ptr grepFile (const std::string &seq, bool is_case, bool is_regex, std::atomic &is_abort); virtual int sum () { return 1; }; // Page methods Size pageSize (int page, int rot) { auto sizef = pageSizeF (page, rot); return { static_cast (sizef.width), static_cast (sizef.height) }; } virtual SizeF pageSizeF (int page, int rot) { return { 0, 0 }; } virtual int pageNumberWrap (int page) { auto scrdoc = ApvlvParams::instance ()->getBoolOrDefault ("autoscrolldoc"); int c = sum (); if (page >= 0 && page < c) { return page; } else if (page >= c && scrdoc) { return page % c; } else if (page < 0 && scrdoc) { while (page < 0) page += c; return page; } else { return -1; } } virtual bool pageRenderToImage (int pn, double zm, int rot, QImage *img); virtual bool pageRenderToWebView (int pn, double zm, int rot, WebView *webview); // some ebooks only have image as page virtual bool pageIsOnlyImage (int pn) { return false; } virtual std::unique_ptr pageLinks (int pn) { return nullptr; } virtual bool pageText (int pn, const Rectangle &rect, std::string &text) { return false; } virtual std::unique_ptr pageSearch (int pn, const char *str) { return nullptr; } virtual std::optional> pageHighlight (int pn, const ApvlvPoint &pa, const ApvlvPoint &pb) { return std::nullopt; } // path methods virtual std::optional pathContent (const std::string &path); virtual std::string pathMimeType (const std::string &path); virtual int pathPageNumber (const std::string &path); virtual bool print (int page = -1); protected: File () {} std::string mFilename; FileIndex mIndex; std::vector mPages; std::map srcPages; std::map srcMimeTypes; ApvlvCover mCover; Note mNote; private: std::optional pathContentHtml (int, double, int); std::optional pathContentPng (int, double, int); }; class FileFactory { public: static int registerClass (const std::string &name, const std::function &fun, std::initializer_list exts); static const std::map> & supportMimeTypes (); static std::vector supportFileExts (); static std::ostream &typeEngineDescription (std::ostream &os); using ExtClass = std::pair>; using ExtClassList = std::vector; static std::optional findMatchClass (const std::string &filename); static std::unique_ptr loadFile (const std::string &filename); private: static std::map> mSupportMimeTypes; static std::map mSupportClass; }; #define FILE_TYPE_DECLARATION(cls) \ private: \ static int class_id_of_##cls #define FILE_TYPE_DEFINITION(name, cls, ...) \ int cls::class_id_of_##cls = FileFactory::registerClass ( \ name, \ [] () -> File * \ { \ return new cls (); \ }, \ __VA_ARGS__) }; #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/ApvlvFileIndex.cc ================================================ /* * This file is part of the apvlv package * Copyright (C) <2008> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE File.cc * * Author: Alf */ #include #include #include #include #include #include "ApvlvFile.h" #include "ApvlvFileIndex.h" #include "ApvlvUtil.h" namespace apvlv { using namespace std; void FileIndex::loadDirectory (const string &path1) { auto exts = FileFactory::supportFileExts (); try { for (auto &entry : filesystem::directory_iterator ( path1, filesystem::directory_options::follow_directory_symlink)) { if (entry.is_directory ()) { auto index = FileIndex (entry.path ().filename ().string (), 0, entry.path ().string (), FileIndexType::DIR); index.loadDirectory (entry.path ().string ()); auto last = entry.last_write_time (); index.mtime = filesystemTimeToMSeconds (last); if (!index.mChildrenIndex.empty ()) { mChildrenIndex.emplace_back (index); } } else if (entry.file_size () > 0) { auto file_ext = filenameExtension (entry.path ().string ()); if (find (exts.cbegin (), exts.cend (), file_ext) != exts.cend ()) { auto index = FileIndex (entry.path ().filename ().string (), 0, entry.path ().string (), FileIndexType::FILE); index.size = static_cast (entry.file_size ()); auto last = entry.last_write_time (); index.mtime = filesystemTimeToMSeconds (last); mChildrenIndex.emplace_back (index); } } } } catch (filesystem::filesystem_error &err) { qWarning () << "file system error: " << err.what (); } } void FileIndex::moveChildChildren (const FileIndex &other_index) { Q_ASSERT (type == FileIndexType::FILE); Q_ASSERT (other_index.type == FileIndexType::FILE); mChildrenIndex = other_index.mChildrenIndex; } void FileIndex::removeChild (const FileIndex &child) { mChildrenIndex.remove (child); } FileIndex::FileIndex (const string &title, int page, const string &path, FileIndexType type) { this->title = title; this->page = page; this->path = path; this->type = type; if (const filesystem::path p (path); exists (p)) { auto note_path = Note::notePathOfPath (path); if (const filesystem::path np (note_path); exists (np)) { const auto note = make_unique (); note->load (note_path); this->score = note->score (); this->tags = note->tagString (); } } } FileIndex::~FileIndex () = default; } // Local Variables: // mode: c++ // End: ================================================ FILE: src/ApvlvFileIndex.h ================================================ /* * This file is part of the apvlv package * Copyright (C) <2008> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE File.h * * Author: Alf */ #ifndef _APVLV_FILE_INDEX_H_ #define _APVLV_FILE_INDEX_H_ #include #include #include #include "ApvlvSearch.h" namespace apvlv { enum class FileIndexType { PAGE, FILE, DIR }; class FileIndex { public: FileIndex () = default; FileIndex (const std::string &title, int page, const std::string &path, FileIndexType type); ~FileIndex (); friend bool operator== (const FileIndex &a, const FileIndex &b) { return a.title == b.title && a.page == b.page && a.path == b.path && a.anchor == b.anchor; } void loadDirectory (const std::string &path1); void moveChildChildren (const FileIndex &other_index); void removeChild (const FileIndex &child); /* public variables */ FileIndexType type{ FileIndexType::PAGE }; std::string title; int page{ 0 }; std::string path; std::string anchor; std::list mChildrenIndex; /* public file variables */ std::int64_t size{ 0 }; std::int64_t mtime{ 0 }; std::string tags; float score{ 0.0f }; }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/ApvlvFileWidget.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvFileWidget.cc * * Author: Alf */ #include "ApvlvFileWidget.h" #include "ApvlvParams.h" namespace apvlv { using namespace std; void FileWidget::scroll (int times, int w, int h) { // need impl in child } double FileWidget::scrollRate () { if (mValScrollBar == nullptr) return mScrollValue; double maxv = mValScrollBar->maximum () - mValScrollBar->minimum (); double val = mValScrollBar->value () / maxv; if (val > 1.0) { return 1.00; } else if (val > 0.0) { return val; } else { return 0.00; } } void FileWidget::scrollTo (double s, double y) { if (!mValScrollBar) return; auto maxv = mValScrollBar->maximum () - mValScrollBar->minimum (); auto val = static_cast (y * maxv); mValScrollBar->setValue (val); } void FileWidget::scrollUp (int times) { if (mValScrollBar == nullptr) return; auto const kj_pixels = ApvlvParams::instance ()->getIntOrDefault ( "kj_pixels", APVLV_KJ_PIXELS_DEFAULT); auto rate = kj_pixels * times; if (mValScrollBar->value () - rate >= mValScrollBar->minimum ()) { mValScrollBar->setValue (mValScrollBar->value () - rate); } else if (mValScrollBar->value () > mValScrollBar->minimum ()) { mValScrollBar->setValue (mValScrollBar->minimum ()); } else { auto params = ApvlvParams::instance (); if (params->getBoolOrDefault ("autoscrollpage")) { if (mPageNumber == 0) { if (params->getBoolOrDefault ("autoscrolldoc")) { showPage (mFile->sum () - 1, 1.0); } } else { showPage (mPageNumber - 1, 1.0); } } } } void FileWidget::scrollDown (int times) { if (!mValScrollBar) return; auto const kj_pixels = ApvlvParams::instance ()->getIntOrDefault ( "kj_pixels", APVLV_KJ_PIXELS_DEFAULT); auto rate = kj_pixels * times; if (mValScrollBar->value () + rate <= mValScrollBar->maximum ()) { mValScrollBar->setValue (mValScrollBar->value () + rate); } else if (mValScrollBar->value () < mValScrollBar->maximum ()) { mValScrollBar->setValue (mValScrollBar->maximum ()); } else { auto params = ApvlvParams::instance (); if (params->getBoolOrDefault ("autoscrollpage")) { if (mPageNumber == mFile->sum () - 1) { if (params->getBoolOrDefault ("autoscrolldoc")) { showPage (0, 0.0); } } else { showPage (mPageNumber + 1, 0.0); } } } } void FileWidget::scrollLeft (int times) { if (!mHalScrollBar) return; auto const hl_pixels = ApvlvParams::instance ()->getIntOrDefault ( "hl_pixels", APVLV_HL_PIXELS_DEFAULT); auto val = mHalScrollBar->value () - hl_pixels * times; if (val > mHalScrollBar->minimumWidth ()) { mHalScrollBar->setValue (val); } else { mHalScrollBar->setValue (mHalScrollBar->minimumWidth ()); } } void FileWidget::scrollRight (int times) { if (!mHalScrollBar) return; auto const hl_pixels = ApvlvParams::instance ()->getIntOrDefault ( "hl_pixels", APVLV_HL_PIXELS_DEFAULT); auto val = mHalScrollBar->value () + hl_pixels * times; if (val + mHalScrollBar->width () < mHalScrollBar->maximumWidth ()) { mHalScrollBar->setValue (val); } else { mHalScrollBar->setValue (mHalScrollBar->maximumWidth () - mHalScrollBar->width ()); } } } /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/ApvlvFileWidget.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvFileWidget.h * * Author: Alf */ #ifndef _APVLV_FILE_WIDGET_H_ #define _APVLV_FILE_WIDGET_H_ #include #include #include "ApvlvFile.h" namespace apvlv { const int APVLV_HL_PIXELS_DEFAULT = 40; const int APVLV_KJ_PIXELS_DEFAULT = 15; const double APVLV_ZOOMRATE_DEFAULT = 1.3f; const int INVALID_PAGENUM = -1; class File; class FileWidget : public QObject { Q_OBJECT public: FileWidget () = default; ~FileWidget () override = default; [[nodiscard]] virtual QWidget *widget () = 0; [[nodiscard]] virtual File * file () const { return mFile; } virtual void setFile (File *file) { mFile = file; } virtual int pageNumber () { return mPageNumber; } virtual std::string anchor () { return mAnchor; } virtual double zoomrate () { return mZoomrate; } virtual void showPage (int pn, double rate) { mPageNumber = pn; mScrollValue = rate; } virtual void showPage (int pn, const std::string &anchor) { mPageNumber = pn; mAnchor = anchor; } virtual void scroll (int times, int w, int h); virtual double scrollRate (); virtual void scrollTo (double s, double y); virtual void scrollUp (int times); virtual void scrollDown (int times); virtual void scrollLeft (int times); virtual void scrollRight (int times); virtual void setZoomrate (double zm) { mZoomrate = zm; } virtual void setRotate (int rot) { mRotate = rot; } virtual int rotate () { return mRotate; } void setAnchor (const std::string &anchor) { mAnchor = anchor; } virtual void setSearchSelect (int select) { mSearchSelect = select; } [[nodiscard]] virtual int searchSelect () const { return mSearchSelect; } virtual void setSearchStr (const std::string &str) { mSearchStr = str; } [[nodiscard]] virtual std::string searchStr () const { return mSearchStr; } virtual void setSearchResults (const WordListRectangle &wlr) { mSearchResults = wlr; } virtual const WordListRectangle & searchResults () { return mSearchResults; } virtual void setSelects (const std::vector &rect_list) { mSelects = rect_list; } virtual const std::vector & selects () { return mSelects; } protected: QScrollBar *mValScrollBar{ nullptr }; QScrollBar *mHalScrollBar{ nullptr }; File *mFile{ nullptr }; int mPageNumber{ INVALID_PAGENUM }; double mScrollValue{ 0.0f }; std::string mAnchor; double mZoomrate{ APVLV_ZOOMRATE_DEFAULT }; int mRotate{ 0 }; std::string mSearchStr; WordListRectangle mSearchResults; int mSearchSelect{ 0 }; std::vector mSelects; }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/ApvlvFrame.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvFrame.cc * * Author: Alf */ #include #include #include #include #include #include #include "ApvlvFileWidget.h" #include "ApvlvFrame.h" #include "ApvlvImageWidget.h" #include "ApvlvInfo.h" #include "ApvlvParams.h" #include "ApvlvView.h" #include "ApvlvWeb.h" #include "ApvlvWebViewWidget.h" namespace apvlv { using namespace std; using namespace Qt; using namespace CommandModeType; std::vector ApvlvFrame::ZoomLabel = { QT_TR_NOOP ("Default"), QT_TR_NOOP ("Fit Width"), QT_TR_NOOP ("Fit Height"), QT_TR_NOOP ("Custom"), }; ApvlvFrame::ApvlvFrame (ApvlvView *view) : mToolStatus (this) { mInuse = true; mView = view; mProCmd = 0; mZoomMode = ZoomMode::NORMAL; mSearchResults = nullptr; mSearchStr = ""; setLayout (&mVbox); mPaned.setHandleWidth (4); mDirectoryWidth = DEFAULT_CONTENT_WIDTH; auto f_width = ApvlvParams::instance ()->getIntOrDefault ("fix_width"); auto f_height = ApvlvParams::instance ()->getIntOrDefault ("fix_height"); if (f_width > 0 && f_height > 0) { mPaned.setFixedSize (f_width, f_height); mVbox.addLayout (&mHBoxLayout, 1); mHBoxLayout.addWidget (&mPaned, 0); } else { mVbox.addWidget (&mPaned, 1); } mDirectory.setFrame (this); QObject::connect (this, SIGNAL (indexGenerited (const FileIndex &)), &mDirectory, SLOT (setIndex (const FileIndex &))); // left is Directory mPaned.addWidget (&mDirectory); // Right is Text mPaned.addWidget (&mTextFrame); mTextFrame.setLayout (&mTextLayout); mTextLayout.addWidget (&mToolStatus, 0); auto guiopt = ApvlvParams::instance ()->getStringOrDefault ("guioptions"); if (guiopt.find ('S') == string::npos) { mToolStatus.hide (); } mVbox.addWidget (&mStatus, 0); if (guiopt.find ('s') == string::npos) { mStatus.hide (); } qDebug () << "ApvlvFrame: " << this << " be created"; } ApvlvFrame::~ApvlvFrame () { qDebug () << "ApvlvFrame: " << this << " be freed"; saveLastPosition (mFilestr); } void ApvlvFrame::inuse (bool use) { mInuse = use; } bool ApvlvFrame::inuse () { return mInuse; } bool ApvlvFrame::loadUri (const string &uri) { mFile = make_unique (); mFile->load (uri); setWidget (mFile->getDisplayType ()); refresh (0, 0.0); return true; } const char * ApvlvFrame::filename () { return mFilestr.empty () ? nullptr : mFilestr.c_str (); } bool ApvlvFrame::print (int ct) { if (!mFile) return false; return mFile->print (ct); } int ApvlvFrame::getSkip () { return mSkip; } void ApvlvFrame::setSkip (int ct) { mSkip = ct; } void ApvlvFrame::toggleDirectory () { auto show = !isShowDirectory (); toggleDirectory (show); } void ApvlvFrame::toggleDirectory (bool show) { if (show) { if (!mDirectory.isReady ()) { qWarning () << "file " << mFilestr << " has no directory"; show = false; } } auto sizes = mPaned.sizes (); if (show) { mDirIndex = {}; if (sizes[0] == 0) { auto psize = mPaned.size (); sizes = { mDirectoryWidth, psize.width () - mPaned.handleWidth () - DEFAULT_CONTENT_WIDTH }; mPaned.setSizes (sizes); } } else { if (sizes[0] != 0) mDirectoryWidth = sizes[0]; auto psize = mPaned.size (); sizes = { 0, psize.width () - mPaned.handleWidth () }; mPaned.setSizes (sizes); } } void ApvlvFrame::setActive (bool act) { mActive = act; auto wid = mWidget->widget (); if (act) { QTimer::singleShot (50, wid, SLOT (setFocus ())); } else { wid->clearFocus (); clearFocus (); } if (mActive && filename ()) { auto path = filesystem::path (filename ()); auto base = path.filename (); mView->setTitle (base.string ()); } mStatus.setActive (act); } void ApvlvFrame::setDirIndex (const string &path) { mDirIndex = { "", 0, path, FileIndexType::DIR }; mDirIndex.loadDirectory (path); emit indexGenerited (mDirIndex); toggleDirectory (true); } bool ApvlvFrame::toggledControlDirectory (bool is_right) { if (!isShowDirectory ()) { return false; } if (auto controlled = isControlledDirectory (); !controlled && !is_right) { mDirectory.setActive (true); return true; } else if (controlled && is_right) { mDirectory.setActive (false); return true; } return false; } bool ApvlvFrame::isShowDirectory () { auto sizes = mPaned.sizes (); return sizes[0] > 1; } bool ApvlvFrame::isControlledDirectory () { if (!isShowDirectory ()) return false; return mDirectory.isActive (); } ApvlvFrame * ApvlvFrame::findByWidget (QWidget *widget) { for (auto doc = widget; doc != nullptr; doc = doc->parentWidget ()) { if (doc->inherits ("apvlv::ApvlvFrame")) return dynamic_cast (doc); } return nullptr; } ApvlvStatus::ApvlvStatus () { setFrameShape (QFrame::NoFrame); setLayout (&mLayout); } void ApvlvStatus::setActive (bool act) { auto children = findChildren (); for (auto child : children) { if (child) { child->setEnabled (act); } } } void ApvlvStatus::showMessages (const vector &msgs) { auto children = findChildren (); vector newlabels; for (std::size_t ind = 0; ind < msgs.size (); ++ind) { if (children.size () > (qsizetype)ind) { auto label = children[ind]; label->setText (QString::fromLocal8Bit (msgs[ind])); } else { auto label = new QLabel (); label->setText (QString::fromLocal8Bit (msgs[ind])); newlabels.push_back (label); } } auto hbox = layout (); for (auto label : newlabels) { hbox->addWidget (label); } } ApvlvToolStatus::ApvlvToolStatus (ApvlvFrame *frame) : mFrame (frame) { auto paction = addAction (QIcon::fromTheme (QIcon::ThemeIcon::GoPrevious), tr ("Previous Page")); QObject::connect (paction, SIGNAL (triggered (bool)), mFrame, SLOT (previousPage ())); auto naction = addAction (QIcon::fromTheme (QIcon::ThemeIcon::GoNext), tr ("Next Page")); QObject::connect (naction, SIGNAL (triggered (bool)), mFrame, SLOT (nextPage ())); mPageValue.setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); QObject::connect (&mPageValue, SIGNAL (editingFinished ()), this, SLOT (gotoPage ())); addWidget (&mPageValue); addWidget (&mPageSum); addSeparator (); addWidget (&mScrollRate); addSeparator (); auto iaction = addAction (QIcon::fromTheme (QIcon::ThemeIcon::ZoomIn), tr ("Zoom In")); QObject::connect (iaction, SIGNAL (triggered (bool)), mFrame, SLOT (zoomIn ())); auto oaction = addAction (QIcon::fromTheme (QIcon::ThemeIcon::ZoomOut), tr ("Zoom Out")); QObject::connect (oaction, SIGNAL (triggered (bool)), mFrame, SLOT (zoomOut ())); addWidget (&mZoomType); for (auto const &str : ApvlvFrame::ZoomLabel) { mZoomType.addItem (ApvlvFrame::tr (str)); } QObject::connect (&mZoomType, SIGNAL (currentIndexChanged (int)), mFrame, SLOT (setZoomMode (int))); mZoomType.setLineEdit (&mZoomValue); addSeparator (); #ifdef APVLV_WITH_OCR addWidget (&mOcrParse); mOcrParse.setText (tr ("OCR Parse")); QObject::connect (&mOcrParse, SIGNAL (checkStateChanged (Qt::CheckState)), mFrame, SLOT (ocrParse ())); addAction (&mOcrCopy); mOcrCopy.setIcon (QIcon::fromTheme (QIcon::ThemeIcon::Scanner)); mOcrCopy.setText (tr ("OCR Copy")); QObject::connect (&mOcrCopy, SIGNAL (triggered (bool)), mFrame, SLOT (ocrCopy ())); #endif } void ApvlvToolStatus::updateValue (int pn, int totpn, double zm, double sr) { mPageValue.setText (QString::number (pn)); mPageSum.setText (QString::fromLocal8Bit ("/%1").arg (totpn)); if (mZoomType.currentIndex () == 3) { mZoomValue.setText ( QString::fromLocal8Bit ("%1%").arg (static_cast (zm * 100))); } mScrollRate.setText ( QString::fromLocal8Bit ("%1%").arg (static_cast (sr * 100))); #ifdef APVLV_WITH_OCR auto need_ocr = mFrame->mFile->pageIsOnlyImage (mFrame->pageNumber ()); mOcrParse.setEnabled (need_ocr); mOcrCopy.setEnabled (need_ocr); #endif } void ApvlvToolStatus::gotoPage () { auto text = mPageValue.text ().trimmed (); auto pn = text.toInt (); if (pn != mFrame->pageNumber ()) { mFrame->showPage (pn, 0.f); } } CmdReturn ApvlvFrame::subProcess ([[maybe_unused]] int ct, uint key) { uint procmd = mProCmd; mProCmd = 0; switch (procmd) { case 'm': markposition (char (key)); break; case '\'': jump (char (key)); break; case 'z': if (key == 'i') { zoomIn (); } else if (key == 'o') { zoomOut (); } else if (key == 'h') { setZoomMode (static_cast (ZoomMode::FITHEIGHT)); } else if (key == 'w') { setZoomMode (static_cast (ZoomMode::FITWIDTH)); } break; default: return CmdReturn::NO_MATCH; break; } return CmdReturn::MATCH; } void ApvlvFrame::previousPage () { previousPage (1); } void ApvlvFrame::nextPage () { nextPage (1); } void ApvlvFrame::setZoomMode (int mode) { if (mode < static_cast (ZoomMode::CUSTOM)) { switch (static_cast (mode)) { using enum ZoomMode; case NORMAL: setZoomString ("normal"); break; case FITWIDTH: setZoomString ("fitwidth"); break; case FITHEIGHT: setZoomString ("fitheight"); break; case CUSTOM: break; } } updateStatus (); } void ApvlvFrame::zoomIn () { auto zoomrate = mWidget->zoomrate (); setZoomrate (zoomrate * 1.1); updateStatus (); } void ApvlvFrame::zoomOut () { auto zoomrate = mWidget->zoomrate (); setZoomrate (zoomrate / 1.1); updateStatus (); } #ifdef APVLV_WITH_OCR void ApvlvFrame::ocrParse () { auto meta = mWidget->widget ()->metaObject (); qDebug () << "widget is " << meta->className (); if (!mWidget->widget ()->inherits ("apvlv::ApvlvImage")) return; auto image = dynamic_cast (mWidget->widget ()); auto state = mToolStatus.mOcrParse.checkState (); image->ocrDisplay (state == Qt::Checked); } void ApvlvFrame::ocrCopy () { auto meta = mWidget->widget ()->metaObject (); qDebug () << "widget is " << meta->className (); if (!mWidget->widget ()->inherits ("apvlv::ApvlvImage")) return; auto image = dynamic_cast (mWidget->widget ()); auto text = image->ocrGetText (); #ifdef QT_DEBUG QMessageBox::information (this, tr ("text in clipboard"), QString::fromLocal8Bit (text.get ())); #endif auto clipboard = QGuiApplication::clipboard (); clipboard->setText (text.get ()); } #endif void ApvlvFrame::wheelEvent (QWheelEvent *event) { auto angel = event->angleDelta (); if (angel.y () > 0) { mWidget->scrollUp (1); updateStatus (); } else { mWidget->scrollDown (1); updateStatus (); } } CmdReturn ApvlvFrame::process (int has, int ct, uint key) { emit focusIn (); if (mProCmd != 0) { return subProcess (ct, key); } if (!has) { ct = 1; } switch (key) { case Key_PageDown: case ctrlValue ('f'): nextPage (ct); break; case Key_PageUp: case ctrlValue ('b'): previousPage (ct); break; case ctrlValue ('d'): halfNextPage (ct); break; case ctrlValue ('u'): halfPreviousPage (ct); break; case ':': case '/': case '?': case 'F': if (isControlledDirectory ()) { mDirectory.focusFilter (); } else { mView->promptCommand (char (key)); return CmdReturn::NEED_MORE; } case 'H': mWidget->scrollTo (0.0, 0.0); break; case 'M': mWidget->scrollTo (0.0, 0.5); break; case 'L': mWidget->scrollTo (0.0, 1.0); break; case '0': mWidget->scrollLeft (INT_MAX); break; case '$': mWidget->scrollRight (INT_MAX); break; case ctrlValue ('p'): case Key_Up: case 'k': if (isControlledDirectory ()) { mDirectory.scrollUp (ct); } else { mWidget->scrollUp (ct); updateStatus (); } break; case ctrlValue ('n'): case ctrlValue ('j'): case Key_Down: case 'j': if (isControlledDirectory ()) { mDirectory.scrollDown (ct); } else { mWidget->scrollDown (ct); updateStatus (); } break; case Key_Backspace: case Key_Left: case ctrlValue ('h'): case 'h': if (isControlledDirectory ()) { mDirectory.scrollLeft (ct); } else { mWidget->scrollLeft (ct); updateStatus (); } break; case Key_Space: case Key_Right: case ctrlValue ('l'): case 'l': if (isControlledDirectory ()) { mDirectory.scrollRight (ct); } else { mWidget->scrollRight (ct); updateStatus (); } break; case Key_Return: directoryShowPage (mDirectory.currentItemFileIndex (), false); break; case 'R': reload (); break; case ctrlValue (']'): gotoLink (ct); break; case ctrlValue ('t'): returnLink (ct); break; case 't': if (isControlledDirectory ()) { mDirectory.tag (); } else { mView->newTab (HelpPdf); mView->open (); } break; case 'T': mView->newTab (HelpPdf); mView->openDir (); break; case 'o': mView->open (); break; case 'O': mView->openDir (); break; case 'r': rotate (ct); break; case 'G': markposition ('\''); if (!has) { showPage (mFile->sum () - 1, 0.0); } else { showPage (ct - 1, 0.0); } break; case 'm': case '\'': case 'z': mProCmd = key; return CmdReturn::NEED_MORE; break; case 'n': if (mSearchCmd == SEARCH) { markposition ('\''); search ("", false); } else if (mSearchCmd == BACKSEARCH) { markposition ('\''); search ("", true); } break; case 'N': if (mSearchCmd == SEARCH) { markposition ('\''); search ("", true); } else if (mSearchCmd == BACKSEARCH) { markposition ('\''); search ("", false); } break; case 's': setSkip (ct); break; case 'c': toggleDirectory (); break; default: return CmdReturn::NO_MATCH; break; } return CmdReturn::MATCH; } ApvlvFrame * ApvlvFrame::clone () { auto *ndoc = new ApvlvFrame (mView); ndoc->loadFile (mFilestr, false, false); ndoc->showPage (mWidget->pageNumber (), mWidget->scrollRate ()); return ndoc; } void ApvlvFrame::setZoomrate (double zm) { mZoomMode = ZoomMode::CUSTOM; mWidget->setZoomrate (zm); } void ApvlvFrame::setZoomString (const char *z) { auto zoomrate = mWidget->zoomrate (); if (z != nullptr) { string_view sv (z); if (sv == "normal") { mZoomMode = ZoomMode::NORMAL; zoomrate = 1.2; } else if (sv == "fitwidth") { mZoomMode = ZoomMode::FITWIDTH; } else if (sv == "fitheight") { mZoomMode = ZoomMode::FITHEIGHT; } else { double d = strtod (z, nullptr); if (d > 0) { mZoomMode = ZoomMode::CUSTOM; zoomrate = d; } } } if (mFile) { int pn = std::max (0, pageNumber () - 1); auto size = mFile->pageSizeF (pn, 0); if (size.width > 0 && size.height > 0) { auto wid = mWidget->widget (); if (mZoomMode == ZoomMode::FITWIDTH) { auto x_root = wid->width (); zoomrate = x_root / size.width; } else if (mZoomMode == ZoomMode::FITHEIGHT) { auto y_root = wid->height (); zoomrate = y_root / size.height; } } mWidget->setZoomrate (zoomrate); } } bool ApvlvFrame::saveLastPosition (const string &filename) { if (filename.empty () || HelpPdf == filename || ApvlvParams::instance ()->getBoolOrDefault ("noinfo", false)) { return false; } bool ret = ApvlvInfo::instance ()->updateFile ( mWidget->pageNumber (), mSkip, mWidget->scrollRate (), filename); return ret; } bool ApvlvFrame::loadLastPosition (const string &filename) { if (filename.empty () || HelpPdf == filename || ApvlvParams::instance ()->getBoolOrDefault ("noinfo")) { showPage (0, 0.0); return false; } bool ret = false; auto optfp = ApvlvInfo::instance ()->file (filename); if (optfp) { // correctly check showPage (optfp.value ()->page, 0.0); setSkip (optfp.value ()->skip); } else { showPage (0, 0.0); ApvlvInfo::instance ()->updateFile (0, 0.0, mWidget->zoomrate (), filename); } return ret; } bool ApvlvFrame::reload () { return loadFile (mFilestr, false, isShowDirectory ()); } int ApvlvFrame::pageNumber () { return mWidget ? mWidget->pageNumber () : 0; } bool ApvlvFrame::loadFile (const std::string &file, bool check, bool show_directory) { if (check && file == mFilestr) { return false; } saveLastPosition (mFilestr); mFile = FileFactory::loadFile (file); if (mFile) { emit indexGenerited (mFile->getIndex ()); mFilestr = file; if (mFile->sum () <= 1) { qDebug () << "sum () = " << mFile->sum (); } setWidget (mFile->getDisplayType ()); loadLastPosition (file); setActive (true); mSearchStr = ""; mSearchResults = nullptr; if (ApvlvParams::instance ()->getIntOrDefault ("autoreload") > 0) { mWatcher = make_unique (); QObject::connect (mWatcher.get (), SIGNAL (fileChanged ()), this, SLOT (changed_cb ())); auto systempath = filesystem::path (file); if (filesystem::is_symlink (systempath)) { auto realname = filesystem::read_symlink (systempath).string (); if (filesystem::is_regular_file (realname)) { mWatcher->addPath (QString::fromLocal8Bit (realname)); } } else { mWatcher->addPath (QString::fromLocal8Bit (file)); } } } if (show_directory && mFile != nullptr) { toggleDirectory (true); } else { toggleDirectory (false); } return mFile != nullptr; } void ApvlvFrame::markposition (const char s) { ApvlvDocPosition adp = { mWidget->pageNumber (), mWidget->scrollRate () }; mPositions[s] = adp; } void ApvlvFrame::jump (const char s) { auto it = mPositions.find (s); if (it != mPositions.end ()) { ApvlvDocPosition adp = it->second; markposition ('\''); showPage (adp.pagenum, adp.scrollrate); } } void ApvlvFrame::showPage (int pn, double s) { auto rp = mFile->pageNumberWrap (pn); if (rp < 0) return; mWidget->setAnchor (""); if (!mZoominit) { mZoominit = true; setZoomString (nullptr); } refresh (rp, s); } void ApvlvFrame::showPage (int pn, const std::string &anchor) { auto rp = mFile->pageNumberWrap (pn); if (rp < 0) return; if (!mZoominit) { mZoominit = true; setZoomString (nullptr); } mWidget->showPage (rp, anchor); updateStatus (); } void ApvlvFrame::nextPage (int times) { showPage (mWidget->pageNumber () + times, 0.0f); } void ApvlvFrame::previousPage (int times) { showPage (mWidget->pageNumber () - times, 0.0f); } void ApvlvFrame::refresh (int pn, double s) { if (mFile == nullptr) return; mWidget->showPage (pn, s); updateStatus (); } void ApvlvFrame::halfNextPage (int times) { double sr = mWidget->scrollRate (); int rtimes = times / 2; if (times % 2 != 0) { if (sr > 0.5) { sr = 0; rtimes += 1; } else { sr = 1; } } showPage (mWidget->pageNumber () + rtimes, sr); } void ApvlvFrame::halfPreviousPage (int times) { double sr = mWidget->scrollRate (); int rtimes = times / 2; if (times % 2 != 0) { if (sr < 0.5) { sr = 1; rtimes += 1; } else { sr = 0; } } showPage (mWidget->pageNumber () - rtimes, sr); } bool ApvlvFrame::needSearch (const std::string &str, bool reverse) { if (mFile == nullptr) return false; // search a different string if (!str.empty () && str != mSearchStr) { qDebug () << "different string."; mSearchStr = str; return true; } else if (mSearchResults == nullptr) { qDebug () << "no result."; return true; } // same string, but need to search next page else if ((!reverse && mWidget->searchSelect () == (int)mSearchResults->size () - 1) || (reverse && mWidget->searchSelect () == 0)) { qDebug () << "same, but need next string: s: " << reverse << ", sel: " << mWidget->searchSelect () << ", max: " << mSearchResults->size (); return true; } // same string, not need search, but has zoomed else { qDebug () << "same, not need next string. sel: " << mWidget->searchSelect () << ", max: " << mSearchResults->size (); if (!reverse) { setHighlightAndIndex (*mSearchResults, mWidget->searchSelect () + 1); } else { setHighlightAndIndex (*mSearchResults, mWidget->searchSelect () - 1); } return false; } return false; } bool ApvlvFrame::search (const char *str, bool reverse) { if (*str == '\0' && mSearchStr.empty ()) { return false; } if (*str) { mSearchCmd = (reverse ? CommandModeType::BACKSEARCH : CommandModeType::SEARCH); } if (!needSearch (str, reverse)) { return true; } mSearchResults = nullptr; unsetHighlight (); auto wrap = ApvlvParams::instance ()->getBoolOrDefault ("wrapscan"); auto i = mWidget->pageNumber (); auto sum = mFile->sum (); auto from = i; bool search = false; while (true) { if (*str != 0 || search) { mSearchResults = mFile->pageSearch ((i + sum) % sum, mSearchStr.c_str ()); if (mSearchResults != nullptr && !mSearchResults->empty ()) { if (i != mWidget->pageNumber ()) showPage (i, 0.0); auto results = *mSearchResults; auto sel = 0; if (reverse) sel = static_cast (results.size () - 1); setHighlightAndIndex (results, sel); return true; } } search = true; if (!reverse && i < (wrap ? (from + sum) : (sum - 1))) { i++; } else if (reverse && i > (wrap ? (from - sum) : 0)) { i--; } else { mView->errorMessage (string ("can't find word: "), mSearchStr); return false; } } } bool ApvlvFrame::totext (const char *file) { if (mFile == nullptr) return false; auto pn = mWidget->pageNumber (); string txt; auto size = mFile->pageSizeF (pn, 0); bool ret = mFile->pageText (pn, { 0, 0, size.width, size.height }, txt); if (ret) { fstream fs{ filename (), ios::out }; if (fs.is_open ()) { fs.write (txt.c_str (), txt.length ()); fs.close (); return true; } } return false; } bool ApvlvFrame::rotate (int ct) { // just hack if (ct == 1) ct = 90; if (ct % 90 != 0) { mView->errorMessage ("Not a 90 times value: ", ct); return false; } auto rotate = mWidget->rotate (); rotate += ct; while (rotate < 0) { rotate += 360; } while (rotate > 360) { rotate -= 360; } mWidget->setRotate (rotate); refresh (mWidget->pageNumber (), 0.0); return true; } void ApvlvFrame::gotoLink ([[maybe_unused]] int ct) { // need impl } void ApvlvFrame::returnLink ([[maybe_unused]] int ct) { // need impl } void ApvlvFrame::directoryShowPage (const FileIndex *index, bool force) { if (index == nullptr) return; if (index->type == FileIndexType::FILE) { loadFile (index->path, true, true); return; } auto file = mDirectory.currentFileFileIndex (); if (file && file->path != mFilestr) loadFile (file->path, true, true); if (index->type == FileIndexType::PAGE) { if (index->page != mWidget->pageNumber () || index->anchor != mWidget->anchor ()) showPage (index->page, index->anchor); } } void ApvlvFrame::setWidget (DISPLAY_TYPE type) { auto sizes = mPaned.sizes (); if (type == DISPLAY_TYPE::IMAGE) { mWidget = make_unique (); mWidget->setFile (mFile.get ()); #ifdef APVLV_WITH_OCR ocrParse (); #endif } else if (type == DISPLAY_TYPE::HTML) { mWidget = make_unique (); mWidget->setFile (mFile.get ()); } else { mWidget.reset (mFile->getWidget ()); } mTextLayout.addWidget (mWidget->widget (), 1); mPaned.setSizes (sizes); } void ApvlvFrame::unsetHighlight () { mWidget->setSearchStr (""); mWidget->setSearchSelect (0); mWidget->setSearchResults ({}); } void ApvlvFrame::setHighlightAndIndex (const WordListRectangle &poses, int sel) { if (!poses[sel].word.empty ()) { mWidget->setSearchStr (poses[sel].word); } mWidget->setSearchSelect (sel); mWidget->setSearchResults (poses); auto sr = mWidget->scrollRate (); if (!poses[sel].rect_list.empty ()) { auto rect = poses[sel].rect_list[0]; auto size = mFile->pageSizeF (mWidget->pageNumber (), mWidget->rotate ()); if (size.height > 0) { sr = rect.p2y / size.height; } } refresh (mWidget->pageNumber (), sr); } void ApvlvFrame::updateStatus () { if (filename ()) { vector labels; int pn = pageNumber () + 1; int totpn = mFile->sum (); auto zm = mWidget->zoomrate (); auto sr = mWidget->scrollRate (); string anchor = mWidget->anchor (); auto systempath = filesystem::path (filename ()); auto bn = systempath.filename (); labels.emplace_back (bn.string ()); auto ss = QString ("%1/%2").arg (pn).arg (totpn); labels.emplace_back (ss.toStdString ()); ss = QString ("%1%").arg (static_cast (zm * 100)); labels.emplace_back (ss.toStdString ()); ss = QString ("%1%").arg (static_cast (sr * 100)); labels.emplace_back (ss.toStdString ()); mStatus.showMessages (labels); mToolStatus.updateValue (pn, totpn, zm, sr); mDirectory.setCurrentIndex (mFilestr, mWidget->pageNumber (), mWidget->anchor ()); } } bool ApvlvFrame::isStatusHidden () { return mStatus.isHidden (); } void ApvlvFrame::statusShow () { mStatus.show (); } void ApvlvFrame::statusHide () { mStatus.hide (); } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/ApvlvFrame.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvFrame.h * * Author: Alf */ #ifndef _APVLV_FRAME_H_ #define _APVLV_FRAME_H_ #include #include #include #include #include #include #include #include #include #include "ApvlvCmds.h" #include "ApvlvDirectory.h" #include "ApvlvFile.h" #include "ApvlvFileWidget.h" #include "ApvlvWidget.h" namespace apvlv { struct ApvlvDocPosition { int pagenum; double scrollrate; }; using ApvlvDocPositionMap = std::map; struct ApvlvWord { CharRectangle pos; std::string word; }; struct ApvlvLine { CharRectangle pos; std::vector mWords; }; class ApvlvFrame; class ApvlvStatus : public QFrame { Q_OBJECT public: ApvlvStatus (); ~ApvlvStatus () override = default; void setActive (bool act); void showMessages (const std::vector &msgs); private: QHBoxLayout mLayout; }; class ApvlvToolStatus : public QToolBar { Q_OBJECT public: explicit ApvlvToolStatus (ApvlvFrame *frame); void updateValue (int pn, int totpn, double zm, double sr); private: ApvlvFrame *mFrame; ApvlvLineEdit mPageValue; QLabel mPageSum; QLabel mScrollRate; QComboBox mZoomType; ApvlvLineEdit mZoomValue; #ifdef APVLV_WITH_OCR QCheckBox mOcrParse; QAction mOcrCopy; #endif private slots: void gotoPage (); friend class ApvlvFrame; }; const int DEFAULT_CONTENT_WIDTH = 300; class FileWidget; class ApvlvView; class ApvlvFrame final : public QFrame { Q_OBJECT public: explicit ApvlvFrame (ApvlvView *view); ~ApvlvFrame () override; bool reload (); void inuse (bool use); bool inuse (); ApvlvFrame *clone (); void setDirIndex (const std::string &path); bool loadFile (const std::string &file, bool check, bool show_directory); bool loadUri (const std::string &uri); const char *filename (); int pageNumber (); void showPage (int pn, double s); void showPage (int pn, const std::string &anchor); void refresh (int pn, double s); void setActive (bool act); void updateStatus (); bool isStatusHidden (); void statusShow (); void statusHide (); bool print (int ct); bool totext (const char *name); bool rotate (int ct); void markposition (char s); void setZoomrate (double zm); void setZoomString (const char *z); void jump (char s); void nextPage (int times); void previousPage (int times); void halfNextPage (int times); void halfPreviousPage (int times); bool search (const char *str, bool reverse); void gotoLink (int ct); void returnLink (int ct); bool loadLastPosition (const std::string &filename); bool saveLastPosition (const std::string &filename); void directoryShowPage (const FileIndex *index, bool force); int getSkip (); void setSkip (int ct); void toggleDirectory (); void toggleDirectory (bool enabled); bool toggledControlDirectory (bool is_right); bool isShowDirectory (); bool isControlledDirectory (); void wheelEvent (QWheelEvent *event) override; CmdReturn process (int has, int times, uint keyval); ApvlvView *mView; void focusInEvent (QFocusEvent *event) override { emit focusIn (); } static ApvlvFrame *findByWidget (QWidget *widget); private: std::unique_ptr mFile; FileIndex mDirIndex{}; bool mInuse; std::unique_ptr mWatcher; std::string mFilestr; uint mProCmd; char mSearchCmd{}; std::unique_ptr mSearchResults; std::string mSearchStr; enum class ZoomMode { NORMAL, FITWIDTH, FITHEIGHT, CUSTOM }; ZoomMode mZoomMode; static std::vector ZoomLabel; bool mZoominit{}; int mSkip{}; ApvlvDocPositionMap mPositions; // the main menubar QVBoxLayout mVbox; // the main panel QSplitter mPaned; int mDirectoryWidth; QHBoxLayout mHBoxLayout; // directory panel Directory mDirectory; QFrame mTextFrame; QVBoxLayout mTextLayout; ApvlvToolStatus mToolStatus; std::unique_ptr mWidget; // status bar ApvlvStatus mStatus; // if active bool mActive{}; void setWidget (DISPLAY_TYPE type); void unsetHighlight (); void setHighlightAndIndex (const WordListRectangle &poses, int sel); bool needSearch (const std::string &str, bool reverse); CmdReturn subProcess (int ct, uint key); signals: void indexGenerited (const FileIndex &index); void focusIn (); private slots: void previousPage (); void nextPage (); void setZoomMode (int mode); void zoomIn (); void zoomOut (); #ifdef APVLV_WITH_OCR void ocrParse (); void ocrCopy (); #endif friend class ApvlvStats; friend class ApvlvToolStatus; }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/ApvlvImageWidget.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvImageWidget.cc * * Author: Alf */ #include #include #include #include #include #include #include "ApvlvImageWidget.h" #include namespace apvlv { using namespace std; #ifdef APVLV_WITH_OCR TextContainer::TextContainer (QWidget *parent) : Editor (parent) { setReadOnly (false); setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOff); } #endif ImageContainer::ImageContainer (QWidget *parent) : QLabel (parent) { mCopyAction.setText (tr ("Copy")); QObject::connect (&mCopyAction, SIGNAL (triggered (bool)), this, SLOT (copy ())); addAction (&mCopyAction); mUnderlineAction.setText (tr ("Underline")); QObject::connect (&mUnderlineAction, SIGNAL (triggered (bool)), this, SLOT (underline ())); addAction (&mUnderlineAction); mCommentAction.setText (tr ("Comment")); QObject::connect (&mCommentAction, SIGNAL (triggered (bool)), this, SLOT (comment ())); addAction (&mCommentAction); setContextMenuPolicy (Qt::ContextMenuPolicy::ActionsContextMenu); setMouseTracking (true); mHoverTimer = new QTimer (this); mHoverTimer->setSingleShot (true); QObject::connect (mHoverTimer, SIGNAL (timeout ()), this, SLOT (handleHover ())); } void ImageContainer::mousePressEvent (QMouseEvent *event) { if (event->button () != Qt::MouseButton::LeftButton) return; redraw (); mIsSelected = true; mPressPosition = event->position (); } void ImageContainer::mouseMoveEvent (QMouseEvent *event) { QToolTip::hideText (); mLastMousePos = event->pos (); mHoverTimer->start (1000); if (!mIsSelected) return; mMovePosition = event->position (); auto range = selectionRange (); auto rect_list = mImageWidget->file ()->pageHighlight ( mImageWidget->pageNumber (), range.first, range.second); if (!rect_list) return; mImageWidget->setSelects (rect_list.value ()); redraw (); } void ImageContainer::leaveEvent (QEvent *event) { mHoverTimer->stop (); QLabel::leaveEvent (event); } void ImageContainer::mouseReleaseEvent (QMouseEvent *event) { mImageWidget->setSelects ({}); mIsSelected = false; } bool ImageContainer::renderImage (int pn, double zm, int rot) { return mImageWidget->file ()->pageRenderToImage (pn, zm, rot, &mImage); } void ImageContainer::redraw () { QImage img = mImage; if (!mImageWidget->searchResults ().empty ()) { imageSelectSearch (&img, mImageWidget->zoomrate (), mImageWidget->searchSelect (), mImageWidget->searchResults ()); } else if (!mImageWidget->selects ().empty ()) { imageSelect (&img, mImageWidget->zoomrate (), mImageWidget->selects ()); } setPixmap (QPixmap::fromImage (img)); resize (img.size ()); } pair ImageContainer::selectionRange () { double left = mPressPosition.x () / mImageWidget->zoomrate (); double top = mPressPosition.y () / mImageWidget->zoomrate (); double right = mMovePosition.x () / mImageWidget->zoomrate (); double bottom = mMovePosition.y () / mImageWidget->zoomrate (); return { { left, top }, { right, bottom } }; } vector ImageContainer::selectionArea () { auto range = selectionRange (); auto rect_list = mImageWidget->file ()->pageHighlight ( mImageWidget->pageNumber (), range.first, range.second); if (!rect_list) return {}; return rect_list.value (); } string ImageContainer::selectionText () { auto range = selectionRange (); string text; mImageWidget->file ()->pageText ( mImageWidget->pageNumber (), { range.first.x, range.first.y, range.second.x, range.second.y }, text); return text; } void ImageContainer::displayComment (QPoint pos) { qDebug () << "display comment at: " << pos.x () << ":" << pos.y (); auto image_pos = pos; image_pos /= mImageWidget->zoomrate (); auto note = mImageWidget->file ()->getNote (); auto comments = note->getCommentsInPage (mImageWidget->pageNumber ()); for (auto const &comment : comments) { QRectF rect{ comment.begin.x, comment.begin.y, comment.end.x - comment.begin.x, comment.end.y - comment.begin.y }; if (rect.contains (image_pos)) { qDebug () << "isVisible: " << QToolTip::isVisible (); QToolTip::showText (QCursor::pos (), QString::fromStdString (comment.commentText), this); break; } } } void ImageContainer::copy () { qDebug () << "copy text"; auto text = selectionText (); auto clipboard = QGuiApplication::clipboard (); clipboard->setText (QString::fromLocal8Bit (text)); mImageWidget->setSelects ({}); redraw (); } void ImageContainer::underline () { qDebug () << "underline text"; auto page = mImageWidget->pageNumber (); auto range = selectionRange (); auto text = selectionText (); if (!text.empty ()) { auto note = mImageWidget->file ()->getNote (); Comment comment; comment.quoteText = text; comment.begin.set (page, &range.first); comment.end.set (page, &range.second); note->addComment (comment); } mImageWidget->setSelects ({}); redraw (); } void ImageContainer::comment () { qDebug () << "comment text"; do { auto text = selectionText (); if (text.empty ()) break; auto input_text = QInputDialog::getMultiLineText (this, tr ("Input"), tr ("Comment")); auto commentText = input_text.trimmed (); if (commentText.isEmpty ()) break; auto page = mImageWidget->pageNumber (); auto range = selectionRange (); auto note = mImageWidget->file ()->getNote (); Comment comment; comment.quoteText = text, comment.commentText = commentText.toStdString (); comment.begin.set (page, &range.first); comment.end.set (page, &range.second); note->addComment (comment); } while (false); mImageWidget->setSelects ({}); redraw (); } void ImageContainer::handleHover () { auto pos = mapFromGlobal (QCursor::pos ()); if (rect ().contains (pos) && pos == mLastMousePos) { qDebug () << "hovered"; displayComment (pos); } } ApvlvImage::ApvlvImage () { setAlignment (Qt::AlignCenter); setHorizontalScrollBarPolicy (Qt::ScrollBarPolicy::ScrollBarAsNeeded); setVerticalScrollBarPolicy (Qt::ScrollBarPolicy::ScrollBarAsNeeded); setWidget (&mImageContainer); } ApvlvImage::~ApvlvImage () { qDebug () << "ApvlvImage: " << this << " be freed"; } #ifdef APVLV_WITH_OCR void ApvlvImage::ocrDisplay (bool is_ocr) { if (is_ocr) { auto image = mImageContainer.pixmap (); auto text = mOCR.getTextFromPixmap (image); mTextContainer.setText (text.get ()); if (widget () != &mTextContainer) { takeWidget (); setWidget (&mTextContainer); } } else { if (widget () != &mImageContainer) { takeWidget (); setWidget (&mImageContainer); } } } std::unique_ptr ApvlvImage::ocrGetText () { auto image = mImageContainer.pixmap (); auto text = mOCR.getTextFromPixmap (image); return text; } #endif void ImageWidget::showPage (int p, double s) { if (p != mPageNumber) { if (!mImage.mImageContainer.renderImage (p, mZoomrate, mRotate)) return; } mImage.mImageContainer.redraw (); #ifdef APVLV_WITH_OCR mImage.mTextContainer.resize (mImage.mImageContainer.size ()); if (mImage.widget () == &mImage.mTextContainer) { mImage.mTextContainer.setZoomrate (mZoomrate); mImage.ocrDisplay (true); } #endif scrollTo (0.0, s); mPageNumber = p; } void ImageWidget::showPage (int p, const string &anchor) { showPage (p, 0.0f); mAnchor = anchor; } void ImageWidget::setSearchResults (const WordListRectangle &wlr) { mSearchResults = wlr; mImage.mImageContainer.redraw (); } void ImageWidget::setZoomrate (double zm) { if (mPageNumber != INVALID_PAGENUM) { if (mImage.mImageContainer.renderImage (mPageNumber, zm, mRotate)) { mImage.mImageContainer.redraw (); mZoomrate = zm; } #ifdef APVLV_WITH_OCR mImage.mTextContainer.setZoomrate (zm); #endif } else { mZoomrate = zm; } } void ImageWidget::setRotate (int rotate) { if (mPageNumber != INVALID_PAGENUM) { if (mImage.mImageContainer.renderImage (mPageNumber, mZoomrate, rotate)) { mImage.mImageContainer.redraw (); mRotate = rotate; } } else { mRotate = rotate; } } bool imageSelect (QImage *pix, double zm, const vector &rect_list) { for (auto const &rect : rect_list) { auto p1xz = static_cast (rect.p1x * zm); auto p2xz = static_cast (rect.p2x * zm); auto p1yz = static_cast (rect.p1y * zm); auto p2yz = static_cast (rect.p2y * zm); for (auto w = p1xz; w < p2xz; ++w) { for (auto h = p1yz; h < p2yz; ++h) { QColor c = pix->pixelColor (w, h); c.setRgb (255 - c.red (), 255 - c.red (), 255 - c.red ()); pix->setPixelColor (w, h, c); } } } return true; } bool imageUnderline (QImage *pix, double zm, const vector &rect_list) { for (auto const &rect : rect_list) { auto p1xz = static_cast (rect.p1x * zm); auto p2xz = static_cast (rect.p2x * zm); auto p1yz = static_cast (rect.p1y * zm); auto p2yz = static_cast (rect.p2y * zm); for (auto w = p1xz; w < p2xz; ++w) { for (auto h = p1yz; h < p2yz; ++h) { QColor c = pix->pixelColor (w, h); c.setRgb (255 - c.red (), 255 - c.red (), 255 - c.red ()); pix->setPixelColor (w, h, c); } } } return true; } bool imageSelectSearch (QImage *pix, double zm, int select, const WordListRectangle &wordlist) { for (auto itr = wordlist.begin (); itr != wordlist.end (); ++itr) { auto rectangles = *itr; for (auto const &pos : rectangles.rect_list) { auto p1xz = static_cast (pos.p1x * zm); auto p2xz = static_cast (pos.p2x * zm); auto p1yz = static_cast (pos.p2y * zm); auto p2yz = static_cast (pos.p1y * zm); if (pix->format () == QImage::Format_ARGB32) { imageArgb32ToRgb32 (*pix, p1xz, p1yz, p2xz, p2yz); } if (std::distance (wordlist.begin (), itr) == select) { for (int w = p1xz; w < p2xz; ++w) { for (int h = p1yz; h < p2yz; ++h) { QColor c = pix->pixelColor (w, h); c.setRgb (255 - c.red (), 255 - c.red (), 255 - c.red ()); pix->setPixelColor (w, h, c); } } } else { for (int w = p1xz; w < p2xz; ++w) { for (int h = p1yz; h < p2yz; ++h) { QColor c = pix->pixelColor (w, h); c.setRgb (255 - c.red () / 2, 255 - c.red () / 2, 255 - c.red () / 2); pix->setPixelColor (w, h, c); } } } } } return true; } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/ApvlvImageWidget.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvImageWidget.h * * Author: Alf */ #ifndef _APVLV_IMAGEWIDGET_H_ #define _APVLV_IMAGEWIDGET_H_ #include #include #include #include #include "ApvlvFileWidget.h" #include "ApvlvUtil.h" #ifdef APVLV_WITH_OCR #include "ApvlvEditor.h" #include "ApvlvOCR.h" #endif namespace apvlv { #ifdef APVLV_WITH_OCR class TextContainer : public Editor { Q_OBJECT public: explicit TextContainer (QWidget *parent = nullptr); ~TextContainer () override = default; }; #endif class ImageWidget; class ImageContainer : public QLabel { Q_OBJECT public: explicit ImageContainer (QWidget *parent = nullptr); void mousePressEvent (QMouseEvent *event) override; void mouseReleaseEvent (QMouseEvent *event) override; void mouseMoveEvent (QMouseEvent *event) override; void leaveEvent (QEvent *event) override; virtual bool renderImage (int pn, double zm, int rot); virtual void redraw (); void setImageWidget (ImageWidget *image_widget) { mImageWidget = image_widget; } private: bool mIsSelected{ false }; QPointF mPressPosition; QPointF mMovePosition; QTimer *mHoverTimer; QPoint mLastMousePos; ImageWidget *mImageWidget{ nullptr }; friend class ImageWidget; QImage mImage; QAction mCopyAction; QAction mUnderlineAction; QAction mCommentAction; std::pair selectionRange (); std::vector selectionArea (); std::string selectionText (); void displayComment (QPoint pos); private slots: void copy (); void underline (); void comment (); void handleHover (); }; class ApvlvImage : public QScrollArea { Q_OBJECT public: ApvlvImage (); ~ApvlvImage () override; #ifdef APVLV_WITH_OCR void ocrDisplay (bool replace); std::unique_ptr ocrGetText (); #endif private: ImageContainer mImageContainer; #ifdef APVLV_WITH_OCR TextContainer mTextContainer; OCR mOCR; #endif friend class ImageWidget; }; class ImageWidget : public FileWidget { public: ImageWidget () { mImage.mImageContainer.setImageWidget (this); mHalScrollBar = mImage.horizontalScrollBar (); mValScrollBar = mImage.verticalScrollBar (); } [[nodiscard]] QWidget * widget () override { return &mImage; } void showPage (int pn, double s) override; void showPage (int pn, const std::string &anchor) override; void setSearchResults (const WordListRectangle &wlr) override; void setZoomrate (double zm) override; void setRotate (int rotate) override; private: ApvlvImage mImage{}; }; bool imageSelectSearch (QImage *pix, double zm, int select, const WordListRectangle &poses); bool imageSelect (QImage *pix, double zm, const std::vector &poses); bool imageUnderline (QImage *pix, double zm, const std::vector &poses); } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/ApvlvInfo.cc ================================================ /* * This file is part of the apvlv package * Copyright (C) <2008> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvInfo.cc * * Author: Alf */ #include #include #include #include #include #include #include "ApvlvInfo.h" #include "ApvlvParams.h" namespace apvlv { using namespace std; void ApvlvInfo::loadFile (std::string_view file) { mFileName = file; ifstream is (mFileName, ios::in); if (is.is_open ()) { string line; while (getline (is, line)) { auto p = line.c_str (); if (*p != '\'' /* the ' */ || !isdigit (*(p + 1))) /* the digit */ { continue; } addPosition (p); } is.close (); } } bool ApvlvInfo::update () { ofstream os (mFileName, ios::out); if (!os.is_open ()) { return false; } int i = 0; for (const auto &infofile : mInfoFiles) { os << "'" << i++ << "\t"; os << infofile.page << ':' << infofile.skip << "\t"; os << infofile.rate << "\t"; os << infofile.file << endl; } os.close (); return true; } std::optional ApvlvInfo::lastFile () { if (mInfoFiles.empty ()) return nullopt; else return &*(mInfoFiles.rbegin ()); } optional ApvlvInfo::file (const string &filename) { auto itr = std::ranges::find_if (std::views::reverse (mInfoFiles), [filename] (auto const &infofile) { return infofile.file == filename; }); if (itr != mInfoFiles.rend ()) { return &(*itr); } return nullopt; } bool ApvlvInfo::updateFile (int page, int skip, double rate, const string &filename) { InfoFile infofile{ page, skip, rate, filename }; auto optinfofile = file (filename); if (optinfofile) { *optinfofile.value () = infofile; } else { mInfoFiles.push_back (infofile); if (mInfoFiles.size () > mMaxInfo) mInfoFiles.pop_front (); } return update (); } ApvlvInfo::ApvlvInfo () { mMaxInfo = ApvlvParams::instance ()->getIntOrDefault ("max_info", DEFAULT_MAX_INFO); } bool ApvlvInfo::addPosition (const char *str) { const char *p; const char *s; p = strchr (str + 2, '\t'); /* Skip the ' and the digit */ if (p == nullptr) { return false; } while (*p != '\0' && !isdigit (*p)) { p++; } int page = int (strtol (p, nullptr, 10)); int skip; s = strchr (p, ':'); for (; s && p < s; ++p) { if (!isdigit (*p)) { break; } } if (p == s) { ++p; skip = int (strtol (p, nullptr, 10)); } else { skip = 0; } p = strchr (p, '\t'); if (p == nullptr) { return false; } while (*p != '\0' && !isdigit (*p)) { p++; } double rate = strtod (p, nullptr); p = strchr (p, '\t'); if (p == nullptr) { return false; } while (*p != '\0' && isspace (*p)) { p++; } if (*p == '\0') { return false; } auto fp = InfoFile{ page, skip, rate, p }; mInfoFiles.emplace_back (fp); return true; } }; // Local Variables: // mode: c++ // End: ================================================ FILE: src/ApvlvInfo.h ================================================ /* * This file is part of the apvlv package * Copyright (C) <2008> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvInfo.h * * Author: Alf */ #ifndef _APVLV_INFO_H_ #define _APVLV_INFO_H_ #include #include #include namespace apvlv { struct InfoFile { int page; int skip; double rate; std::string file; }; const int DEFAULT_MAX_INFO = 100; class ApvlvInfo final { public: ApvlvInfo (const ApvlvInfo &) = delete; ApvlvInfo &operator= (const ApvlvInfo &) = delete; void loadFile (std::string_view file); bool update (); std::optional lastFile (); std::optional file (const std::string &filename); bool updateFile (int page, int skip, double rate, const std::string &filename); static ApvlvInfo * instance () { static ApvlvInfo inst; return &inst; } private: ApvlvInfo (); ~ApvlvInfo () = default; std::string mFileName{}; std::deque mInfoFiles{}; std::deque::size_type mMaxInfo{ DEFAULT_MAX_INFO }; bool addPosition (const char *str); }; }; #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/ApvlvLab.cc ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvLab.cc * * Author: Alf */ #include "ApvlvLab.h" #include "ApvlvUtil.h" #include "ApvlvWebViewWidget.h" namespace apvlv { using namespace std; const string stylesheet_content = ".block_c {\n" " display: block;\n" " font-size: 2.5em;\n" " font-weight: normal;\n" " line-height: 33.6pt;\n" " text-align: center;\n" " text-indent: 0;\n" " margin: 17pt 0;\n" " padding: 0;\n" "}\n" ".block_ {\n" " display: block;\n" " font-size: 1.5em;\n" " font-weight: normal;\n" " line-height: 33.6pt;\n" " text-align: justify;\n" " text-indent: 0;\n" " margin: 17pt 0;\n" " padding: 0;\n" "}\n" ".block_1 {\n" " display: block;\n" " line-height: 1.2;\n" " text-align: justify;\n" " margin: 0 0 7pt;\n" " padding: 0;\n" "}\n"; const string title_template = "\n" "\n" " \n" " \n" " \n" " \n" " \n" " \n" "
\n" "
\n" "
\n" "
\n" " %s\n" " \n" "\n"; const string section_template = "\n" "\n" " \n" " \n" " \n" " \n" " \n" " \n" " %s\n" " \n" "\n"; bool ApvlvLab::load (const string &filename) { return false; } ApvlvLab::~ApvlvLab () = default; bool ApvlvLab::pageRenderToWebView (int pn, double zm, int rot, WebView *webview) { webview->setZoomFactor (zm); QUrl url = QString ("apvlv:///") + QString::number (pn); webview->load (url); return true; } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/ApvlvLab.h ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvLab.h * * Author: Alf */ #ifndef _APVLV_LAB_H_ #define _APVLV_LAB_H_ #include "ApvlvFile.h" #include namespace apvlv { class ApvlvLab : public File { public: bool load (const std::string &filename) override; ~ApvlvLab () override; bool pageRenderToWebView (int pn, double zm, int rot, WebView *webview) override; private: }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/ApvlvLog.cc ================================================ /* * This file is part of the apvlv package * Copyright (C) <2024> Alf * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvLog.cc * * Author: Alf */ #include #include #include #include #include #include "ApvlvLog.h" namespace apvlv { void ApvlvLog::setLogFile (const std::string &path) { using FileFlag = QIODevice::OpenModeFlag; if (!path.empty ()) { mFile.setFileName (QString::fromLocal8Bit (path)); if (mFile.open (FileFlag::Text | FileFlag::WriteOnly | FileFlag::Append) == false) { std::cerr << "Open log file: " << path << "error: " << mFile.errorString ().toStdString () << std::endl; return; } mTextStream.setDevice (&mFile); } QLoggingCategory::setFilterRules ("qt.*=false\n" "default.debug=true\n" "default.*=true"); qInstallMessageHandler (ApvlvLog::logMessage); } void ApvlvLog::writeMessage (const QString &msg) { std::lock_guard lock (mMutex); #ifdef _DEBUG std::cout << msg.toStdString () << std::endl; #endif auto endstr = "\n"; #ifdef WIN32 endstr = "\r\n"; #endif if (mTextStream.device () && mTextStream.device ()->isOpen ()) { mTextStream << msg << endstr; } } ApvlvLog::~ApvlvLog () { if (mFile.isOpen ()) { mFile.close (); } } ApvlvLog * ApvlvLog::instance () { static ApvlvLog log; return &log; } void ApvlvLog::logMessage (QtMsgType type, const QMessageLogContext &context, const QString &msg) { auto now = QTime::currentTime (); auto nowstr = now.toString ("hh:mm:ss.zzz"); QString log = nowstr + " "; if (context.file) { auto filename = QFileInfo (context.file).fileName ().toStdString (); log += QString::asprintf ("%s:%d ", filename.c_str (), context.line); log += QString::asprintf ("%s ", context.function); } log += msg; ApvlvLog::instance ()->writeMessage (log); } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/ApvlvLog.h ================================================ /* * This file is part of the apvlv package * Copyright (C) <2024> Alf * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvLog.h * * Author: Alf */ #ifndef _APVLV_LOG_H_ #define _APVLV_LOG_H_ #include #include #include #include #include namespace apvlv { class ApvlvLog final { public: ApvlvLog (const ApvlvLog &) = delete; const ApvlvLog &operator= (const ApvlvLog &) = delete; void setLogFile (const std::string &path); ~ApvlvLog (); static ApvlvLog *instance (); static void logMessage (QtMsgType type, const QMessageLogContext &context, const QString &msg); private: ApvlvLog () = default; void writeMessage (const QString &log); QFile mFile; QTextStream mTextStream; std::mutex mMutex; }; }; #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/ApvlvMarkdown.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvMarkdown.cc * * Author: Alf */ #include #include #include #include "ApvlvMarkdown.h" #include namespace apvlv { using namespace std; MarkdownNode::MarkdownNode (const MarkdownNode &other) { node_type = other.node_type; list_type = other.list_type; heading_level = other.heading_level; literal = other.literal; title = other.title; url = other.url; for (auto &child : other.children) { auto nchild = new MarkdownNode (*child); children.push_back (unique_ptr (nchild)); } } MarkdownNode::MarkdownNode (MarkdownNode &&other) noexcept { node_type = other.node_type; list_type = other.list_type; heading_level = other.heading_level; literal.swap (other.literal); title.swap (other.title); url.swap (other.url); children.swap (other.children); } MarkdownNode & MarkdownNode::operator= (const MarkdownNode &other) { node_type = other.node_type; list_type = other.list_type; heading_level = other.heading_level; literal = other.literal; title = other.title; url = other.url; for (const auto &child : other.children) { auto nchild = new MarkdownNode (*child); children.push_back (unique_ptr (nchild)); } return *this; } MarkdownNode & MarkdownNode::operator= (MarkdownNode &&other) noexcept { node_type = other.node_type; list_type = other.list_type; heading_level = other.heading_level; literal.swap (other.literal); title.swap (other.title); url.swap (other.url); children.swap (other.children); return *this; } int MarkdownNode::childrenCount () { return static_cast (children.size ()); } MarkdownNode * MarkdownNode::childAt (int index) { return children[index].get (); } void MarkdownNode::appendChildPtr (std::unique_ptr ptr) { children.push_back (std::move (ptr)); } void MarkdownNode::appendChild (MarkdownNode *n) { children.push_back (unique_ptr (n)); } void MarkdownNode::removeChild (MarkdownNode *n) { auto iter = std::find_if (children.begin (), children.end (), [n] (const auto &i) { return i.get () == n; }); if (iter != children.end ()) { children.erase (iter); } } std::vector MarkdownNode::getListTexts () { std::vector texts; for (const auto &i : children) { auto p = i->children.front ().get (); auto t = p->children.front ().get (); texts.push_back (t->literal); } return texts; } void MarkdownNode::setListTexts (cmark_list_type _type, const std::vector &texts) { children.clear (); node_type = CMARK_NODE_LIST; list_type = _type; for (const auto &text : texts) { auto i = create (this, CMARK_NODE_ITEM); auto p = create (i, CMARK_NODE_PARAGRAPH); auto t = create (p, CMARK_NODE_TEXT); t->literal = text; } } void MarkdownNode::setNoListTexts (const std::vector &texts) { setListTexts (CMARK_NO_LIST, texts); } void MarkdownNode::setBulletListTexts (const std::vector &texts) { setListTexts (CMARK_BULLET_LIST, texts); } void MarkdownNode::setOrderedListTexts (const std::vector &texts) { setListTexts (CMARK_ORDERED_LIST, texts); } std::pair MarkdownNode::headText () { if (node_type != CMARK_NODE_HEADING || childrenCount () < 1) { throw std::invalid_argument ("MarkdownNode::headText"); } auto n = childAt (0); std::pair res; res.first = n->heading_level; res.second = n->literal; return res; } void MarkdownNode::appendHeadText (int _level, const std::string &_text) { auto h = create (this, CMARK_NODE_HEADING); h->heading_level = _level; auto ht = create (h, CMARK_NODE_TEXT); ht->literal = _text; } void MarkdownNode::appendHeadAndList (int _level, cmark_list_type _type, const HeadAndList &headAndList) { appendHeadText (_level, headAndList.first); auto list = create (this, CMARK_NODE_LIST); list->setListTexts (_type, headAndList.second); } void MarkdownNode::appendHeadAndNoList (int _level, const HeadAndList &headAndList) { appendHeadAndList (_level, CMARK_NO_LIST, headAndList); } void MarkdownNode::appendHeadAndBulletList (int _level, const HeadAndList &headAndList) { appendHeadAndList (_level, CMARK_BULLET_LIST, headAndList); } void MarkdownNode::appendHeadAndOrderedList (int _level, const HeadAndList &headAndList) { appendHeadAndList (_level, CMARK_ORDERED_LIST, headAndList); } std::unique_ptr MarkdownNode::fromCmarkNode (cmark_node *node) { auto mn = make_unique (cmark_node_get_type (node)); switch (mn->node_type) { case CMARK_NODE_HEADING: mn->heading_level = cmark_node_get_heading_level (node); break; case CMARK_NODE_LIST: mn->list_type = cmark_node_get_list_type (node); break; case CMARK_NODE_IMAGE: case CMARK_NODE_LINK: mn->title = cmark_node_get_title (node); mn->url = cmark_node_get_url (node); break; default: auto l = cmark_node_get_literal (node); if (l) mn->literal = l; } for (auto n = cmark_node_first_child (node); n != nullptr; n = cmark_node_next (n)) { auto cmn = MarkdownNode::fromCmarkNode (n); mn->children.emplace_back (std::move (cmn)); } return mn; } MarkdownNode * MarkdownNode::create (MarkdownNode *parent, cmark_node_type _type, string_view literal) { Q_ASSERT (parent != nullptr); auto mn = make_unique (_type); mn->literal = literal; auto node = mn.get (); parent->appendChildPtr (std::move (mn)); return node; } cmark_node * MarkdownNode::toCmarkNode () { auto doc = cmark_node_new (node_type); switch (node_type) { case CMARK_NODE_HEADING: cmark_node_set_heading_level (doc, heading_level); break; case CMARK_NODE_LIST: cmark_node_set_list_type (doc, list_type); break; case CMARK_NODE_IMAGE: case CMARK_NODE_LINK: cmark_node_set_title (doc, title.c_str ()); cmark_node_set_url (doc, url.c_str ()); break; default: break; } cmark_node_set_literal (doc, literal.c_str ()); for (auto const &child : children) { auto n = child.get (); auto d = n->toCmarkNode (); cmark_node_append_child (doc, d); } return doc; } Markdown::Markdown () : mRoot{ make_unique (CMARK_NODE_DOCUMENT) } { mRoot->node_type = CMARK_NODE_DOCUMENT; } Markdown::Markdown (const Markdown &other) { if (this != &other) { mRoot = make_unique (MarkdownNode (*other.mRoot)); } } Markdown::Markdown (Markdown &&other) noexcept { if (this != &other) { mRoot.swap (other.mRoot); } } Markdown & Markdown::operator= (const Markdown &other) { if (this != &other) { mRoot = make_unique (MarkdownNode (*other.mRoot)); } return *this; } Markdown & Markdown::operator= (Markdown &&other) noexcept { if (this != &other) { mRoot.swap (other.mRoot); } return *this; } bool Markdown::loadFromFile (const std::string &filename) { ifstream ifs (filename, ifstream::binary); if (!ifs.is_open ()) { return false; } return loadFromStream (ifs); } bool Markdown::loadFromStream (std::istream &is) { auto parser = cmark_parser_new (CMARK_OPT_DEFAULT); array buffer{}; while (is.good ()) { is.read (buffer.data (), buffer.size ()); auto got = is.gcount (); if (got == 0) { break; } cmark_parser_feed (parser, buffer.data (), got); } auto doc = cmark_parser_finish (parser); cmark_parser_free (parser); if (doc) { mRoot = MarkdownNode::fromCmarkNode (doc); cmark_node_free (doc); return true; } else { return false; } } bool Markdown::saveToFile (const std::string &filename) { ofstream ofs (filename, ofstream::binary); if (!ofs.is_open ()) { return false; } return saveToStream (ofs); } bool Markdown::saveToStream (std::ostream &os) { auto doc = mRoot->toCmarkNode (); auto text = cmark_render_commonmark (doc, CMARK_OPT_DEFAULT, 78); os << text; free (text); cmark_node_free (doc); return false; } MarkdownNode * Markdown::root () const { return mRoot.get (); } } ================================================ FILE: src/ApvlvMarkdown.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvEditor.h * * Author: Alf */ #ifndef _APVLV_MARKDOWN_H_ #define _APVLV_MARKDOWN_H_ #include #include #include #include namespace apvlv { struct MarkdownNode { cmark_node_type node_type{ CMARK_NODE_TEXT }; cmark_list_type list_type{ CMARK_NO_LIST }; int heading_level{ 1 }; std::string literal; std::string title; std::string url; std::vector> children; explicit MarkdownNode (cmark_node_type _type) : node_type (_type) {}; ~MarkdownNode () = default; MarkdownNode (const MarkdownNode &other); MarkdownNode (MarkdownNode &&other) noexcept; MarkdownNode &operator= (const MarkdownNode &other); MarkdownNode &operator= (MarkdownNode &&other) noexcept; int childrenCount (); MarkdownNode *childAt (int index); void appendChildPtr (std::unique_ptr ptr); void appendChild (MarkdownNode *n); void removeChild (MarkdownNode *n); std::vector getListTexts (); void setListTexts (cmark_list_type _type, const std::vector &texts); void setNoListTexts (const std::vector &texts); void setBulletListTexts (const std::vector &texts); void setOrderedListTexts (const std::vector &texts); using HeadAndList = std::pair>; std::pair headText (); void appendHeadText (int _level, const std::string &_text); void appendHeadAndList (int _level, cmark_list_type _type, const HeadAndList &headAndList); void appendHeadAndNoList (int _level, const HeadAndList &headAndList); void appendHeadAndBulletList (int _level, const HeadAndList &headAndList); void appendHeadAndOrderedList (int _level, const HeadAndList &headAndList); static std::unique_ptr fromCmarkNode (cmark_node *node); static MarkdownNode *create (MarkdownNode *parent, cmark_node_type _type, std::string_view literal = ""); cmark_node *toCmarkNode (); }; class Markdown { public: Markdown (); ~Markdown () = default; Markdown (const Markdown &other); Markdown (Markdown &&other) noexcept; Markdown &operator= (const Markdown &other); Markdown &operator= (Markdown &&other) noexcept; bool loadFromFile (const std::string &filename); bool loadFromStream (std::istream &is); bool saveToFile (const std::string &filename); bool saveToStream (std::ostream &os); [[nodiscard]] MarkdownNode *root () const; static std::unique_ptr create () { return std::make_unique (); } private: std::unique_ptr mRoot; }; } #endif ================================================ FILE: src/ApvlvNote.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvNote.cc * * Author: Alf */ #include #include #include #include #include "ApvlvFile.h" #include "ApvlvMarkdown.h" #include "ApvlvNote.h" #include "ApvlvUtil.h" namespace apvlv { using namespace std; void Location::set (int page1, const ApvlvPoint *point1, int offset1, const std::string &path1, const std::string &anchor1) { page = page1; if (point1) { x = point1->x; y = point1->y; } offset = offset1; path = path1; anchor = anchor1; } void Location::fromMarkdownNode (MarkdownNode *node) { stringstream ss{ node->literal }; ss >> page >> x >> y >> offset; if (!ss.eof ()) { auto str = ss.str (); auto pos = str.find ("[["); auto end = str.find ("]]"); path = str.substr (pos + 2, end - pos - 2); anchor = str.substr (end + 2); } } void Location::toMarkdownNode (MarkdownNode *node) const { stringstream ss; ss << page << " " << x << " " << y << " " << offset; if (!path.empty ()) { ss << " [[" << path << "]]#" << anchor; } node->node_type = CMARK_NODE_TEXT; node->literal = ss.str (); } void Comment::fromMarkdownNode (MarkdownNode *node) { auto qn = node->childAt (0); auto qp = qn->childAt (0); auto qt = qp->childAt (0); quoteText = qt->literal; auto cn = node->childAt (1); commentText = cn->literal; auto list = node->childAt (2); auto i = list->childAt (0); auto p = i->childAt (0); auto t = p->childAt (0); begin.fromMarkdownNode (t); i = list->childAt (1); p = i->childAt (0); t = p->childAt (0); end.fromMarkdownNode (t); i = list->childAt (2); p = i->childAt (0); t = p->childAt (0); struct tm tm{}; strptime (t->literal.c_str (), "%a %b %d %H:%M:%S %Y", &tm); time = std::mktime (&tm); } void Comment::toMarkdownNode (MarkdownNode *node) const { auto qn = MarkdownNode::create (node, CMARK_NODE_BLOCK_QUOTE); auto qp = MarkdownNode::create (qn, CMARK_NODE_PARAGRAPH); MarkdownNode::create (qp, CMARK_NODE_TEXT, quoteText); MarkdownNode::create (node, CMARK_NODE_CODE_BLOCK, commentText); auto list = MarkdownNode::create (node, CMARK_NODE_LIST); list->list_type = CMARK_NO_LIST; auto i = MarkdownNode::create (list, CMARK_NODE_ITEM); auto p = MarkdownNode::create (i, CMARK_NODE_PARAGRAPH); auto t = MarkdownNode::create (p, CMARK_NODE_TEXT); begin.toMarkdownNode (t); i = MarkdownNode::create (list, CMARK_NODE_ITEM); p = MarkdownNode::create (i, CMARK_NODE_PARAGRAPH); t = MarkdownNode::create (p, CMARK_NODE_TEXT); end.toMarkdownNode (t); i = MarkdownNode::create (list, CMARK_NODE_ITEM); p = MarkdownNode::create (i, CMARK_NODE_PARAGRAPH); t = MarkdownNode::create (p, CMARK_NODE_TEXT); t->literal = std::ctime (&time); } Note::~Note () = default; bool Note::loadStreamV1 (std::ifstream &is) { auto doc = Markdown::create (); doc->loadFromStream (is); if (!doc) { return false; } auto node = doc->root (); auto n = node->childAt (0); auto list = node->childAt (1); loadV1Version (list); for (auto i = 2; i < node->childrenCount (); i++) { n = node->childAt (i); if (n->node_type == CMARK_NODE_THEMATIC_BREAK) { continue; } if (i >= node->childrenCount () - 1) break; list = node->childAt (i + 1); if (list == nullptr) break; // skip empty list of heading if (list->node_type == CMARK_NODE_HEADING && list->heading_level == 1) { continue; } auto res = n->headText (); i++; if (res.second == "Meta Data") { loadV1MetaData (list); } else if (res.second == "Comments") { loadV1Comments (list); } else if (res.second == "References") { loadV1References (list); } else if (res.second == "Links") { loadV1Links (list); } else { qDebug () << "Unknown head \"" << res.second << "\""; } } return true; } bool Note::loadStream (std::ifstream &is) { string line; getline (is, line); if (!line.starts_with ("---")) { qWarning () << "note header not found"; return false; } string version; is >> version >> version; if (version == "1") { is.seekg (0, ios::beg); return loadStreamV1 (is); } return false; } bool Note::load (std::string_view sv) { string path = string (sv); if (path.empty ()) path = mPath; if (path.empty ()) return false; mPath = path; ifstream ifs{ path }; if (!ifs.is_open ()) return false; auto ret = loadStream (ifs); ifs.close (); return ret; } void Note::loadV1Version (MarkdownNode *node) { for (auto i = 0; i < node->childrenCount (); i++) { auto n = node->childAt (i); if (n->node_type == CMARK_NODE_SOFTBREAK) continue; auto s = QString::fromLocal8Bit (n->literal); auto vs = s.split (":"); if (vs.size () == 2) { if (vs[0].trimmed () == "version") { auto version = vs[1].trimmed (); Q_ASSERT (version == "1"); } } } } void Note::loadV1MetaData (MarkdownNode *node) { auto texts = node->getListTexts (); for (const auto &text : texts) { auto tokens = QString::fromLocal8Bit (text).split (":"); if (tokens.size () == 2) { auto k = tokens[0].trimmed (); auto v = tokens[1].trimmed (); if (k == "tag") { auto ts = v.split (","); for (auto const &t : ts) { auto tag = t.trimmed (); if (!tag.isEmpty ()) { qDebug () << "got tag: " << tag; mTagSet.insert (tag.toStdString ()); } } } else if (k == "score") { mScore = v.toFloat (); } } } } void Note::loadV1Comments (MarkdownNode *node) { for (auto i = 0; i < node->childrenCount (); i++) { auto ni = node->childAt (i); auto comment = Comment{}; comment.fromMarkdownNode (ni); addComment (comment); } } void Note::loadV1References (MarkdownNode *node) { auto texts = node->getListTexts (); for (const auto &text : texts) { if (!text.empty ()) { mReferences.insert (text); } } } void Note::loadV1Links (MarkdownNode *node) { auto texts = node->getListTexts (); for (const auto &text : texts) { if (!text.empty ()) { mLinks.insert (text); } } } void Note::appendV1Version (MarkdownNode *doc) { MarkdownNode::create (doc, CMARK_NODE_THEMATIC_BREAK); auto g = MarkdownNode::create (doc, CMARK_NODE_PARAGRAPH); MarkdownNode::create (g, CMARK_NODE_TEXT, "version: 1"); MarkdownNode::create (g, CMARK_NODE_LINEBREAK); MarkdownNode::create (g, CMARK_NODE_TEXT, "path: " + mPath); MarkdownNode::create (doc, CMARK_NODE_THEMATIC_BREAK); } void Note::appendV1MetaData (MarkdownNode *doc) { MarkdownNode::HeadAndList headAndList; headAndList.first = "Meta Data"; string tags; for (const auto &cp : mTagSet) { tags += cp + ","; } headAndList.second.push_back ("score: " + QString::number (mScore).toStdString ()); headAndList.second.push_back ("tag: " + tags); doc->appendHeadAndBulletList (1, headAndList); } void Note::appendV1Comments (MarkdownNode *doc) { doc->appendHeadText (1, "Comments"); auto list = MarkdownNode::create (doc, CMARK_NODE_LIST); list->list_type = CMARK_ORDERED_LIST; for (auto const &loc_comment : mCommentList) { auto comment = loc_comment.second; auto n = MarkdownNode::create (list, CMARK_NODE_ITEM); comment.toMarkdownNode (n); } } void Note::appendV1References (MarkdownNode *doc) { MarkdownNode::HeadAndList headAndList; headAndList.first = "References"; for (auto const &r : mReferences) { headAndList.second.push_back (r); } doc->appendHeadAndBulletList (1, headAndList); } void Note::appendV1Links (MarkdownNode *doc) { MarkdownNode::HeadAndList headAndList; headAndList.first = "Links"; for (auto const &r : mLinks) { headAndList.second.push_back (r); } doc->appendHeadAndBulletList (1, headAndList); } bool Note::dumpStream (std::ostream &os) { /* handmade version is ugly */ #if 0 os << "---" << endl; os << "version: 1" << endl; os << "path: " << mFile->getFilename () << endl; os << "---" << endl; os << "# Meta Data" << endl; os << "- tag: "; std::ranges::for_each (mTagSet, [&os] (const string &tag) { os << tag << ","; }); os << endl; os << "- score: " << mScore << endl; os << endl; os << "# Comments" << endl; auto index = 0; std::ranges::for_each (mCommentList, [&os, &index] (const pair &pair1) { os << " - " << index++ << endl; os << pair1.second; }); os << endl; os << "# References" << endl; std::ranges::for_each ( mReferences, [&os] (const string &ref) { os << "- " << ref << endl; }); os << endl; os << "# Links" << endl; std::ranges::for_each ( mLinks, [&os] (const string &link) { os << "- " << link << endl; }); os << endl; #else auto doc = Markdown::create (); auto node = doc->root (); appendV1Version (node); appendV1MetaData (node); appendV1Comments (node); appendV1References (node); appendV1Links (node); doc->saveToStream (os); #endif return true; } bool Note::dump (std::string_view sv) { string path = string (sv); if (path.empty ()) path = mPath; if (path.empty ()) return false; auto fspath = filesystem::path (path).parent_path (); std::error_code code; filesystem::create_directories (fspath, code); ofstream ofs{ path }; if (!ofs.is_open ()) return false; auto ret = dumpStream (ofs); ofs.close (); return ret; } std::string Note::notePathOfFile (File *file) { auto filename = file->getFilename (); return notePathOfPath (filename); } std::string Note::notePathOfPath (std::string_view sv) { auto homedir = QDir::home ().filesystemAbsolutePath ().string (); string filename = string (sv); if (filename.find (homedir) == 0) filename = filename.substr (homedir.size () + 1); if (filename[0] == filesystem::path::preferred_separator) filename = filename.substr (1); if (filename[1] == ':') filename[1] = '-'; auto path = NotesDir + filesystem::path::preferred_separator + filename; return path + ".md"; } } ================================================ FILE: src/ApvlvNote.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvEditor.h * * Author: Alf */ #ifndef _APVLV_NOTE_H_ #define _APVLV_NOTE_H_ #include #include #include #include #include #include namespace apvlv { constexpr float NoteScoreMin = 0.0f; constexpr float NoteScoreMax = 10.0f; class MarkdownNode; struct ApvlvPoint; struct Location { int page{ 0 }; double x{ 0 }; double y{ 0 }; int offset{ -1 }; std::string path; std::string anchor; friend bool operator< (const Location &a, const Location &b) { if (a.page != b.page) return a.page < b.page; if (a.y != b.y) return a.y < b.y; if (a.x != b.x) return a.x < b.x; if (a.offset != b.offset) return a.offset < b.offset; if (a.path != b.path) return a.path < b.path; return false; } void set (int page1, const ApvlvPoint *point1, int offset1 = 0, const std::string &path1 = "", const std::string &anchor1 = ""); void fromMarkdownNode (MarkdownNode *node); void toMarkdownNode (MarkdownNode *node) const; }; class File; class Note; struct Comment { Comment () : time{ std::chrono::system_clock::to_time_t ( std::chrono::system_clock::now ()) } { } std::string quoteText; std::string commentText; Location begin; Location end; time_t time; void fromMarkdownNode (MarkdownNode *node); void toMarkdownNode (MarkdownNode *node) const; }; class Note { public: Note () {} ~Note (); static std::string notePathOfFile (File *file); static std::string notePathOfPath (std::string_view sv); bool loadStreamV1 (std::ifstream &is); bool loadStream (std::ifstream &is); bool load (std::string_view path = ""); bool dumpStream (std::ostream &os); bool dump (std::string_view path = ""); void setScore (float score) { mScore = score; dump (); } float score () { return mScore; } void addTag (const std::string &tag) { mTagSet.insert (tag); dump (); } void removeTag (const std::string &tag) { mTagSet.erase (tag); dump (); } const std::unordered_set & tag () { return mTagSet; } std::string tagString () { if (mTagSet.empty ()) { return ""; } std::ostringstream oss; auto itr = mTagSet.begin (); oss << *itr; ++itr; while (itr != mTagSet.end ()) { oss << "," << *itr; ++itr; } return oss.str (); } void setRemark (const std::string &remark) { mRemark = remark; dump (); } const std::string & remark () { return mRemark; } void addReference (const std::string &ref) { mReferences.insert (ref); dump (); } void removeReference (const std::string &ref) { mReferences.erase (ref); dump (); } const std::unordered_set & references () { return mReferences; } void addLink (const std::string &link) { mLinks.insert (link); dump (); } void removeLink (const std::string &link) { mLinks.erase (link); dump (); } const std::unordered_set & links () { return mLinks; } void addComment (const Comment &comment) { mCommentList.insert ({ comment.begin, comment }); dump (); } void removeComment (const Comment &comment) { mCommentList.erase (comment.begin); dump (); } std::vector getCommentsInPage (int page) { std::vector comments; for (const auto &pair1 : mCommentList) { if (pair1.first.page == page) comments.push_back (pair1.second); }; return comments; } std::vector getCommentsInPath (const std::string &path) { std::vector comments; for (const auto &pair1 : mCommentList) { if (pair1.first.path == path) comments.push_back (pair1.second); } return comments; } private: void loadV1Version (MarkdownNode *node); void loadV1MetaData (MarkdownNode *node); void loadV1Comments (MarkdownNode *node); void loadV1References (MarkdownNode *node); void loadV1Links (MarkdownNode *node); void appendV1Version (MarkdownNode *doc); void appendV1MetaData (MarkdownNode *doc); void appendV1Comments (MarkdownNode *doc); void appendV1References (MarkdownNode *doc); void appendV1Links (MarkdownNode *doc); std::string mPath; float mScore{ 0.0f }; std::unordered_set mTagSet; std::string mRemark; std::unordered_set mReferences; std::unordered_set mLinks; std::map mCommentList; }; } #endif ================================================ FILE: src/ApvlvNoteWidget.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvNoteWidget.cc * * Author: Alf */ #include "ApvlvNoteWidget.h" #include #include #include #include #include namespace apvlv { using namespace std; QString NoteDialog::getTag (const string &filename, const unordered_set &tags, const QStringList &tagList) { auto dia = make_unique (nullptr); dia->setWindowTitle (QString::fromLocal8Bit (filename)); dia->setModal (true); dia->setSizeGripEnabled (true); auto layout = new QVBoxLayout (dia.get ()); dia->setLayout (layout); auto hbox = new QHBoxLayout (); auto label = new QLabel (dia.get ()); label->setText (tr ("Input tag:")); hbox->addWidget (label); auto entry = new QLineEdit (dia.get ()); hbox->addWidget (entry); layout->addLayout (hbox); hbox = new QHBoxLayout (); auto ob = new QPushButton (tr ("OK"), dia.get ()); hbox->addWidget (ob); QObject::connect (ob, SIGNAL (clicked (bool)), dia.get (), SLOT (accept ())); auto oc = new QPushButton (tr ("Cancel"), dia.get ()); hbox->addWidget (oc); QObject::connect (oc, SIGNAL (clicked (bool)), dia.get (), SLOT (reject ())); layout->addLayout (hbox); auto completer = new QCompleter (tagList, dia.get ()); completer->setFilterMode (Qt::MatchContains); entry->setCompleter (completer); auto res = dia->exec (); if (res != QDialog::Accepted) { return {}; } auto str = entry->text (); return str; } } ================================================ FILE: src/ApvlvNoteWidget.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvEditor.h * * Author: Alf */ #ifndef _APVLV_NOTE_WIDGET_H_ #define _APVLV_NOTE_WIDGET_H_ #include #include #include #include "ApvlvEditor.h" #include "ApvlvNote.h" namespace apvlv { class CommentEdit : public Editor { Q_OBJECT public: private: Comment *mComment; }; class NoteEdit : public QFrame { Q_OBJECT public: private: Note *mNote; }; class NoteDialog : QObject { Q_OBJECT public: static QString getTag (const std::string &filename, const std::unordered_set &tags, const QStringList &tagList); }; } #endif ================================================ FILE: src/ApvlvOCR.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvOCR.cc * * Author: Alf */ #include "ApvlvOCR.h" #include "ApvlvParams.h" namespace apvlv { using namespace std; OCR::OCR () { auto lang = ApvlvParams::instance ()->getGroupStringOrDefault ( "ocr", "lang", "eng+chi_sim"); mTessBaseAPI.Init (nullptr, lang.c_str ()); } OCR::~OCR () { mTessBaseAPI.End (); } std::unique_ptr OCR::getTextArea (const QPixmap &pixmap) { return nullptr; } std::unique_ptr OCR::getTextFromPixmap (const QPixmap &pixmap, QRect area) { auto image = pixmap.toImage (); image = image.convertToFormat (QImage::Format_RGB888); mTessBaseAPI.SetImage (image.bits (), image.width (), image.height (), 3, static_cast (image.bytesPerLine ())); auto text = mTessBaseAPI.GetUTF8Text (); if (area.isValid ()) { mTessBaseAPI.SetRectangle (area.left (), area.top (), area.width (), area.height ()); } mTessBaseAPI.Clear (); return unique_ptr (text); } } ================================================ FILE: src/ApvlvOCR.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvOCR.h * * Author: Alf */ #ifndef _APVLV_OCR_H_ #define _APVLV_OCR_H_ #include #include #include #include #include namespace apvlv { using TextAreaVector = std::vector; class OCR final { public: OCR (); ~OCR (); std::unique_ptr getTextArea (const QPixmap &pixmap); std::unique_ptr getTextFromPixmap (const QPixmap &pixmap, QRect area = QRect ()); private: TessBaseAPI mTessBaseAPI; }; } #endif ================================================ FILE: src/ApvlvParams.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvParams.cc * * Author: Alf */ #include #include #include #include #include "ApvlvCmds.h" #include "ApvlvParams.h" #include "ApvlvUtil.h" namespace apvlv { using namespace std; ApvlvParams::ApvlvParams () { push ("inverted", "no"); push ("fullscreen", "no"); push ("zoom", "fitwidth"); push ("continuous", "yes"); push ("autoscrollpage", "yes"); push ("autoscrolldoc", "yes"); push ("noinfo", "no"); push ("width", "800"); push ("height", "600"); push ("fix_width", "0"); push ("fix_height", "0"); push ("background", ""); push ("warpscan", "yes"); push ("commandtimeout", "1000"); #ifdef WIN32 push ("defaultdir", "C:\\"); #else push ("defaultdir", "/tmp"); #endif push ("guioptions", "mTsS"); push ("autoreload", "3"); push ("thread_count", "auto"); push ("lok_path", "/usr/lib64/libreoffice/program"); push (".pdf:engine", "MuPDF"); push (".epub:engine", "Web"); push (".fb2:engine", "Web"); push (".txt:engine", "MuPDF"); push ("ocr:lang", "eng+chi_sim"); } ApvlvParams::~ApvlvParams () = default; bool ApvlvParams::loadFile (const std::string &filename) { string str; fstream os (filename, ios::in); if (!os.is_open ()) { qWarning () << "Open configure file " << filename << " error"; return false; } while ((getline (os, str))) { string argu; string data; string crap; stringstream is (str); is >> crap; if (crap[0] == '\"' || crap.empty ()) { continue; } if (crap == "set") { is >> argu; size_t off = argu.find ('='); if (off == string::npos) { is >> crap >> data; if (crap == "=") { push (argu, data); continue; } } else { argu[off] = ' '; stringstream ass{ argu }; ass >> argu >> data; push (argu, data); continue; } } // like "map n next-page" else if (crap == "map") { is >> argu; if (argu.empty ()) { qWarning () << "map command not complete"; continue; } getline (is, data); while (!data.empty () && isspace (data[0])) data.erase (0, 1); if (!argu.empty () && !data.empty ()) { ApvlvCmds::buildCommandMap (argu, data); } else { qWarning () << "Syntax error: map: " << str; } } else { qWarning () << "Unknown rc command: " << crap << ": " << str; } } return true; } bool ApvlvParams::push (string_view ch, string_view str) { mParamMap[string (ch)] = str; return true; } string ApvlvParams::getGroupStringOrDefault (std::string_view entry, std::string_view key, const std::string &defs) { auto itr = std::ranges::find_if ( mParamMap, [entry, key] (const pair &p) -> bool { if (p.first.find (':') == string::npos) return false; else { auto pos = p.first.find (':'); auto pentry = p.first.substr (0, pos); auto pkey = p.first.substr (pos + 1); return pentry == entry && pkey == key; } }); if (itr != mParamMap.cend ()) { return itr->second; } return defs; } string ApvlvParams::getStringOrDefault (string_view key, const string &defs) { auto itr = std::ranges::find_if (mParamMap, [key] (const pair &p) -> bool { return p.first == key; }); if (itr != mParamMap.cend ()) { return itr->second; } return defs; } int ApvlvParams::getIntOrDefault (string_view key, int defi) { auto values = getStringOrDefault (key, ""); if (values.empty ()) return defi; return int (strtol (values.c_str (), nullptr, 10)); } bool ApvlvParams::getBoolOrDefault (string_view key, bool defb) { auto values = getStringOrDefault (key, ""); if (values.empty ()) return defb; if (values == "true" || values == "yes" || values == "on" || values == "1") { return true; } else { return false; } } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/ApvlvParams.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvParams.h * * Author: Alf */ #ifndef _APVLV_PARAMS_H_ #define _APVLV_PARAMS_H_ #include #include #include namespace apvlv { class ApvlvParams final { public: ApvlvParams (const ApvlvParams &) = delete; const ApvlvParams &operator= (const ApvlvParams &) = delete; ApvlvParams (const ApvlvParams &&) = delete; const ApvlvParams &&operator= (const ApvlvParams &&) = delete; bool loadFile (const std::string &filename); bool push (std::string_view ch, std::string_view str); std::string getGroupStringOrDefault (std::string_view entry, std::string_view key, const std::string &defs = ""); std::string getStringOrDefault (std::string_view key, const std::string &defs = ""); int getIntOrDefault (std::string_view key, int defi = 0); bool getBoolOrDefault (std::string_view key, bool defb = false); static ApvlvParams * instance () { static ApvlvParams inst; return &inst; } private: ApvlvParams (); ~ApvlvParams (); std::map mParamMap; }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/ApvlvQueue.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvQueue.cc * * Author: Alf */ #include #include #include "ApvlvQueue.h" namespace apvlv { using namespace std; unique_ptr TokenDispatcher::getToken (bool isSpecial) { std::unique_lock lk (mMutex); if ((mEnableSpecial && isSpecial) || mDispatchedCount < mCount) { mDispatchedCount++; lk.unlock (); return make_unique (this); } mCondition.wait (lk, [this] { return mDispatchedCount < mCount; }); mDispatchedCount++; return make_unique (this); } void TokenDispatcher::returnToken (Token *token) { std::unique_lock lk (mMutex); mDispatchedCount--; mCondition.notify_all (); } } ================================================ FILE: src/ApvlvQueue.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvCmds.h * * Author: Alf */ #ifndef _APVLV_QUEUE_H_ #define _APVLV_QUEUE_H_ #include #include #include namespace apvlv { template class LockQueue { public: LockQueue () = default; LockQueue (const LockQueue &) = delete; LockQueue &operator= (const LockQueue &) = delete; ~LockQueue () = default; void push (const T &node) { std::lock_guard lock (mMutex); mQueueInternal.push (node); } void push (T &&node) { std::lock_guard lock (mMutex); mQueueInternal.push (std::move (node)); } bool pop (T &node) { std::lock_guard lock (mMutex); if (mQueueInternal.empty ()) return false; node = std::move (mQueueInternal.front ()); mQueueInternal.pop (); return true; } void empty () { std::lock_guard lock (mMutex); return mQueueInternal.empty (); } void clear () { std::lock_guard lock (mMutex); mQueueInternal = {}; } private: std::queue mQueueInternal; std::mutex mMutex; }; class Token; class TokenDispatcher final { public: TokenDispatcher (int count, bool enable) : mCount (count), mEnableSpecial (enable) { mDispatchedCount = 0; } ~TokenDispatcher () = default; std::unique_ptr getToken (bool isSpecial); void returnToken (Token *token); TokenDispatcher (const TokenDispatcher &) = delete; TokenDispatcher &operator= (const TokenDispatcher &) = delete; TokenDispatcher (TokenDispatcher &&) = delete; TokenDispatcher &operator= (TokenDispatcher &&) = delete; private: int mCount; bool mEnableSpecial; int mDispatchedCount; std::mutex mMutex; std::condition_variable mCondition; }; class Token final { public: explicit Token (TokenDispatcher *parent) : mParent (parent), mIsReturned (false) { } ~Token () { if (!mIsReturned) mParent->returnToken (this); } private: TokenDispatcher *mParent; bool mIsReturned; }; } #endif ================================================ FILE: src/ApvlvSearch.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE SearchDialog.cc * * Author: Alf */ #include #include #include #include #include #include "ApvlvFile.h" #include "ApvlvParams.h" #include "ApvlvSearch.h" namespace apvlv { using namespace std; Searcher::Searcher () : mRestart (false), mQuit (false) { auto task = thread (&Searcher::dispatch, this); mTasks.emplace_back (std::move (task)); auto thread_count = thread::hardware_concurrency () - 1; auto thread_value = ApvlvParams::instance ()->getStringOrDefault ("thread_count", "auto"); if (thread_value != "auto") { thread_count = ApvlvParams::instance ()->getIntOrDefault ( "thread_count", thread_count); } for (auto ind = 0u; ind < thread_count; ++ind) { task = thread (&Searcher::fileLoopFunc, this); mTasks.emplace_back (std::move (task)); } } Searcher::~Searcher () { mRestart.store (true); mQuit.store (true); std::ranges::for_each (mTasks, [] (thread &task) { task.join (); }); qDebug ("all search threads ended"); } void Searcher::submit (const SearchOptions &options) { mOptions = options; auto path = filesystem::path (options.mFromDir); if (is_regular_file (path)) { mFilenameQueue.push (absolute (path).string ()); } mRestart.store (true); } unique_ptr Searcher::get () { unique_ptr ptr; mResults.pop (ptr); return ptr; } void Searcher::dispatch () { while (mQuit.load () == false) { if (mRestart.load () == true) { this_thread::sleep_for (2s); mFilenameQueue.clear (); mResults.clear (); mRestart.store (false); try { dirFunc (); } catch (const exception &ext) { qWarning () << "search occurred error: " << ext.what (); } } else { this_thread::sleep_for (1s); } } } void Searcher::dirFunc () { qDebug () << "searching " << QString::fromLocal8Bit (mOptions.mText) << " from " << QString::fromLocal8Bit (mOptions.mFromDir); stack dirs; dirs.push (mOptions.mFromDir); while (!dirs.empty ()) { if (mRestart.load () == true || mQuit.load () == true) { return; } auto dir = dirs.top (); dirs.pop (); filesystem::directory_iterator itr (dir); for (const auto &entry : itr) { if (mRestart.load () == true || mQuit.load () == true) { return; } if (entry.is_directory ()) { if (entry.path ().filename () != "." && entry.path ().filename () != "..") dirs.push (entry.path ().string ()); } else if (entry.is_regular_file ()) { auto ext = entry.path ().extension (); if (ext.empty ()) continue; auto titr = find (mOptions.mTypes.begin (), mOptions.mTypes.end (), entry.path ().extension ()); if (titr != mOptions.mTypes.end ()) { mFilenameQueue.push (entry.path ().string ()); } } } } } void Searcher::fileLoopFunc () { while (mQuit.load () == false) { string name; if (mFilenameQueue.pop (name)) { fileFunc (name); } else { this_thread::sleep_for (1s); } } } void Searcher::fileFunc (const string &path) { auto file = FileFactory::loadFile (path); if (file) { qDebug () << "searching for " << QString::fromLocal8Bit (path); auto result = file->grepFile (mOptions.mText, mOptions.mCaseSensitive, mOptions.mRegex, mRestart); if (result) mResults.push (std::move (result)); } } vector> grep (const string &source, const string &text, bool is_case, bool is_regex) { vector> results; if (is_regex == true) { regex regex_1{ text }; const sregex_token_iterator end; sregex_token_iterator iter; vector regex_texts; while ((iter = regex_token_iterator (source.begin (), source.end (), regex_1)) != end) { regex_texts.push_back (iter->str ()); } size_t pos = 0; for (auto const &r_text : regex_texts) { pos = source.find (r_text, pos); pair res{ pos, r_text.size () }; results.emplace_back (std::move (res)); } } else { auto p_source = &source; auto p_text = &text; if (is_case == false) { auto nsource = source; auto ntext = text; std::ranges::transform (nsource, nsource.begin (), ::tolower); std::ranges::transform (ntext, ntext.begin (), ::tolower); p_source = &nsource; p_text = &ntext; } auto pos = p_source->find (*p_text); while (pos != string::npos) { pair res{ pos, p_text->size () }; results.emplace_back (std::move (res)); pos = p_source->find (*p_text, pos + 1); } } return results; } } ================================================ FILE: src/ApvlvSearch.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE SearchDialog.h * * Author: Alf */ #ifndef _APVLV_SEARCH_H_ #define _APVLV_SEARCH_H_ #include #include #include #include #include "ApvlvQueue.h" namespace apvlv { struct SearchMatch { std::string match; std::string line; size_t pos; size_t length; }; using SearchMatchList = std::vector; struct SearchPageMatch { int page; SearchMatchList matches; }; struct SearchFileMatch { std::string filename; std::vector page_matches; }; class SearchOptions { public: friend bool operator== (SearchOptions const &opt, SearchOptions const &other) { return opt.mText == other.mText && opt.mCaseSensitive != other.mCaseSensitive && opt.mRegex != other.mRegex && opt.mTypes != other.mTypes && opt.mFromDir != other.mFromDir; } std::string mText; bool mCaseSensitive; bool mRegex; std::string mFromDir; std::vector mTypes; }; class Searcher { public: Searcher (); ~Searcher (); void submit (const SearchOptions &options); std::unique_ptr get (); private: void dispatch (); void dirFunc (); void fileLoopFunc (); void fileFunc (const std::string &path); std::vector mTasks; SearchOptions mOptions; LockQueue mFilenameQueue; LockQueue> mResults; std::atomic mRestart; std::atomic mQuit; }; std::vector> grep (const std::string &source, const std::string &text, bool is_case, bool is_regex); } #endif ================================================ FILE: src/ApvlvSearchDialog.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE SearchDialog.cc * * Author: Alf */ #include #include #include "ApvlvFile.h" #include "ApvlvSearchDialog.h" namespace apvlv { using namespace std; SearchDialog::SearchDialog (QWidget *parent) : QDialog (parent), mPreviewIsFinished (true) { setLayout (&mVBox); mVBox.addLayout (&mHBox2); mLabel.setText (tr ("Find Directory: ")); mHBox2.addWidget (&mLabel, 0); mHBox2.addWidget (&mFromDir, 1); QObject::connect (&mFromDir, SIGNAL (returnPressed ()), this, SLOT (search ())); mFromDir.setText (QDir::homePath ()); mDirButton.setText (tr ("...")); mDirButton.setFocusPolicy (Qt::NoFocus); mHBox2.addWidget (&mDirButton, 0); QObject::connect (&mDirButton, &QPushButton::clicked, this, [&] () { auto dir = QFileDialog::getExistingDirectory (); if (!dir.isEmpty ()) mFromDir.setText (dir); }); // search line mVBox.addLayout (&mHBox); mHBox.addWidget (&mSearchEdit, 1); QObject::connect (&mSearchEdit, SIGNAL (returnPressed ()), this, SLOT (search ())); mCaseSensitive.setText (tr ("Case sensitive")); mHBox.addWidget (&mCaseSensitive); mRegex.setText (tr ("Regular expression")); mHBox.addWidget (&mRegex); // file type line mVBox.addLayout (&mHBox3); auto exts = FileFactory::supportFileExts (); std::ranges::for_each (exts, [&] (const auto &ext) { auto checkbox = new QCheckBox ( QString::fromLocal8Bit (ext)); checkbox->setChecked (true); mHBox3.addWidget (checkbox); mTypes.emplace_back (checkbox); }); mVBox.addWidget (&mSplitter); mSplitter.setOrientation (Qt::Vertical); mSplitter.addWidget (&mResults); mSplitter.addWidget (&mPreview); mPreview.resize (400, 300); QObject::connect ( &mResults, SIGNAL (currentItemChanged (QListWidgetItem *, QListWidgetItem *)), this, SLOT (previewItem (QListWidgetItem *))); QObject::connect (&mResults, SIGNAL (itemActivated (QListWidgetItem *)), this, SLOT (activateItem (QListWidgetItem *))); QObject::connect (&mPreview, SIGNAL (loadFinished (bool)), this, SLOT (loadFinish (bool))); QObject::connect (&mGetTimer, SIGNAL (timeout ()), this, SLOT (getResults ())); mGetTimer.start (100); } void SearchDialog::search () { SearchOptions options; options.mText = mSearchEdit.text ().trimmed ().toStdString (); options.mCaseSensitive = mCaseSensitive.isChecked (); options.mRegex = mRegex.isChecked (); options.mFromDir = mFromDir.text ().toStdString (); for (const auto &type : mTypes) { auto ext = type->text ().replace ("&", ""); if (type->isChecked ()) options.mTypes.emplace_back (ext.toStdString ()); } if (mOptions == options) return; mSearcher.submit (options); mResults.clear (); mOptions = options; } void SearchDialog::getResults () { unique_ptr result; while ((result = mSearcher.get ()) != nullptr) { displayResult (std::move (result)); } } void SearchDialog::previewItem (QListWidgetItem *item) { if (item == nullptr) return; if (mPreviewIsFinished == false) return; auto words = item->data (Qt::UserRole).toStringList (); auto path = words[0].toStdString (); auto pn = words[1].toInt (); if (mPreviewFile && mPreviewFile->getFilename () != path) mPreviewFile = nullptr; if (mPreviewFile == nullptr) mPreviewFile = FileFactory::loadFile (path); if (mPreviewFile) { mPreview.setFile (mPreviewFile.get ()); mPreviewIsFinished = false; mPreviewFile->pageRenderToWebView (pn, 1.0, 0, &mPreview); } } void SearchDialog::activateItem (QListWidgetItem *item) { auto words = item->data (Qt::UserRole).toStringList (); auto path = words[0]; auto pn = words[1].toInt (); emit loadFile (path.toStdString (), pn); // accept (); } void SearchDialog::displayResult (unique_ptr result) { auto line = QString::fromLocal8Bit (result->filename); for (const auto &page : result->page_matches) { auto pos = line + ':' + QString::number (page.page + 1); for_each (page.matches.begin (), page.matches.end (), [&] (const auto &match) { auto matchitem = new QListWidgetItem ( { QString::fromLocal8Bit (match.line) }); matchitem->setToolTip (pos); QStringList data{ line, QString::number (page.page) }; matchitem->setData (Qt::UserRole, data); mResults.addItem (matchitem); }); } } void SearchDialog::loadFinish ([[maybe_unused]] bool ret) { mPreviewIsFinished = true; } } ================================================ FILE: src/ApvlvSearchDialog.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE SearchDialog.h * * Author: Alf */ #ifndef _APVLV_SEARCH_DIALOG_H_ #define _APVLV_SEARCH_DIALOG_H_ #include #include #include #include #include #include #include #include #include #include #include #include "ApvlvWebViewWidget.h" namespace apvlv { class File; class SearchDialog : public QDialog { Q_OBJECT public: explicit SearchDialog (QWidget *parent = nullptr); ~SearchDialog () override = default; signals: void loadFile (const std::string &path, int pn); private slots: void search (); void getResults (); void previewItem (QListWidgetItem *item); void activateItem (QListWidgetItem *item); void loadFinish (bool ret); private: void displayResult (std::unique_ptr result); QVBoxLayout mVBox; QHBoxLayout mHBox2; QHBoxLayout mHBox; QHBoxLayout mHBox3; QSplitter mSplitter; QLabel mLabel; QPushButton mDirButton; SearchOptions mOptions; Searcher mSearcher; QTimer mGetTimer; QLineEdit mSearchEdit; QCheckBox mCaseSensitive; QCheckBox mRegex; std::vector mTypes; QLineEdit mFromDir; QListWidget mResults; WebView mPreview; std::unique_ptr mPreviewFile; bool mPreviewIsFinished; }; } #endif ================================================ FILE: src/ApvlvUtil.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvUtil.cc * * Author: Alf */ #include #include #include #include #include #include #include #include #include "ApvlvUtil.h" namespace apvlv { using namespace std; string ScriptDir; string HelpPdf; string IniExam; string IconDir; string IconFile; string IconPage; string Translations; string IniFile; string SessionFile; string LogFile; string NotesDir; string UserScriptDir; static void getXdgOrHomeIni (const QString &appdir) { auto sysenv = QProcessEnvironment::systemEnvironment (); auto xdgdir = sysenv.value ("XDG_CONFIG_DIR").toStdString (); auto homedir = sysenv.value ("HOME").toStdString (); if (homedir.empty ()) { homedir = QDir::homePath ().toStdString (); } IniFile = appdir.toStdString () + "/.apvlvrc"; if (!xdgdir.empty ()) { IniFile = xdgdir + "/apvlv/apvlvrc"; } else if (!homedir.empty ()) { IniFile = homedir + "/.config/apvlv/apvlvrc"; if (!filesystem::is_regular_file (IniFile)) { IniFile = homedir + "/.apvlvrc"; } } } static void getXdgOrCachePath (const QString &appdir) { auto sysenv = QProcessEnvironment::systemEnvironment (); auto xdgdir = sysenv.value ("XDG_CACHE_HOME").toStdString (); auto homedir = sysenv.value ("HOME").toStdString (); if (homedir.empty ()) { homedir = QDir::homePath ().toStdString (); } SessionFile = appdir.toStdString () + "/apvlvinfo"; if (!xdgdir.empty ()) { SessionFile = xdgdir + "/apvlvinfo"; } else if (!homedir.empty ()) { SessionFile = homedir + "/.cache/apvlvinfo"; } } void getRuntimePaths () { auto dirpath = QDir (QCoreApplication::applicationDirPath ()); dirpath.cdUp (); auto prefix = dirpath.path ().toStdString (); ScriptDir = prefix + "/share/scripts"; auto apvlvdir = prefix + "/share/doc/apvlv"; HelpPdf = apvlvdir + "/Startup.pdf"; IconDir = apvlvdir + "/icons/dir.png"; IconFile = apvlvdir + "/icons/pdf.png"; IconPage = apvlvdir + "/icons/reg.png"; Translations = apvlvdir + "/translations"; #ifndef WIN32 IniExam = string (SYSCONFDIR) + "/apvlvrc.example"; #else IniExam = apvlvdir + "/apvlvrc.example"; #endif getXdgOrHomeIni (dirpath.path ()); getXdgOrCachePath (dirpath.path ()); auto homedir = QDir::homePath ().toStdString (); NotesDir = homedir + "/" + "ApvlvNotes"; } optional> xmlContentGetElement (const char *content, size_t length, const vector &names) { auto bytes = QByteArray{ content, (qsizetype)length }; auto xml = make_unique (bytes); std::ptrdiff_t state = 0; while (!xml->atEnd ()) { if (xml->isStartElement ()) { auto name = xml->name ().toString ().toStdString (); auto iter = std::ranges::find (names, name); if (iter == names.end ()) { xml->readNextStartElement (); continue; } auto pos = distance (names.begin (), iter); if (state == pos) { state++; } if (state == static_cast (names.size ())) { return xml; } } xml->readNextStartElement (); } return nullopt; } string xmlStreamGetAttributeValue (QXmlStreamReader *xml, const string &key) { auto attrs = xml->attributes (); for (auto &attr : attrs) { if (attr.name ().toString ().toStdString () == key) return attr.value ().toString ().toStdString (); } return ""; } string xmlContentGetAttributeValue (const char *content, size_t length, const vector &names, const string &key) { auto optxml = xmlContentGetElement (content, length, names); if (!optxml) return ""; auto xml = optxml->get (); return xmlStreamGetAttributeValue (xml, key); } string filenameExtension (const string &filename) { auto pointp = filename.rfind ('.'); if (pointp == string::npos) return ""; string ext = filename.substr (pointp); std::ranges::transform (ext, ext.begin (), ::tolower); return ext; } void imageArgb32ToRgb32 (QImage &image, int left, int top, int right, int bottom) { for (auto x = left; x < right; ++x) { for (auto y = top; y < bottom; ++y) { auto c = image.pixelColor (x, y); double ra = double (c.alpha ()) / 255.0; auto nr = static_cast (c.red () * ra + 255 * (1.0 - ra)); auto ng = static_cast (c.green () * ra + 255 * (1.0 - ra)); auto nb = static_cast (c.blue () * ra + 255 * (1.0 - ra)); auto pc = QColor::fromRgb (nr, ng, nb, 255); image.setPixelColor (x, y, pc); } } } string templateBuild (string_view temp, string_view token, string_view real) { auto pos = temp.find (token); if (pos == string::npos) return string (temp); auto first = temp.substr (0, pos); auto second = temp.substr (pos + token.length ()); auto outstr = string (first); outstr += string (real); outstr += string (second); return outstr; } qint64 parseFormattedDataSize (const QString &sizeStr) { QString cleanStr = sizeStr.simplified ().toUpper (); QRegularExpression re ("^(\\d+(?:\\.\\d+)?)(\\s*)(B?|KB?|MB?|GB?|TB?)$"); QRegularExpressionMatch match = re.match (cleanStr); if (match.hasMatch ()) { double number = match.captured (1).toDouble (); QString unit = match.captured (3); if (unit.isEmpty ()) return static_cast (number); switch (unit[0].unicode ()) { case 'T': return static_cast (number * 1024 * 1024 * 1024 * 1024); case 'G': return static_cast (number * 1024 * 1024 * 1024); case 'M': return static_cast (number * 1024 * 1024); case 'K': return static_cast (number * 1024); default: return static_cast (number); } } return -1; } qint64 filesystemTimeToMSeconds (std::filesystem::file_time_type ftt) { auto epoch = ftt.time_since_epoch () + chrono::seconds{ 6437664000 }; auto milliseconds = chrono::duration_cast (epoch); return static_cast (milliseconds.count ()); } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/ApvlvUtil.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvUtil.h * * Author: Alf */ #ifndef _APVLV_UTIL_H_ #define _APVLV_UTIL_H_ #include #include #include #include #include namespace apvlv { // Global files extern std::string ScriptDir; extern std::string HelpPdf; extern std::string IniExam; extern std::string IconDir; extern std::string IconFile; extern std::string IconPage; extern std::string Translations; extern std::string IniFile; extern std::string SessionFile; extern std::string LogFile; extern std::string NotesDir; extern std::string UserScriptDir; void getRuntimePaths (); #ifdef WIN32 const char PATH_SEP_C = '\\'; const char *const PATH_SEP_S = "\\"; #else const char PATH_SEP_C = '/'; const char *const PATH_SEP_S = "/"; #endif std::optional> xmlContentGetElement (const char *content, size_t length, const std::vector &names); std::string xmlStreamGetAttributeValue (QXmlStreamReader *xml, const std::string &key); std::string xmlContentGetAttributeValue (const char *content, size_t length, const std::vector &names, const std::string &key); std::string filenameExtension (const std::string &filename); void imageArgb32ToRgb32 (QImage &image, int left, int top, int right, int bottom); std::string templateBuild (std::string_view temp, std::string_view token, std::string_view real); qint64 parseFormattedDataSize (const QString &sizeStr); qint64 filesystemTimeToMSeconds (std::filesystem::file_time_type ftt); } #endif // Local Variables: // mode: c++ // mode: c++ // End: ================================================ FILE: src/ApvlvView.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvView.cc * * Author: Alf */ #include #include #include #include #include #include #include "ApvlvDired.h" #include "ApvlvInfo.h" #include "ApvlvParams.h" #include "ApvlvSearchDialog.h" #include "ApvlvView.h" namespace apvlv { using namespace std; using namespace CommandModeType; static bool isAltEscape (QKeyEvent *event) { if (event->key () == Qt::Key_BracketLeft && /* ...so we can ignore mod keys like numlock and capslock. */ (event->modifiers () & Qt::ControlModifier)) { return true; } return false; } void ApvlvCommandBar::keyPressEvent (QKeyEvent *evt) { if (evt->key () == Qt::Key_Escape || isAltEscape (evt) || evt->key () == Qt::Key_Tab || evt->key () == Qt::Key_Up || evt->key () == Qt::Key_Down) { emit keyPressed (evt); return; } QLineEdit::keyPressEvent (evt); } bool ApvlvCommandBar::eventFilter (QObject *obj, QEvent *event) { if (event->type () == QEvent::KeyPress && dynamic_cast (event)->key () == Qt::Key_Tab) { qDebug () << "Ate key press tab"; emit keyPressed (dynamic_cast (event)); return true; } else { return QObject::eventFilter (obj, event); } } ApvlvView::ApvlvView (ApvlvView *parent) : mCmds (this), mSearchDialog (this) { mParent = parent; if (mParent) { mParent->appendChild (this); } auto guiopt = ApvlvParams::instance ()->getStringOrDefault ("guioptions", "mTS"); mCmdType = CmdStatusType::CMD_NONE; mProCmd = 0; mProCmdCnt = 0; mCurrHistroy = -1; mHasFull = false; keyLastEnd = true; processInLast = false; int w = ApvlvParams::instance ()->getIntOrDefault ("width"); int h = ApvlvParams::instance ()->getIntOrDefault ("height"); if (ApvlvParams::instance ()->getBoolOrDefault ("fullscreen")) { fullScreen (); } else { resize (w > 1 ? w : 800, h > 1 ? h : 600); } show (); setCentralWidget (&mCentral); setupMenuBar (guiopt); setMenuBar (&mMenuBar); setupToolBar (); addToolBar (Qt::TopToolBarArea, &mToolBar); bool isMenuBarShow = guiopt.find ('m') != string::npos; if (!isMenuBarShow) { mMenuBar.hide (); } bool isToolBarShow = guiopt.find ('T') != string::npos; if (!isToolBarShow) { mToolBar.hide (); } mCentral.setLayout (&mVBoxLayout); mTabContainer.setTabBarAutoHide (true); mVBoxLayout.addWidget (&mTabContainer, 1); QObject::connect (&mTabContainer, SIGNAL (currentChanged (int)), this, SLOT (tabSwitched (int))); mVBoxLayout.addWidget (&mCommandBar, 0); QObject::connect (&mCommandBar, SIGNAL (textEdited (const QString &)), this, SLOT (commandbarEdited (const QString &))); QObject::connect (&mCommandBar, SIGNAL (returnPressed ()), this, SLOT (commandbarReturn ())); QObject::connect (&mCommandBar, SIGNAL (keyPressed (QKeyEvent *)), this, SLOT (commandbarKeyPressed (QKeyEvent *))); QObject::connect (&mSearchDialog, SIGNAL (loadFile (const std::string &, int)), this, SLOT (loadFileOnPage (const std::string &, int))); cmdHide (); } ApvlvView::~ApvlvView () { if (mParent) { mParent->eraseChild (this); } auto itr = mChildren.begin (); while (itr != mChildren.end ()) { delete *itr; itr = mChildren.begin (); } mCmdHistroy.clear (); } ApvlvWindow * ApvlvView::currentWindow () { auto index = mTabContainer.currentIndex (); if (index < 0) return nullptr; auto root_win = dynamic_cast (mTabContainer.widget (index)); Q_ASSERT (root_win); auto widget = QApplication::focusWidget (); if (widget) { auto win = root_win->findWindowByWidget (widget); if (win) { root_win->setActiveWindow (win); return win; } } auto win = root_win->getActiveWindow (); if (win) return win; return root_win->firstFrameWindow (); } void ApvlvView::delCurrentWindow () { if (auto win = currentWindow (); win) win->perish (); updateTabName (); } void ApvlvView::open () { QString dirname; auto fp = ApvlvInfo::instance ()->lastFile (); if (fp) { dirname = QString::fromLocal8Bit ( filesystem::path (fp.value ()->file).parent_path ().string ()); } else { dirname = QDir::homePath (); } // qDebug ("lastfile: [%s], dirname: [%s]", fp ? fp->file.c_str () : "", // dirname); auto const mimes = FileFactory::supportMimeTypes (); QString filters; for (const auto &m : mimes) { filters += m.first.c_str (); filters += "("; filters += ('*' + m.second[0]); for (decltype (m.second.size ()) index = 1; index < m.second.size (); ++index) filters += (" *" + m.second[index]); filters += ");;"; } QString selected; auto filename = QFileDialog::getOpenFileName (this, "open file", dirname, filters, &selected); if (!filename.isEmpty ()) { loadFile (string (filename.toLocal8Bit ().constData ())); } } void ApvlvView::openDir () { QString dirname; auto fp = ApvlvInfo::instance ()->lastFile (); if (fp) { dirname = QString::fromLocal8Bit ( filesystem::path (fp.value ()->file).parent_path ().string ()); } else { dirname = QDir::homePath (); } auto filename = QFileDialog::getExistingDirectory ( this, "open dir", dirname, QFileDialog::ShowDirsOnly); if (!filename.isEmpty ()) { loadDir (filename.toStdString ()); } } void ApvlvView::openRrl () { auto res = QInputDialog::getText (this, "url", tr ("input url: ")); if (!res.isEmpty ()) { currentFrame ()->loadUri (res.toStdString ()); } } bool ApvlvView::loadDir (const std::string &path) { currentFrame ()->setDirIndex (path); return true; } void ApvlvView::quit (bool only_tab) { if (mTabContainer.count () <= 1 || only_tab == false) { closeEvent (nullptr); return; } auto index = mTabContainer.currentIndex (); Q_ASSERT (index != -1); mTabContainer.removeTab (index); } void ApvlvView::search () { promptCommand ('/'); } void ApvlvView::backSearch () { promptCommand ('?'); } void ApvlvView::advancedSearch () { mSearchDialog.show (); } void ApvlvView::dired () { auto diag = DiredDialog (this); QObject::connect (&diag, SIGNAL (loadFile (const string &, int)), this, SLOT (loadFileOnPage (const string &, int))); diag.exec (); } bool ApvlvView::newTab (const std::string &filename) { auto docname = filename; if (filesystem::is_directory (filename)) { docname = HelpPdf; } auto optndoc = hasLoaded (docname); if (!optndoc) { auto ndoc = new ApvlvFrame (this); if (!ndoc->loadFile (docname, true, true)) { delete ndoc; ndoc = nullptr; } if (ndoc) { regLoaded (ndoc); optndoc = make_optional (ndoc); } } if (optndoc) { newTab (optndoc.value ()); if (filesystem::is_directory (filename)) { loadDir (filename); } return true; } else { return false; } } bool ApvlvView::newTab (ApvlvFrame *core) { auto win = new ApvlvWindow (); win->setFrame (core); auto basename = core->filename () ? filesystem::path (core->filename ()).filename ().string () : "NONE"; auto pos = mTabContainer.currentIndex () + 1; mTabContainer.insertTab (pos, win, QString::fromLocal8Bit (basename)); mTabContainer.setCurrentIndex (pos); return true; } bool ApvlvView::loadFile (const std::string &filename) { auto abpath = filesystem::absolute (filename).string (); ApvlvWindow *win = currentWindow (); ApvlvFrame *ndoc = nullptr; auto optndoc = hasLoaded (abpath); if (!optndoc) { ndoc = new ApvlvFrame (this); if (!ndoc->loadFile (filename, true, true)) { delete ndoc; ndoc = nullptr; } else { regLoaded (ndoc); optndoc = make_optional (ndoc); } } if (optndoc) { win->setFrame (optndoc.value ()); updateTabName (); } return ndoc != nullptr; } void ApvlvView::loadFileOnPage (const string &filename, int pn) { auto cdoc = currentFrame (); if (cdoc) { if (loadFile (filename)) { cdoc->showPage (pn, 0.0); } } } optional ApvlvView::hasLoaded (string_view abpath) { for (auto &core : mDocs) { if (!core->inuse () && abpath == core->filename ()) { return make_optional (core.get ()); } } return nullopt; } void ApvlvView::regLoaded (ApvlvFrame *doc) { auto cache_count = ApvlvParams::instance ()->getIntOrDefault ("cache_count", 10); if (mDocs.size () >= static_cast (cache_count)) { auto found_itr = mDocs.end (); for (auto itr = mDocs.begin (); itr != mDocs.end (); ++itr) { if ((*itr)->inuse () == false) { found_itr = itr; break; } } if (found_itr != mDocs.end ()) { mDocs.erase (found_itr); } } mDocs.push_back (unique_ptr (doc)); } void ApvlvView::setupMenuBar (const string &guiopt) { // File -> open, openDir auto mfile = new QMenu (tr ("File")); mMenuBar.addMenu (mfile); auto action = mfile->addAction (tr ("Open")); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (open ())); action = mfile->addAction (tr ("OpenDir")); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (openDir ())); action = mfile->addAction (tr ("OpenUrl")); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (openRrl ())); action = mfile->addAction (tr ("New Tab")); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (newTab ())); action = mfile->addAction (tr ("Close Tab")); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (closeTab ())); action = mfile->addAction (tr ("Quit")); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (quit (bool))); // Edit -> auto medit = new QMenu (tr ("Edit")); mMenuBar.addMenu (medit); action = medit->addAction (tr ("Search")); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (search ())); action = medit->addAction (tr ("Back Search")); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (backSearch ())); // View -> auto mview = new QMenu (tr ("View")); mMenuBar.addMenu (mview); action = mview->addAction (tr ("ToolBar")); action->setCheckable (true); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (toggleToolBar ())); bool isToolBarShow = guiopt.find ('T') != string::npos; action->setChecked (isToolBarShow); action = mview->addAction (tr ("StatusBar")); action->setCheckable (true); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (toggleStatus ())); bool isStatusShow = guiopt.find ('S') != string::npos; action->setChecked (isStatusShow); action = mview->addAction (tr ("Horizontal Split")); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (horizontalSplit ())); action = mview->addAction (tr ("Vertical Split")); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (verticalSplit ())); action = mview->addAction (tr ("Close Split")); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (unBirth ())); // Navigate -> auto mnavigate = new QMenu (tr ("Navigate")); mMenuBar.addMenu (mnavigate); action = mnavigate->addAction (tr ("Previous Page")); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (previousPage ())); action = mnavigate->addAction (tr ("Next Page")); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (nextPage ())); // Tools auto mtools = new QMenu (tr ("Tools")); mMenuBar.addMenu (mtools); action = mtools->addAction (tr ("Advanced Search")); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (advancedSearch ())); action = mtools->addAction (tr ("Dired")); QObject::connect (action, SIGNAL (trigged (bool)), this, SLOT (dired ())); // Help -> about auto mhelp = new QMenu (tr ("Help")); mMenuBar.addMenu (mhelp); } void ApvlvView::setupToolBar () { auto action = mToolBar.addAction (tr ("Open")); action->setIcon ( QApplication::style ()->standardIcon (QStyle::SP_DialogOpenButton)); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (open ())); action = mToolBar.addAction (tr ("OpenDir")); action->setIcon ( QApplication::style ()->standardIcon (QStyle::SP_DirOpenIcon)); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (openDir ())); action = mToolBar.addAction (tr ("Toggle Directory")); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (toggleDirectory ())); action = mToolBar.addAction (tr ("Quit")); QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (quit (bool))); } void ApvlvView::promptCommand (char ch) { QString s{ ch }; mCommandBar.setText (s); cmdShow (CmdStatusType::CMD_CMD); } void ApvlvView::promptCommand (const char *str) { mCommandBar.setText (str); cmdShow (CmdStatusType::CMD_CMD); } char * ApvlvView::input (const char *str, int width, int height, const string &content) { // need impl return nullptr; } void ApvlvView::cmdShow (CmdStatusType cmdtype) { mCmdType = cmdtype; mCommandBar.show (); mCommandBar.setCursorPosition (1); mCommandBar.setFocus (); } void ApvlvView::cmdHide () { mCmdType = CmdStatusType::CMD_NONE; mCmdTime = chrono::steady_clock::now (); mCommandBar.hide (); mTabContainer.setFocus (); } void ApvlvView::cmdAuto (const char *ps) { stringstream ss (ps); string cmd; string np; string argu; ss >> cmd >> np >> argu; if (np.empty ()) { return; } if (np[np.length () - 1] == '\\') { np.replace (np.length () - 1, 1, 1, ' '); np += argu; ss >> argu; } if (cmd.empty () || np.empty ()) { return; } auto comp = ApvlvCompletion{}; if (cmd == "o" || cmd == "open" || cmd == "TOtext") { comp.addPath (np); } else if (cmd == "doc") { vector items; for (auto &doc : mDocs) { items.emplace_back (doc->filename ()); } comp.addItems (items); } qDebug () << "find match: " << np; auto comtext = comp.complete (np); if (!comtext.empty ()) { qDebug () << "get a match: " << comtext; QString s = QString::fromLocal8Bit (comtext); s.replace (" ", "\\ "); QString linetext = QString::asprintf (":%s %s", cmd.c_str (), s.toStdString ().c_str ()); mCommandBar.setText (linetext); } else { qDebug () << "no get match"; } } void ApvlvView::fullScreen () { if (mHasFull == false) { ApvlvFrame *core = currentFrame (); if (core) core->toggleDirectory (false); showFullScreen (); mHasFull = true; } else { showNormal (); mHasFull = false; } } void ApvlvView::nextPage () { currentFrame ()->nextPage (1); } void ApvlvView::previousPage () { currentFrame ()->previousPage (1); } void ApvlvView::toggleDirectory () { currentFrame ()->toggleDirectory (); } void ApvlvView::toggleToolBar () { if (mToolBar.isHidden ()) { mToolBar.show (); } else { mToolBar.hide (); } } void ApvlvView::toggleStatus () { for (auto &doc : mDocs) { if (doc->isStatusHidden ()) { doc->statusShow (); } else { doc->statusHide (); } } } void ApvlvView::newTab () { auto doc = currentFrame ()->clone (); newTab (doc); } void ApvlvView::closeTab () { quit (true); } void ApvlvView::horizontalSplit () { currentWindow ()->birth (ApvlvWindow::WindowType::SP, nullptr); } void ApvlvView::verticalSplit () { currentWindow ()->birth (ApvlvWindow::WindowType::VSP, nullptr); } void ApvlvView::unBirth () { currentWindow ()->perish (); } ApvlvFrame * ApvlvView::currentFrame () { if (auto widget = QApplication::focusWidget (); widget) { if (auto doc = ApvlvFrame::findByWidget (widget); doc) return doc; } ApvlvWindow *curwin = currentWindow (); Q_ASSERT (curwin); return curwin->getFrame (); } CmdReturn ApvlvView::subProcess (int times, uint keyval) { uint procmd = mProCmd; mProCmd = 0; mProCmdCnt = 0; switch (procmd) { case 'Z': if (keyval == 'Z') quit (true); case ctrlValue ('w'): if (keyval == 'q' || keyval == ctrlValue ('Q')) { if (currentWindow ()->isRoot ()) { quit (true); } else { delCurrentWindow (); } } else { CmdReturn rv = currentWindow ()->process (times, keyval); updateTabName (); return rv; } break; case 'g': if (keyval == 't') { if (times == 0) switchTab (mTabContainer.currentIndex () + 1); else switchTab (times - 1); } else if (keyval == 'T') { if (times == 0) switchTab (mTabContainer.currentIndex () - 1); else switchTab (times - 1); } else if (keyval == 'g') { if (times == 0) times = 1; currentFrame ()->showPage (times - 1, 0.0); } break; default: return CmdReturn::NO_MATCH; } return CmdReturn::MATCH; } CmdReturn ApvlvView::process (int has, int ct, uint key) { if (mProCmd != 0) { auto ret = subProcess (mProCmdCnt, key); if (ret == CmdReturn::MATCH) { saveKey (has, ct, key, true); } return ret; } switch (key) { case 'Z': mProCmd = 'Z'; return CmdReturn::NEED_MORE; case ctrlValue ('w'): mProCmd = ctrlValue ('w'); mProCmdCnt = has ? ct : 1; saveKey (has, ct, key, false); return CmdReturn::NEED_MORE; case 'q': quit (true); break; case 'f': fullScreen (); break; case '.': processLastKey (); break; case 'g': mProCmd = 'g'; mProCmdCnt = has ? ct : 0; saveKey (has, ct, key, false); return CmdReturn::NEED_MORE; default: CmdReturn ret = currentFrame ()->process (has, ct, key); if (ret == CmdReturn::NEED_MORE) { saveKey (has, ct, key, false); } else if (ret == CmdReturn::MATCH) { saveKey (has, ct, key, true); } } return CmdReturn::MATCH; } bool ApvlvView::run (const char *str) { bool ret; switch (*str) { case SEARCH: currentFrame ()->markposition ('\''); ret = currentFrame ()->search (str + 1, false); break; case BACKSEARCH: currentFrame ()->markposition ('\''); ret = currentFrame ()->search (str + 1, true); break; case COMMANDMODE: ret = runCommand (str + 1); break; case FIND: ret = currentFrame ()->search (str + 1, false); break; default: ret = false; break; } return ret; } void ApvlvView::setTitle (const std::string &title) { setWindowTitle (QString::fromLocal8Bit (title)); } bool ApvlvView::runCommand (const char *str) { bool ret = true; if (*str == '!') { system (str + 1); } else { stringstream ss (str); string cmd; string subcmd; string argu; ss >> cmd >> subcmd >> argu; if (!subcmd.empty () && subcmd[subcmd.length () - 1] == '\\') { subcmd.replace (subcmd.length () - 1, 1, 1, ' '); subcmd += argu; ss >> argu; } if (cmd == "set") { if (subcmd == "skip") { currentFrame ()->setSkip ( int (strtol (argu.c_str (), nullptr, 10))); } else { ApvlvParams::instance ()->push (subcmd, argu); } } else if (cmd == "map" && !subcmd.empty ()) { ApvlvCmds::buildCommandMap (subcmd, argu); } else if ((cmd == "o" || cmd == "open" || cmd == "doc") && !subcmd.empty ()) { char *home; if (subcmd[0] == '~') { home = getenv ("HOME"); if (home) { subcmd.replace (0, 1, home); } } if (filesystem::is_directory (subcmd)) { ret = loadDir (subcmd); } else if (filesystem::is_regular_file (subcmd)) { ret = loadFile (subcmd); } else { errorMessage (string ("no file: "), subcmd); ret = false; } } else if (cmd == "TOtext" && !subcmd.empty ()) { currentFrame ()->totext (subcmd.c_str ()); } else if ((cmd == "pr" || cmd == "print")) { currentFrame ()->print ( subcmd.empty () ? -1 : int (strtol (subcmd.c_str (), nullptr, 10))); } else if (cmd == "sp") { if (currentWindow ()->birth (ApvlvWindow::WindowType::SP, nullptr)) { windowAdded (); } } else if (cmd == "vsp") { if (currentWindow ()->birth (ApvlvWindow::WindowType::VSP, nullptr)) { windowAdded (); } } else if ((cmd == "zoom" || cmd == "z") && !subcmd.empty ()) { currentFrame ()->setZoomString (subcmd.c_str ()); } else if (cmd == "forwardpage" || cmd == "fp") { if (subcmd.empty ()) currentFrame ()->nextPage (1); else currentFrame ()->nextPage ( int (strtol (subcmd.c_str (), nullptr, 10))); } else if (cmd == "prewardpage" || cmd == "bp") { if (subcmd.empty ()) currentFrame ()->previousPage (1); else currentFrame ()->previousPage ( int (strtol (subcmd.c_str (), nullptr, 10))); } else if (cmd == "directory") { currentFrame ()->toggleDirectory (); } else if (cmd == "goto" || cmd == "g") { currentFrame ()->markposition ('\''); auto p = strtol (subcmd.c_str (), nullptr, 10); p += currentFrame ()->getSkip (); currentFrame ()->showPage (int (p - 1), 0.0); } else if (cmd == "help" || cmd == "h") { loadFile (HelpPdf); } else if (cmd == "q" || cmd == "quit") { if (currentWindow ()->isRoot ()) { quit (true); } else { delCurrentWindow (); } } else if (cmd == "qall") { quit (false); } else if (cmd == "tabnew") { newTab (HelpPdf); } else if (cmd == "tabn" || cmd == "tabnext") { switchTab (mTabContainer.currentIndex () + 1); } else if (cmd == "tabp" || cmd == "tabprevious") { switchTab (mTabContainer.currentIndex () - 1); } else if (cmd == "toc") { loadFile (currentFrame ()->filename ()); } else { bool isn = true; for (char i : cmd) { if (i < '0' || i > '9') { isn = false; break; } } if (isn && currentFrame ()) { auto p = strtol (cmd.c_str (), nullptr, 10); p += currentFrame ()->getSkip (); if (p != currentFrame ()->pageNumber ()) { currentFrame ()->showPage (int (p - 1), 0.0); } } else { errorMessage (string ("no command: "), cmd); ret = false; } } } return ret; } void ApvlvView::keyPressEvent (QKeyEvent *evt) { if (mCmdType != CmdStatusType::CMD_NONE) { return; } auto now = chrono::steady_clock::now (); auto millis = chrono::duration_cast (now - mCmdTime).count (); if (millis < 1000L) return; mCmds.append (evt); } void ApvlvView::commandbarEdited ([[maybe_unused]] const QString &str) { // need impl } void ApvlvView::commandbarReturn () { if (mCmdType == CmdStatusType::CMD_CMD) { auto str = mCommandBar.text (); if (!str.isEmpty ()) { if (run (str.toStdString ().c_str ())) { mCmdHistroy.emplace_back (str.toStdString ()); mCurrHistroy = mCmdHistroy.size () - 1; cmdHide (); } } else { cmdHide (); } } else if (mCmdType == CmdStatusType::CMD_MESSAGE) { cmdHide (); } } void ApvlvView::commandbarKeyPressed (QKeyEvent *gek) { if (gek->key () == Qt::Key_Tab) { auto str = mCommandBar.text (); if (!str.isEmpty ()) { cmdAuto (str.toStdString ().c_str () + 1); } } else if (gek->key () == Qt::Key_Backspace) { auto str = mCommandBar.text (); if (str.length () == 1) { cmdHide (); mCurrHistroy = mCmdHistroy.size () - 1; } } else if (gek->key () == Qt::Key_Escape || isAltEscape (gek)) { cmdHide (); mCurrHistroy = mCmdHistroy.size () - 1; } else if (gek->key () == Qt::Key_Up) { if (!mCmdHistroy.empty ()) { mCommandBar.setText (QString::fromLocal8Bit ( mCurrHistroy > 0 ? mCmdHistroy[mCurrHistroy--] : mCmdHistroy[0])); } } else if (gek->key () == Qt::Key_Down) { if (!mCmdHistroy.empty ()) { mCommandBar.setText (QString::fromLocal8Bit ( (size_t)mCurrHistroy < mCmdHistroy.size () - 1 ? mCmdHistroy[++mCurrHistroy] : mCmdHistroy[mCmdHistroy.size () - 1])); } } } void ApvlvView::closeEvent (QCloseEvent *evt) { if (mParent == nullptr) { QGuiApplication::exit (); } } void ApvlvView::tabSwitched (int pnum) { qDebug () << "tabwidget switch to: " << pnum; if (pnum == -1) return; auto adoc = currentFrame (); if (adoc && adoc->filename ()) { auto filename = filesystem::path (currentFrame ()->filename ()).filename (); setTitle (filename.string ()); } } void ApvlvView::switchTab (int tabPos) { int ntabs = mTabContainer.count (); while (tabPos < 0) tabPos += ntabs; tabPos = tabPos % ntabs; mTabContainer.setCurrentIndex (tabPos); } void ApvlvView::windowAdded () { auto index = mTabContainer.currentIndex (); Q_ASSERT (index != -1); updateTabName (); } void ApvlvView::updateTabName () { auto win = currentWindow (); if (win == nullptr) return; auto frame = win->getFrame (); if (frame == nullptr) return; const char *filename = frame->filename (); string gfilename; if (filename == nullptr) gfilename = "None"; else gfilename = filesystem::path (filename).filename ().string (); setTitle (gfilename); auto index = mTabContainer.currentIndex (); Q_ASSERT (index != -1); auto tagname = QString::fromLocal8Bit (gfilename); // need impl tagname = QString("[%1] %2").arg (mTabList[index]).arg // (gfilename.c_str()); mTabContainer.setTabText (index, tagname); } void ApvlvView::appendChild (ApvlvView *view) { mChildren.push_back (view); } void ApvlvView::eraseChild (ApvlvView *view) { auto itr = mChildren.begin (); while (*itr != view && itr != mChildren.end ()) itr++; if (*itr == view) { mChildren.erase (itr); } } void ApvlvView::saveKey (int has, int ct, uint key, bool end) { if (processInLast) return; if (keyLastEnd) { keySquence.clear (); } struct keyNode key_node = { has, ct, key, end }; keySquence.push_back (key_node); keyLastEnd = end; } void ApvlvView::processLastKey () { processInLast = true; for (auto node : keySquence) { process (node.Has, node.Ct, node.Key); } processInLast = false; } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/ApvlvView.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvView.h * * Author: Alf */ #ifndef _APVLV_VIEW_H_ #define _APVLV_VIEW_H_ #include #include #include #include #include #include #include #include #include #include "ApvlvCmds.h" #include "ApvlvCompletion.h" #include "ApvlvFrame.h" #include "ApvlvSearchDialog.h" #include "ApvlvWindow.h" namespace apvlv { namespace CommandModeType { const char SEARCH = '/'; const char BACKSEARCH = '?'; const char COMMANDMODE = ':'; const char FIND = 'F'; } class ApvlvFrame; class ApvlvWindow; class ApvlvCommandBar : public QLineEdit { Q_OBJECT public: ApvlvCommandBar () { installEventFilter (this); }; protected: void keyPressEvent (QKeyEvent *evt) override; bool eventFilter (QObject *obj, QEvent *event) override; signals: void keyPressed (QKeyEvent *evt); }; class ApvlvView final : public QMainWindow { Q_OBJECT public: explicit ApvlvView (ApvlvView *view = nullptr); ~ApvlvView () override; ApvlvWindow *currentWindow (); void delCurrentWindow (); bool newTab (const std::string &filename); bool newTab (ApvlvFrame *core); void promptCommand (char ch); void promptCommand (const char *str); template void errorMessage (T... args) { std::stringstream msg; msg << "ERROR: "; msg << (... + args); mCommandBar.setText (QString::fromLocal8Bit (msg.str ())); cmdShow (CmdStatusType::CMD_MESSAGE); } static char *input (const char *str, int width = 400, int height = 150, const std::string &content = ""); bool run (const char *str); bool loadFile (const std::string &filename); bool loadDir (const std::string &path); std::optional hasLoaded (std::string_view abpath); void regLoaded (ApvlvFrame *doc); CmdReturn process (int hastimes, int times, uint keyval); CmdReturn subProcess (int times, uint keyval); void cmdShow (CmdStatusType cmdtype); void cmdHide (); void cmdAuto (const char *str); void setTitle (const std::string &title); ApvlvFrame *currentFrame (); void appendChild (ApvlvView *view); void eraseChild (ApvlvView *view); public slots: void loadFileOnPage (const std::string &filename, int pn); void open (); void openDir (); void openRrl (); void quit (bool only_tab = true); void search (); void backSearch (); void advancedSearch (); void dired (); void fullScreen (); void nextPage (); void previousPage (); void toggleDirectory (); void toggleToolBar (); void toggleStatus (); void newTab (); void closeTab (); void horizontalSplit (); void verticalSplit (); void unBirth (); private: void setupMenuBar (const std::string &guiopt); void setupToolBar (); bool runCommand (const char *cmd); void switchTab (int tabPos); // Update the tab's context and update tab label. void windowAdded (); void updateTabName (); CmdStatusType mCmdType; std::chrono::time_point mCmdTime; uint mProCmd; int mProCmdCnt; QFrame mCentral; QVBoxLayout mVBoxLayout; QTabWidget mTabContainer; ApvlvCommandBar mCommandBar; QMenuBar mMenuBar; QToolBar mToolBar; bool mHasFull; struct keyNode { ; int Has; int Ct; uint Key; bool End; }; bool keyLastEnd; bool processInLast; std::vector keySquence; void saveKey (int has, int ct, uint key, bool end); void processLastKey (); void closeEvent (QCloseEvent *evt) override; void keyPressEvent (QKeyEvent *evt) override; ApvlvCmds mCmds; SearchDialog mSearchDialog; std::vector> mDocs; std::vector mCmdHistroy; size_t mCurrHistroy; ApvlvView *mParent; std::vector mChildren; private slots: void commandbarEdited (const QString &str); void commandbarReturn (); void commandbarKeyPressed (QKeyEvent *gek); void tabSwitched (int ind); }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/ApvlvWeb.cc ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvHtm.cc * * Author: Alf */ #include #include "ApvlvWeb.h" #include "ApvlvWebViewWidget.h" namespace apvlv { using namespace std; bool ApvlvWEB::load (const string &filename) { mUrl = filename.c_str (); return true; } bool ApvlvWEB::pageRenderToWebView (int pn, double zm, int rot, WebView *webview) { webview->setZoomFactor (zm); // do not load started page in browsing if (!webview->url ().isValid ()) { webview->load (mUrl); } return true; } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/ApvlvWeb.h ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvWeb.h * * Author: Alf */ #ifndef _APVLV_WEB_H_ #define _APVLV_WEB_H_ #include #include "ApvlvFile.h" #include "ApvlvWebViewWidget.h" namespace apvlv { class ApvlvWEB : public File { public: ApvlvWEB () = default; [[nodiscard]] DISPLAY_TYPE getDisplayType () const override { return DISPLAY_TYPE::CUSTOM; } FileWidget * getWidget () override { auto wid = new WebViewWidget (); wid->setFile (this); wid->setInternalScroll (true); return wid; } bool load (const std::string &filename) override; bool pageRenderToWebView (int pn, double zm, int rot, WebView *webview) override; protected: QUrl mUrl; }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/ApvlvWebViewWidget.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvWebViewWidget.cc * * Author: Alf */ #include #include #include #include #include #include #include #include #include "ApvlvUtil.h" #include "ApvlvWebViewWidget.h" namespace apvlv { using namespace std; void ApvlvSchemeHandler::requestStarted (QWebEngineUrlRequestJob *job) { auto url = job->requestUrl (); auto path = url.path ().toStdString (); auto key = path.substr (1); auto mime = mFile->pathMimeType (key); auto roptcont = mFile->pathContent (key); if (!roptcont) { job->fail (QWebEngineUrlRequestJob::UrlNotFound); return; } mArray = std::move (roptcont.value ()); mBuffer.setData (mArray); job->reply (QByteArray (mime.c_str ()), &mBuffer); emit webpageUpdated (key); } WebView::WebView () { mProfile.setHttpCacheType (QWebEngineProfile::NoCache); mProfile.installUrlSchemeHandler ("apvlv", &mSchemeHandler); mPage = make_unique (&mProfile); setPage (mPage.get ()); mCopyAction.setText (tr ("Copy")); QObject::connect (&mCopyAction, SIGNAL (triggered (bool)), this, SLOT (copy ())); mMenu.addAction (&mCopyAction); mUnderlineAction.setText (tr ("Underline")); QObject::connect (&mUnderlineAction, SIGNAL (triggered (bool)), this, SLOT (underline ())); mMenu.addAction (&mUnderlineAction); mCommentAction.setText (tr ("Comment")); QObject::connect (&mCommentAction, SIGNAL (triggered (bool)), this, SLOT (comment ())); mMenu.addAction (&mCommentAction); } WebViewWidget::WebViewWidget () { QObject::connect (&mWebView, SIGNAL (loadFinished (bool)), this, SLOT (webviewLoadFinished (bool))); QObject::connect (&mWebView.mSchemeHandler, SIGNAL (webpageUpdated (const string &)), this, SLOT (webviewUpdate (const string &))); loadJavaScriptFromDir (ScriptDir); } bool WebViewWidget::loadJavaScriptFromDir (const std::string &dir) { std::filesystem::path script_dir{ dir }; if (!is_directory (script_dir)) return false; QList script_list; for (auto &entry : std::filesystem::directory_iterator ( script_dir, std::filesystem::directory_options::follow_directory_symlink)) { auto const &path = entry.path (); if (entry.is_regular_file () && path.extension () == ".js") { QFile file (path); if (!file.open (QIODevice::ReadOnly | QIODevice::Text)) { qWarning () << "open " << path.c_str () << "to read error"; continue; } auto contents = file.readAll (); auto script = QWebEngineScript{}; script.setName (QString::fromStdString (path.filename ())); script.setSourceCode (contents); script.setInjectionPoint (QWebEngineScript::DocumentCreation); script.setWorldId (QWebEngineScript::MainWorld); script.setRunsOnSubFrames (true); script_list.push_back (script); } } auto page = mWebView.page (); page->scripts ().insert (script_list); return true; } void WebViewWidget::showPage (int p, double s) { mFile->pageRenderToWebView (p, mZoomrate, 0, &mWebView); mPageNumber = p; } void WebViewWidget::showPage (int p, const string &anchor) { mFile->pageRenderToWebView (p, mZoomrate, 0, &mWebView); mPageNumber = p; mAnchor = anchor; } void WebViewWidget::scroll (int times, int h, int v) { if (!mFile) return; auto scripts = QString ("scrollByTimes(%1, %2, %3);").arg (times).arg (h).arg (v); auto page = mWebView.page (); page->runJavaScript (scripts); } void WebViewWidget::scrollTo (double xrate, double yrate) { if (!mFile) return; auto scripts = QString ("scrollToPosition(%1, %2);").arg (xrate).arg (yrate); auto page = mWebView.page (); page->runJavaScript (scripts); } void WebViewWidget::scrollUp (int times) { scroll (times, 0, -50); if (mWebView.isScrolledToTop ()) { if (mIsInternalScroll) { auto scripts = QString ("dispatchKeydownEvent(%1);").arg (37); auto page = mWebView.page (); page->runJavaScript (scripts); } else { auto p = mFile->pageNumberWrap (mPageNumber - 1); if (p >= 0) { mIsScrollUp = true; showPage (p, 0.0); } } } } void WebViewWidget::scrollDown (int times) { scroll (times, 0, 50); if (mWebView.isScrolledToBottom ()) { if (mIsInternalScroll) { auto scripts = QString ("dispatchKeydownEvent(%1);").arg (39); auto page = mWebView.page (); page->runJavaScript (scripts); } else { auto p = mFile->pageNumberWrap (mPageNumber + 1); if (p >= 0) showPage (p, 0.0); } } } void WebViewWidget::scrollLeft (int times) { scroll (times, -50, 0); } void WebViewWidget::scrollRight (int times) { scroll (times, 50, 0); } void WebViewWidget::setSearchStr (const string &str) { auto qstr = QString::fromLocal8Bit (str); QWebEnginePage::FindFlags flags{}; mWebView.findText (qstr, flags); } void WebViewWidget::setSearchSelect (int select) { auto text = mWebView.selectedText (); QWebEnginePage::FindFlags flags{}; mWebView.findText (text, flags); mSearchSelect = select; } void WebViewWidget::setZoomrate (double zm) { mWebView.setZoomFactor (zm); mZoomrate = zm; } bool WebView::isScrolledToTop () { auto page = this->page (); auto p = page->scrollPosition (); return p.y () < 0.5; } bool WebView::isScrolledToBottom () { auto page = this->page (); auto p = page->scrollPosition (); auto cs = page->contentsSize (); return p.y () + QWebEngineView::height () + 0.5 > cs.height (); } void WebView::contextMenuEvent (QContextMenuEvent *event) { qDebug () << "WebView::customeMenuRequest"; mMenu.popup (mapToGlobal (event->pos ())); } std::pair WebView::getSelectionPosition () const { int begin = -1; int end = -1; QEventLoop loop; mPage->runJavaScript ( "getSelectionOffset(0);", [&loop, &begin, &end] (const QVariant &result) { if (result.isValid () && result.typeId () == QMetaType::QVariantList) { auto offsets = result.toList (); begin = offsets[0].toInt (); end = offsets[1].toInt (); qDebug () << "Begin offset:" << offsets[0].toInt (); qDebug () << "End offset:" << offsets[1].toInt (); loop.quit (); } }); loop.exec (); return std::make_pair (begin, end); } void WebView::underLinePosition (int begin, int end, const std::string &tooltip) { qDebug () << "underLinePosition" << begin << " -> " << end; QString src = QString ("underlineByOffset(%1, %2, '%3');") .arg (begin) .arg (end) .arg (tooltip.c_str()); mPage->runJavaScript (src); } void WebView::copy () const { qDebug () << "copy text"; auto text = mPage->selectedText (); auto clipboard = QGuiApplication::clipboard (); clipboard->setText (text); } void WebView::underline () { qDebug () << "underline text"; auto text = mPage->selectedText (); if (text.isEmpty ()) { return; } auto offset = getSelectionPosition (); if (offset.first < 0 || offset.second < 0) { return; } auto path = mPage->url ().path ().toStdString (); auto file = mSchemeHandler.file (); auto note = file->getNote (); Comment comment; comment.quoteText = text.toStdString (); comment.begin.set (0, nullptr, offset.first, path); comment.end.set (0, nullptr, offset.second, path); note->addComment (comment); } void WebView::comment () { qDebug () << "comment text"; do { auto text = mPage->selectedText (); if (text.isEmpty ()) break; auto input_text = QInputDialog::getText (this, tr ("Input"), tr ("Comment")); auto commentText = input_text.trimmed (); if (commentText.isEmpty ()) break; commentText = commentText.toHtmlEscaped (); auto offset = getSelectionPosition (); if (offset.first < 0 || offset.second < 0) break; auto path = mPage->url ().path ().toStdString (); auto file = mSchemeHandler.file (); auto note = file->getNote (); Comment comment; comment.quoteText = text.toStdString (); comment.commentText = commentText.toStdString (); comment.begin.set (0, nullptr, offset.first, path); comment.end.set (0, nullptr, offset.second, path); note->addComment (comment); } while (false); } void WebViewWidget::setComment () { auto note = mFile->getNote (); if (!note) return; auto path = mWebView.page ()->url ().path ().toStdString (); auto comments = note->getCommentsInPath (path); for (auto &comment : comments) { auto begin_offset = comment.begin.offset; auto end_offset = comment.end.offset; mWebView.underLinePosition (begin_offset, end_offset, comment.commentText); } } void WebViewWidget::webviewLoadFinished (bool suc) { if (suc) { if (!mAnchor.empty ()) { auto page = mWebView.page (); auto scripts = QString ("scrollToAnchor('%1');").arg (mAnchor.c_str ()); page->runJavaScript (scripts); } else if (mIsScrollUp) { scrollTo (0.0, 1.0); mIsScrollUp = false; } if (!mIsScrollUp) { setComment (); } } } void WebViewWidget::webviewFindTextFinished ( const QWebEngineFindTextResult &result) { mSearchResult = result; } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/ApvlvWebViewWidget.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvWebViewWidget.h * * Author: Alf */ #ifndef _APVLV_WEBVIEW_WIDGET_H_ #define _APVLV_WEBVIEW_WIDGET_H_ #include #include #include #include #include #include #include #include #include #include #include "ApvlvFileWidget.h" namespace apvlv { class File; class ApvlvSchemeHandler : public QWebEngineUrlSchemeHandler { Q_OBJECT public: void setFile (File *file) { mFile = file; } File * file () const { return mFile; } void requestStarted (QWebEngineUrlRequestJob *job) override; private: File *mFile; QByteArray mArray; QBuffer mBuffer; signals: void webpageUpdated (const std::string &key); }; class WebView : public QWebEngineView { Q_OBJECT public: WebView (); void setFile (File *file) { mSchemeHandler.setFile (file); } protected: void contextMenuEvent (QContextMenuEvent *event) override; private: QWebEngineProfile mProfile; std::unique_ptr mPage; ApvlvSchemeHandler mSchemeHandler; QMenu mMenu; bool isScrolledToTop (); bool isScrolledToBottom (); [[nodiscard]] std::pair getSelectionPosition () const; void underLinePosition (int begin, int end, const std::string &); QAction mCopyAction; QAction mUnderlineAction; QAction mCommentAction; friend class WebViewWidget; private slots: void copy () const; void underline (); void comment (); }; class WebViewWidget : public FileWidget { Q_OBJECT public: WebViewWidget (); [[nodiscard]] QWidget * widget () override { return &mWebView; } void setFile (File *file) override { mFile = file; mWebView.setFile (mFile); } void showPage (int pn, double s) override; void showPage (int pn, const std::string &anchor) override; void scroll (int times, int w, int h) override; void scrollTo (double x, double y) override; void scrollUp (int times) override; void scrollDown (int times) override; void scrollLeft (int times) override; void scrollRight (int times) override; void setSearchStr (const std::string &str) override; void setSearchSelect (int select) override; void setZoomrate (double zm) override; void setInternalScroll (bool internal) { mIsInternalScroll = internal; } bool internalScroll () { return mIsInternalScroll; } private: WebView mWebView{}; bool mIsInternalScroll{ false }; bool mIsScrollUp{ false }; QWebEngineFindTextResult mSearchResult; bool loadJavaScriptFromDir (const std::string &dir); void setComment (); signals: void webpageUpdated (const std::string &msg); private slots: void webviewUpdate (const std::string &msg) { emit webpageUpdated (msg); }; void webviewLoadFinished (bool suc); void webviewFindTextFinished (const QWebEngineFindTextResult &result); }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/ApvlvWidget.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvWidget.cc * * Author: Alf */ #include #include "ApvlvWidget.h" namespace apvlv { void ApvlvLineEdit::keyPressEvent (QKeyEvent *event) { if (event->key () == Qt::Key_Escape) { clearFocus (); event->ignore (); } else { QLineEdit::keyPressEvent (event); } } } ================================================ FILE: src/ApvlvWidget.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvWidget.h * * Author: Alf */ #ifndef _APVLV_WIDGET_H_ #define _APVLV_WIDGET_H_ #include namespace apvlv { class ApvlvLineEdit : public QLineEdit { protected: void keyPressEvent (QKeyEvent *event) override; }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/ApvlvWindow.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvWindow.cc * * Author: Alf */ #include #include #include #include "ApvlvParams.h" #include "ApvlvView.h" #include "ApvlvWindow.h" namespace apvlv { ApvlvWindow::ApvlvWindow () { qDebug () << "win: " << this << ", init"; setLayout (&mLayout); mLayout.addWidget (&mPaned); } ApvlvWindow::~ApvlvWindow () { qDebug () << "win: " << this << ", released"; if (mType == WindowType::FRAME) { auto frame = stealFrame (); if (frame) frame->inuse (false); } } CmdReturn ApvlvWindow::process (int ct, uint key) { ApvlvWindow *nwin; qDebug () << "input [" << key << "]"; switch (key) { case ctrlValue ('w'): case 'k': case 'j': case 'h': case 'l': nwin = getNeighbor (ct, key); if (nwin != nullptr && nwin != this) { nwin->setActive (true); } break; case '-': smaller (ct); break; case '+': bigger (ct); break; default: break; } return CmdReturn::MATCH; } ApvlvWindow * ApvlvWindow::findWindowByWidget (QWidget *widget) { auto doc = ApvlvFrame::findByWidget (widget); if (doc == nullptr) return nullptr; std::stack winstack; winstack.push (this); while (!winstack.empty ()) { auto win = winstack.top (); winstack.pop (); if (win->mType == ApvlvWindow::WindowType::FRAME) { if (win->getFrame () == doc) return win; } else { winstack.push (win->firstWindow ()); winstack.push (win->secondWindow ()); } } return nullptr; } ApvlvWindow * ApvlvWindow::getNeighbor (int count, uint key) { ApvlvWindow *last_win = nullptr; ApvlvWindow *win = this; for (int c = 0; c < count; ++c) { switch (key) { case ctrlValue ('w'): win = win->getNext (); break; case 'k': win = win->getTop (); break; case 'j': win = win->getBottom (); break; case 'h': win = win->getLeft (); break; case 'l': win = win->getRight (); break; default: break; } if (win != nullptr) last_win = win; else break; } return last_win; } ApvlvWindow * ApvlvWindow::getLeft () { if (mType == WindowType::FRAME && getFrame ()->toggledControlDirectory (false)) { return this; } ApvlvWindow *fwin = this; while (fwin->parentWindow ()) { fwin = fwin->parentWindow (); if (fwin->mType == WindowType::SP && fwin->firstWindow () != this) { if (fwin->secondWindow () == this) return fwin->firstWindow (); while (fwin->mType != WindowType::FRAME) { fwin = fwin->secondWindow (); } return fwin; } } return nullptr; } ApvlvWindow * ApvlvWindow::getRight () { if (mType == WindowType::FRAME && getFrame ()->toggledControlDirectory (true)) { return this; } ApvlvWindow *fwin = this; while (fwin->parentWindow ()) { fwin = fwin->parentWindow (); if (fwin->mType == WindowType::SP && fwin->secondWindow () != this) { if (fwin->firstWindow () == this) return fwin->secondWindow (); while (fwin->mType != WindowType::FRAME) { fwin = fwin->firstWindow (); } return fwin; } } return nullptr; } ApvlvWindow * ApvlvWindow::getTop () { ApvlvWindow *fwin = this; while (fwin->parentWindow ()) { fwin = fwin->parentWindow (); if (fwin->mType == WindowType::VSP && (fwin->firstWindow () != this)) { if (fwin->secondWindow () == this) return fwin->firstWindow (); while (fwin->mType != WindowType::FRAME) { fwin = fwin->secondWindow (); } return fwin; } } return nullptr; } ApvlvWindow * ApvlvWindow::getBottom () { ApvlvWindow *fwin = this; while (fwin->parentWindow ()) { fwin = fwin->parentWindow (); if (fwin->mType == WindowType::VSP && fwin->secondWindow () != this) { if (fwin->firstWindow () == this) return fwin->secondWindow (); while (fwin->mType != WindowType::FRAME) { fwin = fwin->firstWindow (); } return fwin; } } return nullptr; } void ApvlvWindow::splitWidget (WindowType type, QWidget *one, QWidget *other) { setFrameStyle (QFrame::NoFrame); setLineWidth (0); mPaned.setOrientation (type == WindowType::SP ? Qt::Horizontal : Qt::Vertical); mPaned.setHandleWidth (10); mPaned.setStretchFactor (0, 1); mPaned.setStretchFactor (1, 1); mPaned.addWidget (one); mPaned.addWidget (other); mType = type; } void ApvlvWindow::perishWidget () { auto par = parentWindow (); auto win1 = par->firstWindow (); auto win2 = par->secondWindow (); auto rewin = (this == win1 ? win2 : win1); auto par_par = par->parent (); if (par_par->inherits ("QStackedWidget")) { win1->setParent (nullptr); win2->setParent (nullptr); if (rewin->mType == WindowType::FRAME) { auto frame = rewin->stealFrame (); par->setFrame (frame); } else { par->splitWidget (rewin->mType, rewin->firstWindow (), rewin->secondWindow ()); } qDebug () << win1 << " will be deleted "; win1->deleteLater (); qDebug () << win2 << " will be deleted "; win2->deleteLater (); } else { auto par_splitter = dynamic_cast (par_par); auto par0 = dynamic_cast (par_splitter->widget ((0))); auto par1 = dynamic_cast (par_splitter->widget ((1))); rewin->setParent (nullptr); par0->setParent (nullptr); par1->setParent (nullptr); if (par0 == par) { qDebug () << rewin << " and " << par1 << " will be inserted "; par_splitter->addWidget (rewin); par_splitter->addWidget (par1); qDebug () << par0 << " will be deleted "; par0->deleteLater (); } else { qDebug () << par0 << " and " << rewin << " will be inserted "; par_splitter->addWidget (par0); par_splitter->addWidget (rewin); qDebug () << par1 << " will be deleted "; par1->deleteLater (); } } } void ApvlvWindow::setAsRootActive () { auto root_win = rootWindow (); if (root_win) root_win->setActiveWindow (this); } ApvlvWindow * ApvlvWindow::getNext () { auto *n = getRight (); if (n) return n; n = getBottom (); if (n) return n; n = getLeft (); if (n) return n; n = getTop (); return n; } // birth a new FRAME window, and the new window beyond the input doc // this made a FRAME window to SP|VSP bool ApvlvWindow::birth (WindowType type, ApvlvFrame *doc) { Q_ASSERT (mType == WindowType::FRAME); auto frame = dynamic_cast (mPaned.widget (0)); frame->setParent (nullptr); if (doc == nullptr) { doc = frame->clone (); if (doc == nullptr) { frame->mView->errorMessage ("can't split"); return false; } frame->mView->regLoaded (doc); } auto win1 = new ApvlvWindow (); win1->setFrame (frame); auto win2 = new ApvlvWindow (); win2->setFrame (doc); qDebug () << "win: " << this << " birth two: " << win1 << " and " << win2; splitWidget (type, win1, win2); if (auto root = rootWindow (); root && root->mActive == this) { root->mActive = win1; } return true; } void ApvlvWindow::perish () { qDebug () << "win: " << this << " try to perish "; if (mType != WindowType::FRAME) return; if (auto root = rootWindow (); root && root->mActive == this) { root->mActive = nullptr; } perishWidget (); } void ApvlvWindow::setActive (bool act) { Q_ASSERT (mType == WindowType::FRAME); QTimer::singleShot (50, getFrame (), SLOT (setFocus ())); } void ApvlvWindow::setFrame (ApvlvFrame *doc) { qDebug () << "win: " << this << ", set core: " << doc; setFrameStyle (QFrame::Raised | QFrame::Box); setLineWidth (1); if (mType == WindowType::FRAME) { auto frame = stealFrame (); if (frame) { frame->inuse (false); } } mPaned.setStretchFactor (0, 1); mPaned.addWidget (doc); doc->inuse (true); mType = WindowType::FRAME; QObject::connect (doc, SIGNAL (focusIn ()), this, SLOT (setAsRootActive ())); } ApvlvFrame * ApvlvWindow::stealFrame () { Q_ASSERT (mType == WindowType::FRAME); auto frame = dynamic_cast (mPaned.widget (0)); if (frame) { frame->setParent (nullptr); } return frame; } ApvlvFrame * ApvlvWindow::getFrame () { Q_ASSERT (mType == WindowType::FRAME); auto frame = dynamic_cast (mPaned.widget (0)); return frame; } ApvlvWindow * ApvlvWindow::firstWindow () { Q_ASSERT (mType != WindowType::FRAME); auto win = dynamic_cast (mPaned.widget (0)); return win; } ApvlvWindow * ApvlvWindow::secondWindow () { Q_ASSERT (mType != WindowType::FRAME); auto win = dynamic_cast (mPaned.widget (1)); return win; } ApvlvWindow * ApvlvWindow::parentWindow () { auto win = parent (); Q_ASSERT (win); if (win->inherits ("QSplitter")) win = win->parent (); return dynamic_cast (win); } ApvlvWindow * ApvlvWindow::rootWindow () { auto win = this; while (win && !win->isRoot ()) win = win->parentWindow (); return win; } ApvlvWindow * ApvlvWindow::firstFrameWindow () { auto win = this; while (win->mType != WindowType::FRAME) { win = win->firstWindow (); } return win; } bool ApvlvWindow::isRoot () { auto par = parentWindow (); if (par == nullptr) return true; else return false; } void ApvlvWindow::smaller (int times) { if (isRoot ()) return; auto pwin = parentWindow (); auto sizes = pwin->mPaned.sizes (); int len = 20 * times; if (pwin->firstWindow () == this) { sizes[0] -= len; sizes[1] += len; } else { sizes[0] += len; sizes[1] -= len; } pwin->mPaned.setSizes (sizes); } void ApvlvWindow::bigger (int times) { if (isRoot ()) return; smaller (0 - times); } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/ApvlvWindow.h ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvWindow.h * * Author: Alf */ #ifndef _APVLV_WINDOW_H_ #define _APVLV_WINDOW_H_ #include "ApvlvFrame.h" namespace apvlv { class ApvlvFrame; class ApvlvWindowContext; class ApvlvWindow final : public QFrame { Q_OBJECT public: ApvlvWindow (); ~ApvlvWindow () override; /* WE operate the AW_DOC window * Any SP, VSP are a virtual window, just for contain the AW_DOC window * AW_NONE is an empty window, need free * So, ANY user interface function can only get the AW_DOC window * */ enum class WindowType { FRAME, SP, VSP, }; WindowType mType{ WindowType::FRAME }; bool birth (WindowType type, ApvlvFrame *doc); void perish (); void setActive (bool act); void setFrame (ApvlvFrame *doc); ApvlvFrame *stealFrame (); ApvlvFrame *getFrame (); ApvlvWindow *firstWindow (); ApvlvWindow *secondWindow (); ApvlvWindow *parentWindow (); ApvlvWindow *rootWindow (); ApvlvWindow *firstFrameWindow (); void setActiveWindow (ApvlvWindow *win) { mActive = win; } ApvlvWindow * getActiveWindow () { return mActive; } bool isRoot (); void smaller (int times = 1); void bigger (int times = 1); ApvlvWindow *getNeighbor (int count, uint key); ApvlvWindow *getNext (); CmdReturn process (int times, uint keyval); ApvlvWindow *findWindowByWidget (QWidget *widget); private: QVBoxLayout mLayout; QSplitter mPaned; ApvlvWindow *mActive{ nullptr }; ApvlvWindow *getLeft (); ApvlvWindow *getRight (); ApvlvWindow *getTop (); ApvlvWindow *getBottom (); void splitWidget (WindowType type, QWidget *one, QWidget *other); private slots: void perishWidget (); void setAsRootActive (); }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/CMakeLists.txt ================================================ # Simplified CMakeLists.txt for apvlv src directory # Include source and engine configurations include(${CMAKE_CURRENT_SOURCE_DIR}/Sources.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/Engines.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/WindowsEngines.cmake) # Setup include directories include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${QUAZIP_INCLUDE_DIRS} ${Qt_INCLUDE_DIRS} ${CMARK_INCLUDE_DIRS} ) # Setup link directories link_directories(${QUAZIP_LIBRARY_DIRS}) # Initialize required libraries set(APVLV_REQ_LIBRARIES ${QUAZIP_LIBRARIES} ${Qt_LIBRARIES} ${CMARK_LIBRARIES} ) # Add engine-specific files and libraries list(APPEND HEADERS ${APVLV_ENGINE_HEADERS}) list(APPEND SOURCES ${APVLV_ENGINE_SOURCES}) list(APPEND APVLV_REQ_LIBRARIES ${APVLV_ENGINE_LIBRARIES}) # Message about engines message(STATUS "Building with QtPdf engine") message(STATUS "Building with Web engine for EPUB/FB2") # Format target for clang-format find_program(CLANG_FORMAT clang-format) if(CLANG_FORMAT) # Get all source files file(GLOB_RECURSE ALL_CXX_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cc" "${CMAKE_CURRENT_SOURCE_DIR}/*.h" ) # Create format target add_custom_target(format COMMAND ${CLANG_FORMAT} -i -style=file ${ALL_CXX_SOURCES} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Formatting source code with clang-format" ) message(STATUS "Added 'make format' target for code formatting") else() message(WARNING "clang-format not found, 'make format' target not available") endif() # Create executable add_executable(apvlv ${HEADERS} ${SOURCES}) set_property(TARGET apvlv PROPERTY AUTOMOC ON) target_link_libraries(apvlv ${APVLV_REQ_LIBRARIES}) # Test executable add_executable(testNote ApvlvNote.cc ApvlvMarkdown.cc testNote.cc) set_property(TARGET testNote PROPERTY AUTOMOC ON) target_link_libraries(testNote ${APVLV_REQ_LIBRARIES}) # Post-build operations if(WIN32) # Windows specific post-build add_custom_command(TARGET apvlv POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/share ${CMAKE_CURRENT_BINARY_DIR}/share ) else() # Unix specific post-build add_custom_command(TARGET apvlv POST_BUILD COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/share COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/share ${CMAKE_BINARY_DIR}/share ) endif() # Translation setup find_program(LUPDATE lupdate HINTS "${_qt_bin_dir}") find_program(LRELEASE lrelease HINTS "${_qt_bin_dir}") if(LUPDATE STREQUAL "LUPDATE-NOTFOUND") find_program(LUPDATE lupdate-qt6 HINTS "${_qt_bin_dir}") find_program(LRELEASE lrelease-qt6 HINTS "${_qt_bin_dir}") endif() file(GLOB cppfiles *.cc) add_custom_target(apvlv_lupdate COMMAND ${LUPDATE} -locations none -target-language zh_CN ${cppfiles} -ts ${CMAKE_SOURCE_DIR}/zh_CN.ts ) add_custom_target(apvlv_lrelease COMMAND ${LRELEASE} ${CMAKE_SOURCE_DIR}/zh_CN.ts -qm ${CMAKE_SOURCE_DIR}/share/doc/apvlv/translations/zh_CN.qm ) add_dependencies(apvlv_lrelease apvlv_lupdate) add_dependencies(apvlv apvlv_lrelease) # Installation install(TARGETS apvlv DESTINATION bin) ================================================ FILE: src/Engines.cmake ================================================ # Document engine configuration # Initialize engine-specific variables set(APVLV_ENGINE_HEADERS "") set(APVLV_ENGINE_SOURCES "") set(APVLV_ENGINE_LIBRARIES "") # MuPDF engine if(APVLV_WITH_MUPDF) message(STATUS "Enable MuPDF engine") list(APPEND APVLV_ENGINE_HEADERS file/ApvlvMuPdf.h) list(APPEND APVLV_ENGINE_SOURCES file/ApvlvMuPdf.cc) if(WIN32) if(MUPDF_FOUND) if(TARGET mupdf::mupdf) list(APPEND APVLV_ENGINE_LIBRARIES mupdf::mupdf) elseif(MUPDF_STATIC_LIBRARIES) list(APPEND APVLV_ENGINE_LIBRARIES ${MUPDF_STATIC_LIBRARIES}) else() list(APPEND APVLV_ENGINE_LIBRARIES mupdf) endif() else() # Fallback to direct library name list(APPEND APVLV_ENGINE_LIBRARIES mupdf) endif() else() if(MUPDF_FOUND) list(APPEND APVLV_ENGINE_LIBRARIES ${MUPDF_STATIC_LIBRARIES} -lharfbuzz) else() list(APPEND APVLV_ENGINE_LIBRARIES -lmupdf) endif() endif() endif() # Poppler engine if(APVLV_WITH_POPPLER) if(WIN32) # On Windows, Poppler is enabled by default message(STATUS "Enable Poppler engine") list(APPEND APVLV_ENGINE_HEADERS file/ApvlvPopplerPdf.h) list(APPEND APVLV_ENGINE_SOURCES file/ApvlvPopplerPdf.cc) if(POPPLER_FOUND) if(TARGET Poppler::poppler) list(APPEND APVLV_ENGINE_LIBRARIES Poppler::poppler) elseif(POPPLER_LIBRARIES) list(APPEND APVLV_ENGINE_LIBRARIES ${POPPLER_LIBRARIES}) else() list(APPEND APVLV_ENGINE_LIBRARIES poppler) endif() else() # Fallback to direct library name list(APPEND APVLV_ENGINE_LIBRARIES poppler) endif() elseif() message(STATUS "Enable Poppler engine") include_directories(${POPPLER_INCLUDE_DIRS}) list(APPEND APVLV_ENGINE_HEADERS file/ApvlvPopplerPdf.h) list(APPEND APVLV_ENGINE_SOURCES file/ApvlvPopplerPdf.cc) list(APPEND APVLV_ENGINE_LIBRARIES ${POPPLER_LIBRARIES}) endif() endif() # DjVu support if(APVLV_WITH_DJVU) message(STATUS "Enable DjVu support") list(APPEND APVLV_ENGINE_HEADERS file/ApvlvDjvu.h) list(APPEND APVLV_ENGINE_SOURCES file/ApvlvDjvu.cc) if(WIN32) if(TARGET djvulibre::djvulibre) list(APPEND APVLV_ENGINE_LIBRARIES djvulibre::djvulibre) elseif(DJVULIBRE_DIR) include_directories(${DJVULIBRE_DIR}/include) link_directories(${DJVULIBRE_DIR}/lib) list(APPEND APVLV_ENGINE_LIBRARIES djvulibre) else() # Fallback to direct library name list(APPEND APVLV_ENGINE_LIBRARIES djvulibre) endif() else() list(APPEND APVLV_ENGINE_LIBRARIES -ldjvulibre) endif() endif() # OCR support if(APVLV_WITH_OCR) message(STATUS "Enable OCR support") add_definitions(-DAPVLV_WITH_OCR) list(APPEND APVLV_ENGINE_HEADERS ApvlvOCR.h) list(APPEND APVLV_ENGINE_SOURCES ApvlvOCR.cc) if(WIN32) if(TESSERACT_FOUND) if(TARGET tesseract::tesseract) list(APPEND APVLV_ENGINE_LIBRARIES tesseract::tesseract) elseif(TESSERACT_LIBRARIES) list(APPEND APVLV_ENGINE_LIBRARIES ${TESSERACT_LIBRARIES}) else() list(APPEND APVLV_ENGINE_LIBRARIES tesseract) endif() else() # Fallback to direct library name list(APPEND APVLV_ENGINE_LIBRARIES tesseract) endif() else() if(TESSERACT_FOUND) list(APPEND APVLV_ENGINE_LIBRARIES ${TESSERACT_LIBRARIES}) endif() endif() endif() ================================================ FILE: src/Sources.cmake ================================================ # Source files configuration # Core headers set(HEADERS ApvlvCmds.h ApvlvFrame.h ApvlvFile.h ApvlvFileIndex.h ApvlvFileWidget.h ApvlvWidget.h ApvlvWeb.h ApvlvInfo.h ApvlvParams.h ApvlvUtil.h ApvlvView.h ApvlvWindow.h ApvlvCompletion.h ApvlvDirectory.h ApvlvLab.h ApvlvLog.h ApvlvSearch.h ApvlvSearchDialog.h ApvlvDired.h ApvlvQueue.h ApvlvImageWidget.h ApvlvWebViewWidget.h ApvlvEditor.h ApvlvNote.h ApvlvNoteWidget.h ApvlvMarkdown.h file/ApvlvHtm.h file/ApvlvImage.h file/ApvlvQtPdf.h file/ApvlvEpub.h file/ApvlvFb2.h file/ApvlvTxt.h ) # Core sources set(SOURCES ApvlvCmds.cc ApvlvFrame.cc ApvlvFile.cc ApvlvFileIndex.cc ApvlvFileWidget.cc ApvlvWidget.cc ApvlvWeb.cc ApvlvInfo.cc ApvlvParams.cc ApvlvUtil.cc ApvlvView.cc ApvlvWindow.cc ApvlvCompletion.cc ApvlvDirectory.cc ApvlvLab.cc ApvlvLog.cc ApvlvSearch.cc ApvlvSearchDialog.cc ApvlvDired.cc ApvlvQueue.cc ApvlvImageWidget.cc ApvlvWebViewWidget.cc ApvlvEditor.cc ApvlvNote.cc ApvlvNoteWidget.cc ApvlvMarkdown.cc file/ApvlvHtm.cc file/ApvlvImage.cc file/ApvlvQtPdf.cc file/ApvlvEpub.cc file/ApvlvFb2.cc file/ApvlvTxt.cc main.cc ) ================================================ FILE: src/WindowsEngines.cmake ================================================ # Windows-specific document engines if(WIN32 AND APVLV_WITH_OFFICE) message(STATUS "Enable MSOffice as office file engine") find_package(Qt6AxContainer REQUIRED) include_directories(${Qt6AxContainer_INCLUDE_DIRS}) list(APVLV_ENGINE_LIBRARIES ${Qt6AxContainer_LIBRARIES} Shlwapi.lib) list(APPEND APVLV_ENGINE_HEADERS file/ApvlvAxOffice.h) list(APPEND APVLV_ENGINE_SOURCES file/ApvlvAxOffice.cc) elseif(NOT WIN32 AND APVLV_WITH_OFFICE) message(STATUS "Enable libreOffice as office file engine") list(APPEND APVLV_ENGINE_LIBRARIES -llibreofficekitgtk) add_definitions(-DLIBO_INTERNAL_ONLY=1) list(APPEND APVLV_ENGINE_HEADERS file/ApvlvLibreOffice.h) list(APPEND APVLV_ENGINE_SOURCES file/ApvlvLibreOffice.cc) endif() ================================================ FILE: src/config.h.in ================================================ #ifndef APVLV_CONFIG_H #define APVLV_CONFIG_H // Package information #define PACKAGE_NAME "@PACKAGE_NAME@" #define PACKAGE_VERSION "@PACKAGE_VERSION@" #define PACKAGE_BUGREPORT "@PACKAGE_BUGREPORT@" #define RELEASE "@RELEASE@" // System paths #define SYSCONFDIR "@SYSCONFDIR@" // Feature flags #cmakedefine APVLV_WITH_OCR #cmakedefine APVLV_WITH_MUPDF #cmakedefine APVLV_WITH_POPPLER #cmakedefine APVLV_WITH_DJVU #cmakedefine APVLV_WITH_OFFICE #endif // APVLV_CONFIG_H ================================================ FILE: src/file/ApvlvAxOffice.cc ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvOffice.cc * * Author: Alf */ #include #include #include #include #include #include #include "ApvlvAxOffice.h" #include "ApvlvUtil.h" #include "ApvlvWebViewWidget.h" namespace apvlv { using namespace std; FILE_TYPE_DEFINITION ("MSOffice", ApvlvOfficeWord, { ".doc", ".docx" }); FILE_TYPE_DEFINITION ("MSOffice", ApvlvPowerPoint, { ".ppt", ".pptx" }); FILE_TYPE_DEFINITION ("MSOffice", ApvlvExcel, { ".xls", ".xlsx" }); bool ApvlvOfficeWord::load (const string &filename) { auto qname = QString::fromLocal8Bit (filename); mApp = new QAxWidget ("Word.Application"); mApp->setProperty ("Visible", false); mDocs = mApp->querySubObject ("Documents"); mDoc = mDocs->querySubObject ( "OpenNoRepairDialog(const QString&, bool, bool, bool)", qname, false, true, false); if (mDoc == nullptr) { mApp->dynamicCall ("Quit()"); delete mApp; return false; } return true; } int ApvlvOfficeWord::sum () { auto content = mDoc->querySubObject ("Content"); auto pages = content->dynamicCall ("Information(wdNumberOfPagesInDocument)"); return pages.toInt (); } SizeF ApvlvOfficeWord::pageSizeF (int pn, int rot) { auto pnstr = QString ("Pages(%1)").arg (pn + 1); auto win = mDoc->querySubObject ("ActiveWindow"); auto pane = win->querySubObject ("ActivePane"); auto page = pane->querySubObject (pnstr.toStdString ().c_str ()); auto width = page->property ("Width"); auto height = page->property ("Height"); return { width.toDouble (), height.toDouble () }; } bool ApvlvOfficeWord::pageText (int pn, const Rectangle &rect, string &text) { auto content = mDoc->querySubObject ("Content"); return false; } bool ApvlvOfficeWord::pageRenderToImage (int pn, double zm, int rot, QImage *pix) { char szFormatName[1024]; const char *lpFormatName; static UINT auPriorityList[] = { CF_TEXT, CF_BITMAP }; auto gf = GetPriorityClipboardFormat (auPriorityList, 2); auto pnstr = QString ("Pages(%1)").arg (pn + 1); auto win = mDoc->querySubObject ("ActiveWindow"); auto pane = win->querySubObject ("ActivePane"); auto page = pane->querySubObject (pnstr.toStdString ().c_str ()); auto selection = mApp->querySubObject ("Selection"); selection->dynamicCall ("GoTo(int, int, int, const QVariant&)", 1, 1, 1, page->property ("Start")); selection->dynamicCall ("MoveDown(int, int, int)", 5, 1, 0); selection->dynamicCall ("EndKey(int, int)", 6, 1); selection->dynamicCall ("CopyAsPicture()"); Sleep (1000); auto clip = QApplication::clipboard (); auto mime = clip->mimeData (); for (auto const &t : mime->formats ()) { qDebug () << "clipboard contains: " << t; } // HWND hWnd = (HWND)mApp->winId (); if (OpenClipboard (NULL) == FALSE) { qDebug ("open clipboard error: %d\n", GetLastError ()); return false; } auto uFormat = EnumClipboardFormats (0); while (uFormat) { if (GetClipboardFormatNameA (uFormat, szFormatName, sizeof (szFormatName))) lpFormatName = szFormatName; else lpFormatName = "(unknown)"; qDebug ("get file: %s\n", lpFormatName); uFormat = EnumClipboardFormats (uFormat); } if (IsClipboardFormatAvailable (CF_BITMAP) == FALSE) { qDebug ("no picture: %d\n", GetLastError ()); CloseClipboard (); return false; } HBITMAP hBitmap = static_cast (GetClipboardData (CF_BITMAP)); if (hBitmap == nullptr) { qDebug ("no image: %d\n", GetLastError ()); CloseClipboard (); return false; } IPicture *iPicture = nullptr; auto hr = OleCreatePictureIndirect (reinterpret_cast (hBitmap), IID_IPicture, TRUE, (void **)&iPicture); if (FAILED (hr)) { qDebug ("failed create: %d\n", GetLastError ()); CloseClipboard (); return false; } LPSTREAM stream = NULL; hr = SHCreateStreamOnFileA ("z:\\a.bmp", STGM_CREATE | STGM_WRITE, &stream); if (FAILED (hr)) { iPicture->Release (); CloseClipboard (); return false; } hr = iPicture->SaveAsFile (stream, FALSE, NULL); iPicture->Release (); CloseClipboard (); if (FAILED (hr)) { qDebug ("save failed\n"); return false; } return true; } bool ApvlvOfficeWord::pageRenderToWebView (int pn, double zm, int rot, WebView *webview) { webview->setZoomFactor (zm); QUrl url = QString ("apvlv:///%1").arg (pn); webview->load (url); return true; } optional ApvlvOfficeWord::pathContent (const string &path) { auto pn = QString::fromLocal8Bit (path).toInt (); auto pageRange = mDoc->querySubObject ( "GoTo(int, int, int, const QVariant&)", 1, 1, pn + 1); auto endRange = mDoc->querySubObject ( "GoTo(int, int, int, const QVariant&)", 1, 1, pn + 2); if (pageRange && endRange) { int endPosition; if (endRange->property ("Start").toInt () == 1) { auto content = mDoc->querySubObject ("Content"); endPosition = content->property ("End").toInt (); delete content; } else { endPosition = endRange->property ("Start").toInt () - 1; } pageRange->setProperty ("End", endPosition); pageRange->dynamicCall ("Copy()"); } else { qWarning () << "Failed to get page range"; return nullptr; } auto clip = QApplication::clipboard (); auto mime = clip->mimeData (); return QByteArray::fromStdString (mime->html ().toStdString ()); } bool ApvlvPowerPoint::load (const string &filename) { auto qname = QString::fromLocal8Bit (filename); mApp = new QAxWidget ("PowerPoint.Application"); mApp->setProperty ("Visible", false); mDocs = mApp->querySubObject ("Presentations"); mDoc = mDocs->querySubObject ("Open(const QString&, bool, bool, bool)", qname, true, false, false); if (mDoc == nullptr) { mApp->dynamicCall ("Quit()"); delete mApp; return false; } return true; } int ApvlvPowerPoint::sum () { auto slides = mDoc->querySubObject ("Slides"); auto count = slides->property ("Count"); return count.toInt (); } SizeF ApvlvPowerPoint::pageSizeF (int pn, int rot) { auto page = mDoc->querySubObject ("PageSetup"); auto width = page->property ("SlideWidth"); auto height = page->property ("SlideHeight"); return { width.toDouble (), height.toDouble () }; } bool ApvlvPowerPoint::pageText (int pn, const Rectangle &rect, string &text) { auto content = mDoc->querySubObject ("Content"); return false; } bool ApvlvPowerPoint::pageRenderToImage (int pn, double zm, int rot, QImage *pix) { auto slides = mDoc->querySubObject ("Slides"); auto slide = slides->querySubObject ("Item(int)", pn + 1); auto temppath = QString ("%1/apvlv_%2.png").arg (QDir::tempPath ()).arg (rand ()); temppath.replace ("/", "\\"); slide->dynamicCall ("Export(const QString &, const QString &)", temppath, QString ("PNG")); if (QFile::exists (temppath) == false) { return false; } *pix = QImage (temppath); QFile::remove (temppath); return true; } ExcelWidget * ApvlvExcel::getWidget () { auto wid = new ExcelWidget (); wid->setFile (this); return wid; } void ExcelWidget::setFile (File *file) { mFile = file; auto qname = QString::fromLocal8Bit (mFile->getFilename ()); mAxWidget.setControl (qname); } void ExcelWidget::showPage (int p, double s) { auto sheets = mAxWidget.querySubObject ("Sheets"); auto sheet = sheets->querySubObject ("Item(int)", p + 1); sheet->dynamicCall ("Activate()"); mPageNumber = p; mScrollValue = s; } void ExcelWidget::showPage (int p, const string &anchor) { showPage (p, 0.0); mPageNumber = p; mScrollValue = 0; } bool ApvlvExcel::load (const string &filename) { auto qname = QString::fromLocal8Bit (filename); mApp = new QAxWidget ("Excel.Workbook"); mApp->setProperty ("Visible", false); mApp->setProperty ("ReadOnly", true); mApp->setControl (qname); mDoc = nullptr; return true; } int ApvlvExcel::sum () { auto sheets = mApp->querySubObject ("Sheets"); auto count = sheets->property ("Count"); return count.toInt (); } bool ApvlvExcel::pageText (int pn, const Rectangle &rect, string &text) { return false; // auto content = mDoc->querySubObject ("Content"); } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/file/ApvlvAxOffice.h ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvOffice.h * * Author: Alf */ #ifndef _APVLV_AXOFFICE_H_ #define _APVLV_AXOFFICE_H_ #include #include "ApvlvFile.h" #include "ApvlvFileWidget.h" namespace apvlv { class AxOffice { public: virtual ~AxOffice () { if (mDoc) mDoc->dynamicCall ("Close()"); if (mApp) mApp->dynamicCall ("Quit()"); delete mDoc; delete mApp; } protected: QAxWidget *mApp; QAxObject *mDocs; QAxObject *mDoc; }; class ApvlvOfficeWord : public File, public AxOffice { FILE_TYPE_DECLARATION (ApvlvOfficeWord); public: bool load (const std::string &filename) override; int sum () override; SizeF pageSizeF (int page, int rot) override; bool pageText (int pn, const Rectangle &rect, std::string &text) override; bool pageRenderToImage (int pn, double zm, int rot, QImage *pix) override; bool pageRenderToWebView (int pn, double zm, int rot, WebView *webview) override; std::optional pathContent (const std::string &path) override; }; class ApvlvPowerPoint : public File, public AxOffice { FILE_TYPE_DECLARATION (ApvlvPowerPoint); public: bool load (const std::string &filename) override; int sum () override; SizeF pageSizeF (int page, int rot) override; bool pageText (int pn, const Rectangle &rect, std::string &text) override; bool pageRenderToImage (int pn, double zm, int rot, QImage *pix) override; }; class ExcelWidget : public FileWidget { public: ExcelWidget () { mAxWidget.setProperty ("Visible", true); mAxWidget.setProperty ("ReadOnly", true); } [[nodiscard]] QWidget * widget () override { return &mAxWidget; } void setFile (File *file) override; void showPage (int, double s) override; void showPage (int, const std::string &anchor) override; private: QAxWidget mAxWidget{ "Excel.Workbook" }; }; class ApvlvExcel : public File, public AxOffice { FILE_TYPE_DECLARATION (ApvlvExcel); public: bool load (const std::string &filename) override; [[nodiscard]] virtual DISPLAY_TYPE getDisplayType () const override { return DISPLAY_TYPE::CUSTOM; } ExcelWidget *getWidget () override; int sum () override; bool pageText (int pn, const Rectangle &rect, std::string &text) override; }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/file/ApvlvDjvu.cc ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvDjvu.cc * * Author: Alf */ #include #include "ApvlvDjvu.h" #include "ApvlvUtil.h" namespace apvlv { using namespace std; void handleDdjvuMessages (ddjvu_context_t *ctx, int wait) { const ddjvu_message_t *msg; if (wait) ddjvu_message_wait (ctx); while ((msg = ddjvu_message_peek (ctx))) { qDebug () << "tag: " << msg->m_any.tag; switch (msg->m_any.tag) { case DDJVU_ERROR: break; case DDJVU_INFO: break; case DDJVU_PAGEINFO: break; default: break; } ddjvu_message_pop (ctx); } } FILE_TYPE_DEFINITION ("djvulibre", ApvlvDJVU, { ".djv", ".djvu" }); bool ApvlvDJVU::load (const string &filename) { mContext = ddjvu_context_create ("apvlv"); if (mContext == nullptr) { qCritical () << "djvu context error"; return false; } mDoc = ddjvu_document_create_by_filename (mContext, filename.c_str (), false); if (mDoc == nullptr) { qCritical ("djvu create document error"); ddjvu_context_release (mContext); mContext = nullptr; return false; } if (ddjvu_document_get_type (mDoc) != DDJVU_DOCTYPE_SINGLEPAGE) { qCritical () << "djvu type: " << ddjvu_document_get_type (mDoc); ddjvu_document_release (mDoc); mDoc = nullptr; ddjvu_context_release (mContext); mContext = nullptr; return false; } return true; } ApvlvDJVU::~ApvlvDJVU () { if (mContext) { ddjvu_context_release (mContext); } if (mDoc) { ddjvu_document_release (mDoc); } } SizeF ApvlvDJVU::pageSizeF (int pn, int rot) { ddjvu_status_t t; ddjvu_pageinfo_t info; while ((t = ddjvu_document_get_pageinfo (mDoc, 0, &info)) < DDJVU_JOB_OK) { handleDdjvuMessages (mContext, true); } SizeF sizef{ 0.0f, 0.0f }; if (t == DDJVU_JOB_OK) { sizef.width = static_cast (info.width); sizef.height = static_cast (info.height); } return sizef; } int ApvlvDJVU::sum () { return mDoc ? ddjvu_document_get_pagenum (mDoc) : 0; } bool ApvlvDJVU::pageRenderToImage (int pn, double zm, int rot, QImage *pix) { ddjvu_page_t *tpage; if ((tpage = ddjvu_page_create_by_pageno (mDoc, pn)) == nullptr) { qDebug () << "no this page: " << pn; return false; } auto size = pageSizeF (pn, rot); auto dx = size.width * zm; auto dy = size.height * zm; auto ix = static_cast (dx); auto iy = static_cast (dy); ddjvu_rect_t prect = { 0, 0, static_cast (ix), static_cast (iy) }; ddjvu_rect_t rrect = { 0, 0, static_cast (ix), static_cast (iy) }; ddjvu_format_t *format = ddjvu_format_create (DDJVU_FORMAT_RGB24, 0, nullptr); ddjvu_format_set_row_order (format, true); auto psize = 3 * ix * iy; auto buffer = make_unique (psize); int retry = 0; while (retry <= 20 && ddjvu_page_render (tpage, DDJVU_RENDER_COLOR, &prect, &rrect, format, 3 * ix, buffer.get ()) == false) { this_thread::sleep_for (50ms); ++retry; qDebug () << "fender failed, retry " << retry; } auto image = QImage (ix, iy, QImage::Format_RGB888); for (auto y = 0; y < iy; ++y) { for (auto x = 0; x < ix; ++x) { auto rgb = buffer.get () + 3 * (x + y * ix); image.setPixel (x, y, qRgb (rgb[0], rgb[1], rgb[2])); } } *pix = std::move (image); return true; } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/file/ApvlvDjvu.h ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvDjvu.h * * Author: Alf */ #ifndef _APVLV_DJVU_H_ #define _APVLV_DJVU_H_ #include #include "ApvlvFile.h" namespace apvlv { class ApvlvDJVU : public File { FILE_TYPE_DECLARATION (ApvlvDJVU); public: bool load (const std::string &filename) override; ~ApvlvDJVU () override; SizeF pageSizeF (int page, int rot) override; int sum () override; bool pageRenderToImage (int pn, double zm, int rot, QImage *img) override; private: ddjvu_context_t *mContext; ddjvu_document_t *mDoc; }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/file/ApvlvEpub.cc ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvHtm.cc * * Author: Alf */ #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #include #pragma GCC diagnostic pop #include "ApvlvEpub.h" #include "ApvlvUtil.h" #include "ApvlvWebViewWidget.h" namespace apvlv { FILE_TYPE_DEFINITION ("Web", ApvlvEPUB, { ".epub" }); using namespace std; bool ApvlvEPUB::load (const string &filename) { mQuaZip = make_unique (QString::fromLocal8Bit (filename)); if (mQuaZip->open (QuaZip::mdUnzip) == false) { return false; } auto filenames = mQuaZip->getFileNameList (); if (!filenames.contains ("META-INF/container.xml")) { return false; } auto optcontainer = getZipFileContents ("META-INF/container.xml"); if (!optcontainer) { return false; } string contentfile = containerGetContentfile (optcontainer->constData (), optcontainer->length ()); if (contentfile.empty ()) { return false; } if (contentGetMedia (contentfile) == false) { return false; } ncxSetIndex (idSrcs["ncx"]); return true; } ApvlvEPUB::~ApvlvEPUB () { mQuaZip->close (); } int ApvlvEPUB::sum () { return int (mPages.size ()); } bool ApvlvEPUB::pageRenderToWebView (int pn, double zm, int rot, WebView *webview) { webview->setZoomFactor (zm); QUrl epuburi = QString ("apvlv:///") + QString::fromLocal8Bit (mPages[pn]); webview->load (epuburi); return true; } unique_ptr ApvlvEPUB::pageSearch (int pn, const char *s) { auto qpath = QString::fromLocal8Bit (mPages[pn]); auto content = getZipFileContents (qpath); auto html = content->toStdString (); auto pos = html.find (s); if (pos == string::npos) return nullptr; auto wordlist = make_unique (); do { WordRectangle word{}; word.word = s; wordlist->push_back (word); pos = html.find (s, pos + 1); } while (pos != string::npos); return wordlist; } optional ApvlvEPUB::pathContent (const string &path) { auto optcontent = getZipFileContents (QString::fromLocal8Bit (path)); return optcontent; } optional ApvlvEPUB::getZipFileContents (const QString &name) { if (mQuaZip->setCurrentFile (name) == false) return nullopt; auto zipfile = make_unique (mQuaZip.get ()); zipfile->open (QIODevice::ReadOnly); auto qarray = zipfile->readAll (); zipfile->close (); return qarray; } string ApvlvEPUB::containerGetContentfile (const char *container, int len) { vector names{ "container", "rootfiles", "rootfile" }; return xmlContentGetAttributeValue (container, len, names, "full-path"); } bool ApvlvEPUB::contentGetMedia (const string &contentfile) { string cover_id = "cover"; auto optcontent = getZipFileContents (QString::fromLocal8Bit (contentfile)); if (!optcontent) { return false; } vector metas{ "package", "metadata" }; auto optcover = xmlContentGetElement (optcontent->constData (), optcontent->length (), metas); if (optcover) { auto xml = optcover->get (); while (!xml->atEnd () && !(xml->isEndElement () && xml->name ().toString () == "metadata")) { xml->readNext (); if (xml->isStartElement () && xml->name ().toString () == "meta") { auto attrs = xml->attributes (); for (auto const &attr : attrs) { if (attr.name ().toString () == "name" && attr.value ().toString () == "cover") { cover_id = xml->attributes () .value ("content") .toString () .toStdString (); } } } } } vector items = { "package", "manifest", "item" }; auto optxml = xmlContentGetElement (optcontent->constData (), optcontent->length (), items); if (!optxml) return false; auto xml = optxml->get (); while (!xml->atEnd () && xml->name ().toString () == "item") { if (xml->isStartElement ()) { string href = xmlStreamGetAttributeValue (xml, "href"); if (href.empty ()) { xml->readNextStartElement (); continue; } if (contentfile.rfind ('/') != string::npos) { string dirname = contentfile.substr (0, contentfile.rfind ('/')); href = dirname + "/" + href; } string id = xmlStreamGetAttributeValue (xml, "id"); string type = xmlStreamGetAttributeValue (xml, "media-type"); if (id == cover_id) { idSrcs["cover"] = href; srcMimeTypes[href] = type; } else { idSrcs[id] = href; srcMimeTypes[href] = type; } } xml->readNextStartElement (); } vector names{ "package", "spine", "itemref" }; optxml = xmlContentGetElement (optcontent->constData (), optcontent->length (), names); if (!optxml) return false; xml = optxml->get (); while (!xml->atEnd () && xml->name ().toString () == "itemref") { if (xml->isStartElement ()) { string id = xmlStreamGetAttributeValue (xml, "idref"); mPages.push_back (idSrcs[id]); srcPages[idSrcs[id]] = static_cast (mPages.size () - 1); } xml->readNextStartElement (); } return true; } bool ApvlvEPUB::ncxSetIndex (const string &ncxfile) { auto opttoc = getZipFileContents (QString::fromLocal8Bit (ncxfile)); if (!opttoc) { return false; } vector names{ "ncx", "navMap" }; auto optxml = xmlContentGetElement (opttoc->constData (), opttoc->length (), names); if (!optxml) { return false; } mIndex = { "__cover__", 0, getFilename (), FileIndexType::FILE }; auto xml = optxml->get (); ncxNodeSetIndex (xml, "navMap", ncxfile, mIndex); return true; } void ApvlvEPUB::ncxNodeSetIndex (QXmlStreamReader *xml, const string &element_name, const string &ncxfile, FileIndex &index) { while (!xml->atEnd () && !(xml->isEndElement () && xml->name ().toString ().toStdString () == element_name)) { if (xml->isStartElement ()) { if (xml->name ().toString () == "navLabel") { while (!(xml->isEndElement () && xml->name ().toString () == "navLabel")) { xml->readNextStartElement (); if (xml->name ().toString () == "text") { auto text = xml->readElementText ( QXmlStreamReader::ReadElementTextBehaviour:: SkipChildElements); index.title = text.toStdString (); break; } } xml->readNextStartElement (); } if (xml->name ().toString () == "content") { string srcstr = xmlStreamGetAttributeValue (xml, "src"); if (srcstr.empty ()) continue; if (ncxfile.find ('/') != string::npos) { auto ncxdir = filesystem::path (ncxfile).parent_path ().string (); srcstr = string (ncxdir) + '/' + srcstr; } index.path = srcstr; auto href = srcstr; if (srcstr.find ('#') != string::npos) { index.anchor = srcstr.substr (srcstr.find ('#')); href = srcstr.substr (0, srcstr.find ('#')); } for (decltype (mPages.size ()) ind = 0; ind < mPages.size (); ++ind) { if (mPages[ind] == href) { index.page = int (ind); break; } } xml->readNextStartElement (); } if (xml->name ().toString () == "navPoint") { xml->readNextStartElement (); auto childindex = FileIndex{}; ncxNodeSetIndex (xml, "navPoint", ncxfile, childindex); index.mChildrenIndex.emplace_back (childindex); } } xml->readNextStartElement (); } } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/file/ApvlvEpub.h ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvEpub.h * * Author: Alf */ #ifndef _APVLV_EPUB_H_ #define _APVLV_EPUB_H_ #include #include #include #include #include #include "ApvlvFile.h" namespace apvlv { class ApvlvEPUB : public File { FILE_TYPE_DECLARATION (ApvlvEPUB); public: bool load (const std::string &filename) override; ~ApvlvEPUB () override; int sum () override; bool pageRenderToWebView (int pn, double zm, int rot, WebView *widget) override; std::unique_ptr pageSearch (int pn, const char *s) override; std::optional pathContent (const std::string &path) override; private: std::optional getZipFileContents (const QString &name); static std::string containerGetContentfile (const char *container, int len); bool contentGetMedia (const std::string &contentfile); bool ncxSetIndex (const std::string &ncxfile); void ncxNodeSetIndex (QXmlStreamReader *xml, const std::string &element_name, const std::string &ncxfile, FileIndex &index); std::unique_ptr mQuaZip; std::map idSrcs; }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/file/ApvlvFb2.cc ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvFb2.cc * * Author: Alf */ #include #include #include #include "ApvlvFb2.h" #include "ApvlvUtil.h" #include "ApvlvWebViewWidget.h" namespace apvlv { using namespace std; const string stylesheet_content = ".block_c {\n" " display: block;\n" " font-size: 2.5em;\n" " font-weight: normal;\n" " line-height: 33.6pt;\n" " text-align: center;\n" " text-indent: 0;\n" " margin: 17pt 0;\n" " padding: 0;\n" "}\n" ".block_ {\n" " display: block;\n" " font-size: 1.5em;\n" " font-weight: normal;\n" " line-height: 33.6pt;\n" " text-align: justify;\n" " text-indent: 0;\n" " margin: 17pt 0;\n" " padding: 0;\n" "}\n" ".block_1 {\n" " display: block;\n" " line-height: 1.2;\n" " text-align: justify;\n" " margin: 0 0 7pt;\n" " padding: 0;\n" "}\n"; const string title_template = "\n" "\n" " \n" " \n" " \n" " \n" " \n" " \n" "
\n" "
\n" "
\n" "
\n" " %s\n" " \n" "\n"; const string section_template = "\n" "\n" " \n" " \n" " \n" " \n" " \n" " \n" " %s\n" " \n" "\n"; FILE_TYPE_DEFINITION ("Web", ApvlvFB2, { ".fb2" }); bool ApvlvFB2::load (const string &filename) { QFile file (QString::fromLocal8Bit (filename)); if (!file.open (QFile::ReadOnly | QFile::Text)) { return false; } auto bytes = file.readAll (); parseFb2 (bytes.constData (), bytes.length ()); return true; } bool ApvlvFB2::parseFb2 (const char *content, size_t length) { parseDescription (content, length); parseBinary (content, length); parseBody (content, length); generateIndex (); return true; } bool ApvlvFB2::parseDescription (const char *content, size_t length) { vector keys{ "FictionBook", "description", "title-info", "coverpage", "image" }; auto value = xmlContentGetAttributeValue (content, length, keys, "href"); mCoverHref = value; return true; } bool ApvlvFB2::parseBody (const char *content, size_t length) { vector keys = { "FictionBook", "body" }; auto optxml = xmlContentGetElement (content, length, keys); if (!optxml) return false; auto xml = optxml->get (); while (!xml->atEnd () && !(xml->isEndElement () && xml->name ().toString () == "body")) { if (xml->isStartElement () && xml->name () == QString ("title")) { stringstream ss; while (!xml->atEnd () && !(xml->isEndElement () && xml->name ().toString () == "title")) { if (xml->isStartElement ()) { if (xml->name () == QString ("empty-line")) { ss << "
"; } else if (xml->name () == QString ("p")) { ss << "

"; auto xmltext = xml->readElementText ().trimmed (); ss << xmltext.toStdString (); ss << "

"; ss << "
"; } } xml->readNext (); } auto htmlstr = templateBuild (title_template, "%s", ss.str ()); appendTitle (htmlstr, "application/xhtml+xml"); } else if (xml->isStartElement () && xml->name ().toString () == "section") { stringstream ss; string title; while (!xml->atEnd () && !(xml->isEndElement () && xml->name ().toString () == "section")) { if (xml->isStartElement ()) { if (xml->name ().toString () == "title") { auto xmltext = xml->readElementText ( QXmlStreamReader::IncludeChildElements) .trimmed (); title = xmltext.toStdString (); ss << "

"; ss << title; ss << "

"; ss << "
"; } else if (xml->name ().toString () == "p") { ss << "

"; auto xmltext = xml->readElementText ().trimmed (); ss << xmltext.toStdString (); ss << "

"; } } xml->readNext (); } auto htmlstr = templateBuild (section_template, "%s", ss.str ()); appendSection (title, htmlstr, "application/xhtml+xml"); } xml->readNext (); } return true; } bool ApvlvFB2::parseBinary (const char *content, size_t length) { string idstr; vector keys = { "FictionBook", "binary" }; auto optxml = xmlContentGetElement (content, length, keys); if (!optxml) return false; auto xml = optxml->get (); idstr = xmlStreamGetAttributeValue (xml, "id"); if (mCoverHref.empty () || idstr == mCoverHref.substr (1)) { string mimetype = xmlStreamGetAttributeValue (xml, "content-type"); auto contents = xml->readElementText ().toStdString (); QByteArray b64contents{ contents.c_str (), (qsizetype)contents.length () }; auto bytes = QByteArray::fromBase64 (b64contents); auto section = bytes.toStdString (); appendCoverpage (section, mimetype); } return true; } void ApvlvFB2::appendCoverpage (const string §ion, const string &mime) { appendSection ("__cover__", section, mime); } void ApvlvFB2::appendTitle (const string §ion, const string &mime) { appendSection ("TITLE", section, mime); } void ApvlvFB2::appendSection (const string &title, const string §ion, const string &mime) { stringstream uri; uri << mPages.size (); appendPage (uri.str (), title, section, mime); } void ApvlvFB2::appendPage (const string &uri, const string &title, const string §ion, const string &mime) { srcPages[uri] = (int)mPages.size (); mPages.push_back (uri); titleSections.insert ({ uri, { title, section } }); srcMimeTypes.insert ({ uri, mime }); } bool ApvlvFB2::generateIndex () { stringstream pagenum; mIndex = { "", 0, getFilename (), FileIndexType::FILE }; for (int ind = 0; ind < (int)mPages.size (); ++ind) { pagenum << ind; if (mPages[ind] == "__cover__") continue; auto title = titleSections[mPages[ind]].first; auto chap = FileIndex (title, ind, pagenum.str (), FileIndexType::PAGE); mIndex.mChildrenIndex.emplace_back (chap); } return true; } int ApvlvFB2::sum () { return (int)mPages.size (); } bool ApvlvFB2::pageRenderToWebView (int pn, double zm, int rot, WebView *webview) { webview->setZoomFactor (zm); QUrl url = QString ("apvlv:///") + QString::number (pn); webview->load (url); return true; } optional ApvlvFB2::pathContent (const string &uri) { if (uri == "stylesheet.css") { auto byte_array = QByteArray::fromStdString (stylesheet_content); return byte_array; } auto byte_array = QByteArray::fromStdString (titleSections[uri].second); return byte_array; } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/file/ApvlvFb2.h ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvFb2.h * * Author: Alf */ #ifndef _APVLV_FB2_H_ #define _APVLV_FB2_H_ #include #include "ApvlvFile.h" namespace apvlv { class ApvlvFB2 : public File { FILE_TYPE_DECLARATION (ApvlvFB2); public: bool load (const std::string &filename) override; ~ApvlvFB2 () override = default; int sum () override; bool pageRenderToWebView (int pn, double zm, int rot, WebView *webview) override; std::optional pathContent (const std::string &path) override; private: std::map> titleSections; std::string mCoverHref; bool parseFb2 (const char *content, size_t length); bool parseDescription (const char *content, size_t length); bool parseBody (const char *content, size_t length); bool parseBinary (const char *content, size_t length); void appendCoverpage (const std::string §ion, const std::string &mime); void appendTitle (const std::string §ion, const std::string &mime); void appendSection (const std::string &title, const std::string §ion, const std::string &mime); void appendPage (const std::string &uri, const std::string &title, const std::string §ion, const std::string &mime); bool generateIndex (); }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/file/ApvlvHtm.cc ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvHtm.cc * * Author: Alf */ #include "ApvlvHtm.h" #include "ApvlvWebViewWidget.h" namespace apvlv { FILE_TYPE_DEFINITION ("Web", ApvlvHTML, { ".htm", ".html" }); using namespace std; bool ApvlvHTML::load (const string &filename) { mUrl.setScheme ("file"); mUrl.setPath (QString::fromLocal8Bit (filename)); return true; } bool ApvlvHTML::pageRenderToWebView (int pn, double zm, int rot, WebView *webview) { webview->setZoomFactor (zm); webview->load (mUrl); return true; } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/file/ApvlvHtm.h ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvHtm.h * * Author: Alf */ #ifndef _APVLV_HTM_H_ #define _APVLV_HTM_H_ #include #include "ApvlvFile.h" namespace apvlv { class ApvlvHTML : public File { FILE_TYPE_DECLARATION (ApvlvHTML); public: bool load (const std::string &filename) override; bool pageRenderToWebView (int pn, double zm, int rot, WebView *webview) override; protected: QUrl mUrl; }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/file/ApvlvImage.cc ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvImage.cc * * Author: Alf */ #include "ApvlvImage.h" namespace apvlv { FILE_TYPE_DEFINITION ("Web", ApvlvIMAGE, { ".png", ".jpg", ".jpeg", ".gif", ".bmp" }); } // Local Variables: // mode: c++ // End: ================================================ FILE: src/file/ApvlvImage.h ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvImage.h * * Author: Alf */ #ifndef _APVLV_IMAGE_H_ #define _APVLV_IMAGE_H_ #include "ApvlvHtm.h" namespace apvlv { class ApvlvIMAGE : public ApvlvHTML { FILE_TYPE_DECLARATION (ApvlvIMAGE); public: bool pageIsOnlyImage (int pn) override { return true; } }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/file/ApvlvLibreOffice.cc ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvOffice.cc * * Author: Alf */ #include #include #include #include #include #include "ApvlvLibreOffice.h" namespace apvlv { FILE_TYPE_DEFINITION ("libreOffice", ApvlvOFFICE, { ".doc", ".docx", ".xls", ".xlsx", "ppt", "pptx" }); using namespace std; mutex ApvlvOFFICE::mLokMutex; TokenDispatcher ApvlvOFFICE::mDispatcher{ 1, true }; unique_ptr ApvlvOFFICE::mOffice; bool ApvlvOFFICE::load (const string &filename) { auto filepath = filesystem::path (filename); if (filepath.filename ().string ().starts_with ("~")) { qWarning () << "filename: " << QString::fromLocal8Bit (filename) << " maybe a temporary file, skip."; return false; } auto token = mDispatcher.getToken (true); initLokInstance (); lock_guard lk (mLokMutex); mDoc = unique_ptr{ mOffice->documentLoad ( filename.c_str ()) }; if (mDoc == nullptr) return false; mDoc->initializeForRendering (); return true; } ApvlvOFFICE::~ApvlvOFFICE () { auto token = mDispatcher.getToken (true); lock_guard lk (mLokMutex); mDoc = nullptr; } int ApvlvOFFICE::sum () { auto token = mDispatcher.getToken (true); lock_guard lk (mLokMutex); auto num = mDoc->getParts (); return num; } bool ApvlvOFFICE::pageText (int pn, const Rectangle &rect, string &text) { auto tmpname = QString ("%1/apvlv.%2.txt") .arg (QDir::temp ().path ()) .arg (random ()); auto token = mDispatcher.getToken (false); unique_lock lk (mLokMutex); mDoc->setPart (pn); mDoc->saveAs (tmpname.toStdString ().c_str (), "txt"); lk.unlock (); token.reset (); QFile file (tmpname); if (file.open (QIODeviceBase::ReadOnly) == false) return false; auto bytes = file.readAll (); text.append (bytes.toStdString ()); file.close (); file.remove (); return true; } bool ApvlvOFFICE::pageRenderToImage (int pn, double zm, int rot, QImage *pix) { auto token = mDispatcher.getToken (true); lock_guard lk (mLokMutex); mDoc->setPart (pn); QTemporaryFile file; if (file.open ()) { mDoc->saveAs (file.fileName ().toStdString ().c_str (), "png"); *pix = QImage (file.fileName (), "png"); file.close (); } return true; } void ApvlvOFFICE::initLokInstance () { lock_guard lk (mLokMutex); if (mOffice == nullptr) { auto lok_path = ApvlvParams::instance ()->getStringOrDefault ( "lok_path", DEFAULT_LOK_PATH); mOffice = unique_ptr{ lok::lok_cpp_init (lok_path.c_str ()) }; } } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/file/ApvlvLibreOffice.h ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvOffice.h * * Author: Alf */ #ifndef _APVLV_OFFICE_H_ #define _APVLV_OFFICE_H_ #include #include #include "ApvlvFile.h" namespace apvlv { const char *const DEFAULT_LOK_PATH = "/usr/lib64/libreoffice/program"; class ApvlvOFFICE : public File { FILE_TYPE_DECLARATION (ApvlvOFFICE); public: bool load (const std::string &filename) override; ~ApvlvOFFICE () override; int sum () override; bool pageText (int pn, const Rectangle &rect, std::string &text) override; bool pageRenderToImage (int pn, double zm, int rot, QImage *pix) override; protected: std::unique_ptr mDoc; private: static std::unique_ptr mOffice; static std::mutex mLokMutex; static TokenDispatcher mDispatcher; static void initLokInstance (); }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/file/ApvlvMuPdf.cc ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvPdf.cc * * Author: Alf */ #include #include #include #include #include "ApvlvMuPdf.h" namespace apvlv { using namespace std; FILE_TYPE_DEFINITION ("MuPDF", ApvlvMuPDF, { ".pdf", ".xps", ".epub", ".mobi", ".fb2", ".cbz", ".svg", ".txt" }); ApvlvMuPDF::ApvlvMuPDF () : mDoc{ nullptr } { mContext = fz_new_context (nullptr, nullptr, FZ_STORE_UNLIMITED); fz_register_document_handlers (mContext); } ApvlvMuPDF::~ApvlvMuPDF () { fz_drop_document (mContext, mDoc); fz_drop_context (mContext); } bool ApvlvMuPDF::load (const string &filename) { mDoc = fz_open_document (mContext, filename.c_str ()); if (mDoc == nullptr) { return false; } generateIndex (); return true; } SizeF ApvlvMuPDF::pageSizeF (int pn, int rot) { auto page = fz_load_page (mContext, mDoc, pn); auto rect = fz_bound_page (mContext, page); fz_drop_page (mContext, page); SizeF sizef{ rect.x1 - rect.x0, rect.y1 - rect.y0 }; return sizef; } int ApvlvMuPDF::sum () { auto pages = fz_count_pages (mContext, mDoc); return pages; } bool ApvlvMuPDF::pageIsOnlyImage (int pn) { auto text_page = fz_new_stext_page_from_page_number (mContext, mDoc, pn, nullptr); if (text_page == nullptr) { return true; } auto only_image = (text_page->first_block == nullptr); fz_drop_stext_page (mContext, text_page); return only_image; } bool ApvlvMuPDF::pageRenderToImage (int pn, double zm, int rot, QImage *pix) { auto scale = fz_scale (static_cast (zm), static_cast (zm)); auto mat = fz_pre_rotate (scale, static_cast (rot)); auto color = fz_device_rgb (mContext); auto pixmap = fz_new_pixmap_from_page_number (mContext, mDoc, pn, mat, color, 0); if (pixmap == nullptr) return false; auto comments = mNote.getCommentsInPage (pn); pageRenderComments (pn, pixmap, comments, mat); QImage img{ pixmap->w, pixmap->h, QImage::Format_RGB32 }; for (auto y = 0; y < pixmap->h; ++y) { auto p = pixmap->samples + y * pixmap->stride; for (auto x = 0; x < pixmap->w; ++x) { QColor c{ int (p[0]), int (p[1]), int (p[2]) }; img.setPixelColor (x, y, c); p += pixmap->n; } } fz_drop_pixmap (mContext, pixmap); *pix = img; return true; } optional> ApvlvMuPDF::pageHighlight (int pn, const ApvlvPoint &pa, const ApvlvPoint &pb) { auto options = fz_stext_options{}; auto text_page = fz_new_stext_page_from_page_number (mContext, mDoc, pn, &options); auto fa = fz_point{ static_cast (pa.x), static_cast (pa.y) }; auto fb = fz_point{ static_cast (pb.x), static_cast (pb.y) }; std::array quad_array; auto quads = fz_highlight_selection ( mContext, text_page, fa, fb, quad_array.data (), quad_array.size ()); fz_drop_stext_page (mContext, text_page); if (quads == 0) return nullopt; auto rect_list = vector{}; for (auto i = 0; i < quads; ++i) { auto quad = &quad_array[i]; Rectangle r{ quad->ul.x, quad->ul.y, quad->lr.x, quad->lr.y }; rect_list.emplace_back (r); } return rect_list; } bool ApvlvMuPDF::pageText (int pn, const Rectangle &rect, string &text) { auto text_page = fz_new_stext_page_from_page_number (mContext, mDoc, pn, nullptr); auto fzrect = fz_rect{ .x0 = static_cast (rect.p1x), .y0 = static_cast (rect.p1y), .x1 = static_cast (rect.p2x), .y1 = static_cast (rect.p2y), }; text = fz_copy_rectangle (mContext, text_page, fzrect, 0); fz_drop_stext_page (mContext, text_page); return true; } unique_ptr ApvlvMuPDF::pageSearch (int pn, const char *str) { int hit; std::array quad_array; auto count = fz_search_page_number (mContext, mDoc, pn, str, &hit, quad_array.data (), quad_array.size ()); if (count == 0) return nullptr; auto list = make_unique (); for (auto i = 0; i < count; ++i) { WordRectangle rectangle; rectangle.word = str; auto quad = quad_array[i]; Rectangle rect{ quad.ul.x, quad.lr.y, quad.lr.x, quad.ul.y }; rectangle.rect_list.push_back (rect); list->push_back (rectangle); } return list; } void ApvlvMuPDF::pageRenderComments (int pn, fz_pixmap *pixmap, const vector &comments, const fz_matrix &mat) { if (comments.empty ()) return; auto dev = fz_new_draw_device (mContext, mat, pixmap); auto stroke = fz_new_stroke_state (mContext); stroke->linewidth = 0.4; fz_path *path = fz_new_path (mContext); auto scale = fz_scale (1.0, 1.0); std::array color = { 0.0, 0.0, 1.0 }; for (const auto &comment : comments) { ApvlvPoint pa{ comment.begin.x, comment.begin.y }; ApvlvPoint pb{ comment.end.x, comment.end.y }; auto rect_list = pageHighlight (pn, pa, pb); if (rect_list->empty ()) continue; for (auto const &rect : rect_list.value ()) { fz_moveto (mContext, path, static_cast (rect.p1x), static_cast (rect.p2y)); fz_lineto (mContext, path, static_cast (rect.p2x), static_cast (rect.p2y)); fz_stroke_path (mContext, dev, path, stroke, scale, fz_device_rgb (mContext), color.data (), 0.8, fz_default_color_params); } } fz_drop_path (mContext, path); fz_drop_stroke_state (mContext, stroke); fz_drop_device (mContext, dev); } void ApvlvMuPDF::generateIndex () { mIndex = { "", 0, "", FileIndexType::FILE }; fz_outline *top_toc; fz_try (mContext) top_toc = fz_load_outline (mContext, mDoc); fz_catch (mContext) { qCritical () << "load " << mFilename << " outline error"; fz_report_error (mContext); top_toc = nullptr; } if (top_toc == nullptr) return; auto toc = top_toc; while (toc != nullptr) { auto child_index = FileIndex{}; generateIndexRecursively (child_index, toc); mIndex.mChildrenIndex.push_back (child_index); toc = toc->next; } fz_drop_outline (mContext, top_toc); } void ApvlvMuPDF::generateIndexRecursively (FileIndex &index, const fz_outline *outline) { index.type = FileIndexType::PAGE; if (outline->title) index.title = outline->title; index.page = fz_page_number_from_location (mContext, mDoc, outline->page); if (outline->uri != nullptr) { index.path = outline->uri; auto pos = index.path.find ('#'); if (pos != string::npos) { index.anchor = index.path.substr (pos); index.path = index.path.substr (0, pos); } if (index.page == -1) { auto dest = fz_resolve_link (mContext, mDoc, outline->uri, nullptr, nullptr); index.page = fz_page_number_from_location (mContext, mDoc, dest); } } auto toc = outline->down; while (toc != nullptr) { auto child_index = FileIndex{}; generateIndexRecursively (child_index, toc); index.mChildrenIndex.push_back (child_index); toc = toc->next; } } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/file/ApvlvMuPdf.h ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvPdf.h * * Author: Alf */ #ifndef _APVLV_MUPDF_H_ #define _APVLV_MUPDF_H_ #include #include #include "ApvlvFile.h" namespace apvlv { class ApvlvMuPDF : public File { FILE_TYPE_DECLARATION (ApvlvMuPDF); public: ApvlvMuPDF (); ~ApvlvMuPDF () override; bool load (const std::string &filename) override; [[nodiscard]] DISPLAY_TYPE getDisplayType () const override { return DISPLAY_TYPE::IMAGE; } SizeF pageSizeF (int page, int rot) override; int sum () override; bool pageIsOnlyImage (int pn) override; bool pageRenderToImage (int pn, double zm, int rot, QImage *img) override; std::optional> pageHighlight (int pn, const ApvlvPoint &pa, const ApvlvPoint &pb) override; bool pageText (int pn, const Rectangle &rect, std::string &text) override; std::unique_ptr pageSearch (int pn, const char *str) override; private: fz_context *mContext; fz_document *mDoc; void pageRenderComments (int pn, fz_pixmap *pixmap, const std::vector &comments, const fz_matrix &mat); void generateIndex (); void generateIndexRecursively (FileIndex &index, const fz_outline *outline); }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/file/ApvlvPopplerPdf.cc ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvPdf.cc * * Author: Alf */ #include #include #include #include #include #include "ApvlvPopplerPdf.h" #include "ApvlvView.h" namespace apvlv { FILE_TYPE_DEFINITION ("poppler", ApvlvPopplerPDF, { ".pdf" }); using namespace std; using namespace Poppler; bool ApvlvPopplerPDF::load (const string &filename) { mDoc = Document::load (QString::fromLocal8Bit (filename)); if (mDoc == nullptr) { auto text = QInputDialog::getText (nullptr, "password", "input password"); auto pass = QByteArray::fromStdString (text.toStdString ()); mDoc = Document::load (QString::fromStdString (filename), pass, pass); } if (mDoc == nullptr) { return false; } generateIndex (); return true; } SizeF ApvlvPopplerPDF::pageSizeF (int pn, int rot) { auto page = mDoc->page (pn); auto qsize = page->pageSizeF (); if (rot == 0 || rot == 180) { return { qsize.width (), qsize.height () }; } else { return { qsize.height (), qsize.width () }; } } int ApvlvPopplerPDF::sum () { return mDoc ? mDoc->numPages () : 0; } unique_ptr ApvlvPopplerPDF::pageSearch (int pn, const char *str) { if (mDoc == nullptr) return nullptr; auto page = mDoc->page (pn); auto results = page->search (str); if (results.empty ()) return nullptr; auto poses = make_unique (); for (auto const &res : results) { WordRectangle wr; wr.rect_list.push_back ( { res.left (), res.top (), res.right (), res.bottom () }); poses->push_back (wr); } return poses; } bool ApvlvPopplerPDF::pageIsOnlyImage (int pn) { auto page = mDoc->page (pn); auto list = page->textList (); return list.empty (); } bool ApvlvPopplerPDF::pageRenderToImage (int pn, double zm, int rot, QImage *pix) { if (mDoc == nullptr) return false; auto xres = 72.0 * zm; auto yres = 72.0 * zm; auto prot = Poppler::Page::Rotate0; if (rot == 90) prot = Poppler::Page::Rotate90; if (rot == 180) prot = Poppler::Page::Rotate180; if (rot == 270) prot = Poppler::Page::Rotate270; auto page = mDoc->page (pn); auto size = page->pageSizeF (); auto image = page->renderToImage (xres, yres, 0, 0, size.width () * zm, size.height () * zm, prot); *pix = std::move (image); return true; } bool ApvlvPopplerPDF::generateIndex () { auto outlines = mDoc->outline (); if (outlines.empty ()) return false; mIndex = { "", 0, getFilename (), FileIndexType::FILE }; generateChildrenIndex (mIndex, outlines); return true; } void ApvlvPopplerPDF::generateChildrenIndex (FileIndex &root_index, const QVector &outlines) { for (auto const &outline : outlines) { FileIndex index{ outline.name ().toStdString (), outline.destination ()->pageNumber () - 1, "", FileIndexType::PAGE }; auto child_outlines = outline.children (); generateChildrenIndex (index, child_outlines); root_index.mChildrenIndex.emplace_back (index); } } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/file/ApvlvPopplerPdf.h ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvPdf.h * * Author: Alf */ #ifndef _APVLV_POPPLERPDF_H_ #define _APVLV_POPPLERPDF_H_ #include #include "ApvlvFile.h" namespace apvlv { class ApvlvPopplerPDF : public File { FILE_TYPE_DECLARATION (ApvlvPopplerPDF); public: bool load (const std::string &filename) override; ~ApvlvPopplerPDF () override = default; SizeF pageSizeF (int page, int rot) override; int sum () override; bool pageIsOnlyImage (int pn) override; bool pageRenderToImage (int pn, double zm, int rot, QImage *img) override; std::unique_ptr pageSearch (int pn, const char *s) override; private: bool generateIndex (); void generateChildrenIndex (FileIndex &root_index, const QVector &outlines); std::unique_ptr mDoc; }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/file/ApvlvQtPdf.cc ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvPdf.cc * * Author: Alf */ #include #include #include #include #include #include #include #include #include "ApvlvQtPdf.h" #include "ApvlvUtil.h" namespace apvlv { using namespace std; FILE_TYPE_DEFINITION ("QtPdf", ApvlvPDF, { ".pdf" }); bool ApvlvPDF::load (const string &filename) { mDoc = make_unique (); mView = nullptr; auto res = mDoc->load (QString::fromLocal8Bit (filename)); if (res == QPdfDocument::Error::IncorrectPassword) { if (QThread::currentThread () == QApplication::instance ()->thread ()) { auto text = QInputDialog::getText (nullptr, "password", "input password"); if (text.isEmpty ()) return false; auto pass = QByteArray::fromStdString (text.toStdString ()); mDoc->setPassword (pass); res = mDoc->load (QString::fromLocal8Bit (filename)); } else { qWarning () << "file: " << filename << " has password, skip !!!"; } } if (res != QPdfDocument::Error::None) { return false; } mSearchModel = make_unique (); mSearchModel->setDocument (mDoc.get ()); generateIndex (); return true; } PDFWidget * ApvlvPDF::getWidget () { auto wid = new PDFWidget (); wid->setFile (this); return wid; } SizeF ApvlvPDF::pageSizeF (int pn, int rot) { auto qsize = mDoc->pagePointSize (pn); if (rot == 0 || rot == 180) { return { qsize.width (), qsize.height () }; } else { return { qsize.height (), qsize.width () }; } } int ApvlvPDF::sum () { return mDoc ? mDoc->pageCount () : 0; } unique_ptr ApvlvPDF::pageSearch (int pn, const char *str) { if (mDoc == nullptr) return nullptr; mSearchModel->setSearchString (str); auto results = mSearchModel->resultsOnPage (pn); if (results.empty ()) return nullptr; auto poses = make_unique (); for (auto const &res : results) { WordRectangle word_rectangle; for (auto const &rect : res.rectangles ()) { word_rectangle.rect_list.push_back ( { rect.left (), rect.bottom (), rect.right (), rect.top () }); } poses->push_back (word_rectangle); } return poses; } bool ApvlvPDF::pageIsOnlyImage (int pn) { auto sel = mDoc->getAllText (pn); auto has_text = sel.isValid (); return !has_text; } bool ApvlvPDF::pageRenderToImage (int pn, double zm, int rot, QImage *pix) { if (mDoc == nullptr) return false; using enum QPdfDocumentRenderOptions::Rotation; auto sizeF = pageSizeF (pn, rot); QSize image_size{ int (sizeF.width * zm), int (sizeF.height * zm) }; auto prot = None; if (rot == 90) prot = Clockwise90; if (rot == 180) prot = Clockwise180; if (rot == 270) prot = Clockwise270; QPdfDocumentRenderOptions options{}; options.setRotation (prot); options.setScaledSize (image_size); *pix = mDoc->render (pn, image_size, options); if (auto comments = mNote.getCommentsInPage (pn); !comments.empty ()) { pageRenderComments (pn, pix, comments); } return true; } bool ApvlvPDF::pageText (int pn, const Rectangle &rect, string &text) { if (mDoc == nullptr) return false; auto selection = mDoc->getSelection (pn, { rect.p1x, rect.p1y }, { rect.p2x, rect.p2y }); text = selection.text ().toStdString (); return true; } void ApvlvPDF::pageRenderComments (int pn, QImage *img, const std::vector &comments) { auto model = make_unique (); model->setDocument (mDoc.get ()); QPainter painter (img); painter.setPen (QPen (Qt::blue, 0.4)); // TODO // search not work, need better impl for (auto const &comment : comments) { model->setSearchString (QString::fromLocal8Bit (comment.quoteText)); auto links = model->resultsOnPage (pn); if (links.empty ()) continue; for (auto const &link : links) { auto rects = link.rectangles (); auto brect = rects[0]; auto erect = rects[rects.count () - 1]; painter.drawLine (brect.x (), brect.y () + brect.height (), erect.x (), brect.y () + brect.height ()); } } painter.end (); } bool ApvlvPDF::generateIndex () { auto bookmark_model = make_unique (); bookmark_model->setDocument (mDoc.get ()); mIndex = { "", 0, getFilename (), FileIndexType::FILE }; getIndexIter (mIndex, bookmark_model.get (), QModelIndex ()); return true; } void ApvlvPDF::getIndexIter (FileIndex &file_index, const QPdfBookmarkModel *bookmark_model, const QModelIndex &parent) { for (auto row = 0; row < bookmark_model->rowCount (parent); ++row) { auto index = bookmark_model->index (row, 0, parent); auto title = bookmark_model->data (index, Qt::UserRole); auto level = bookmark_model->data (index, 257); auto page = bookmark_model->data (index, 258); auto location = bookmark_model->data (index, 259); FileIndex child_index (title.toString ().toStdString (), page.toInt (), "", FileIndexType::PAGE); if (bookmark_model->hasChildren (index)) { getIndexIter (child_index, bookmark_model, index); } file_index.mChildrenIndex.emplace_back (child_index); } } void PDFWidget::setFile (File *file) { mFile = file; auto pdf = dynamic_cast (mFile); mPdfView.setDocument (pdf->mDoc.get ()); mPdfView.setSearchModel (pdf->mSearchModel.get ()); } void PDFWidget::showPage (int p, double s) { auto nav = mPdfView.pageNavigator (); nav->jump (p, { 0, 0 }); scrollTo (s, 0.0); mPageNumber = p; mScrollValue = s; } void PDFWidget::showPage (int p, const string &anchor) { auto nav = mPdfView.pageNavigator (); nav->jump (p, { 0, 0 }); mPageNumber = p; mScrollValue = 0; } void PDFWidget::setSearchSelect (int select) { auto pdf = dynamic_cast (mFile); auto model = pdf->mSearchModel.get (); auto doc_select = 0; for (auto pn = 0; pn < mPageNumber; ++pn) { auto res = model->resultsOnPage (pn); doc_select += static_cast (res.size ()); } doc_select += select; mPdfView.setCurrentSearchResultIndex (doc_select); auto link = model->resultAtIndex (doc_select); auto scr = static_cast (link.location ().y ()); mValScrollBar->setValue (scr); qDebug ("link: %d,{%f,%f}: select: %d", link.page (), link.location ().x (), link.location ().y (), doc_select); mSearchSelect = select; } void PDFWidget::setZoomrate (double zm) { mZoomrate = zm; mPdfView.setZoomFactor (zm); } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/file/ApvlvQtPdf.h ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvPdf.h * * Author: Alf */ #ifndef _APVLV_QTPDF_H_ #define _APVLV_QTPDF_H_ #include #include #include "ApvlvFile.h" #include "ApvlvFileWidget.h" namespace apvlv { class PDFWidget : public FileWidget { public: PDFWidget () { mHalScrollBar = mPdfView.horizontalScrollBar (); mValScrollBar = mPdfView.verticalScrollBar (); } [[nodiscard]] QWidget * widget () override { return &mPdfView; } void setFile (File *file) override; void showPage (int pn, double s) override; void showPage (int pn, const std::string &anchor) override; void setSearchSelect (int select) override; void setZoomrate (double zm) override; private: QPdfView mPdfView{}; }; class ApvlvPDF : public File { FILE_TYPE_DECLARATION (ApvlvPDF); public: bool load (const std::string &filename) override; ~ApvlvPDF () override = default; [[nodiscard]] DISPLAY_TYPE getDisplayType () const override { return DISPLAY_TYPE::IMAGE; } PDFWidget *getWidget () override; SizeF pageSizeF (int page, int rot) override; int sum () override; bool pageIsOnlyImage (int pn) override; bool pageRenderToImage (int pn, double zm, int rot, QImage *img) override; bool pageText (int, const Rectangle &rect, std::string &text) override; std::unique_ptr pageSearch (int pn, const char *s) override; private: void pageRenderComments (int pn, QImage *img, const std::vector &comments); bool generateIndex (); void getIndexIter (FileIndex &file_index, const QPdfBookmarkModel *bookmark_model, const QModelIndex &parent); std::unique_ptr mDoc; std::unique_ptr mSearchModel; QWidget *mView; friend class PDFWidget; }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/file/ApvlvTxt.cc ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvTxt.cc * * Author: Alf */ #include #include #include #include "ApvlvTxt.h" #include "ApvlvWebViewWidget.h" namespace apvlv { FILE_TYPE_DEFINITION ("Web", ApvlvTXT, { ".txt", ".text" }); bool ApvlvTXT::pageText (int pn, const Rectangle &rect, std::string &text) { QFile file (mUrl.path ()); if (file.open (QIODeviceBase::ReadOnly) == false) return false; auto bytes = file.readAll (); text.append (bytes.toStdString ()); file.close (); return true; } bool ApvlvTXT::pageRenderToWebView (int pn, double zm, int rot, WebView *webview) { auto settings = webview->settings (); settings->setDefaultTextEncoding ("UTF-8"); return ApvlvHTML::pageRenderToWebView (pn, zm, rot, webview); } } // Local Variables: // mode: c++ // End: ================================================ FILE: src/file/ApvlvTxt.h ================================================ /* * This file is part of the apvlv package * Copyright (C) <2010> * * Contact: Alf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /* @CPPFILE ApvlvTxt.h * * Author: Alf */ #ifndef _APVLV_TXT_H_ #define _APVLV_TXT_H_ #include "ApvlvHtm.h" namespace apvlv { class ApvlvTXT : public ApvlvHTML { FILE_TYPE_DECLARATION (ApvlvTXT); public: bool load (const std::string &filename) override { return true; } bool pageText (int pn, const Rectangle &rect, std::string &text) override; bool pageRenderToWebView (int pn, double zm, int rot, WebView *webview) override; std::string pathMimeType (const std::string &path) override { return "text/plain"; }; }; } #endif /* Local Variables: */ /* mode: c++ */ /* End: */ ================================================ FILE: src/main.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /*@CPPFILE main.cpp Apvlv start at here * * Author: Alf */ #include #include #include #include #include #include #include #include #include "ApvlvInfo.h" #include "ApvlvLog.h" #include "ApvlvParams.h" #include "ApvlvUtil.h" #include "ApvlvView.h" using namespace std; using namespace apvlv; #if defined WIN32 && defined NDEBUG #pragma comment(linker, "/subsystem:windows") #pragma comment(linker, "/ENTRY:mainCRTStartup") #endif static void registerUrlScheme () { QWebEngineUrlScheme scheme ("apvlv"); scheme.setSyntax (QWebEngineUrlScheme::Syntax::Path); QWebEngineUrlScheme::registerScheme (scheme); } static void usageExit () { std::cout << PACKAGE_NAME << " [options] paths" << endl; std::cout << endl; std::cout << "Options: " << endl; std::cout << "\t-h display this and exit\n" "\t-v display version info and exit\n" "\t-c [file] set user configuration file\n" "\t paths document path list" << endl; FileFactory::typeEngineDescription (std::cout); std::cout << "Please send bug report to " << PACKAGE_BUGREPORT << endl; exit (0); } static void versionExit () { fprintf (stdout, "%s %s-%s\n" "Please send bug report to %s\n" "\n", PACKAGE_NAME, PACKAGE_VERSION, RELEASE, PACKAGE_BUGREPORT); exit (0); } static list parseCommandLine (const QCoreApplication &app) { QCommandLineParser parser; auto versionOption = QCommandLineOption (QStringList () << "v" << "version", QObject::tr ("version number")); parser.addOption (versionOption); auto helpOption = QCommandLineOption (QStringList () << "h" << "help", QObject::tr ("help information")); parser.addOption (helpOption); auto configFileOption = QCommandLineOption (QStringList () << "c" << "config-file", QObject::tr ("config file"), "config"); parser.addOption (configFileOption); auto logFileOption = QCommandLineOption (QStringList () << "l" << "log-file", QObject::tr ("log file"), "log"); parser.addOption (logFileOption); parser.addPositionalArgument ("path", QObject::tr ("document path")); parser.process (app); if (parser.isSet (helpOption)) { usageExit (); } if (parser.isSet (versionOption)) { versionExit (); } if (parser.isSet (configFileOption)) { auto value = parser.value (configFileOption); IniFile = filesystem::absolute (value.toStdString ()).string (); } if (parser.isSet (logFileOption)) { auto value = parser.value (logFileOption); LogFile = filesystem::absolute (value.toStdString ()).string (); } /* * load the global sys conf file * */ auto sysIni = string (SYSCONFDIR) + "/apvlvrc"; auto params = ApvlvParams::instance (); params->loadFile (sysIni); /* * load the user conf file * */ qDebug () << "using config: " << IniFile; ApvlvParams::instance ()->loadFile (IniFile); list paths; auto pathlist = parser.positionalArguments (); for (const auto &path : pathlist) { paths.emplace_back (path.toStdString ()); } return paths; } static void loadTranslator (QTranslator &translator) { map lanuage_translator{ { "Chinese", "zh_CN" } }; auto lan = QLocale::system ().language (); auto lanstr = QLocale::languageToString (lan).toStdString (); if (lanuage_translator.find (lanstr) != lanuage_translator.end ()) { auto lantrans = lanuage_translator[lanstr]; if (!translator.load (QString::fromLocal8Bit (lantrans), QString::fromLocal8Bit (Translations))) { qWarning () << "Load i18n file failed, using English"; } else { QCoreApplication::installTranslator (&translator); } } } int main (int argc, char *argv[]) { registerUrlScheme (); QApplication app (argc, argv); getRuntimePaths (); QTranslator translator; loadTranslator (translator); ApvlvInfo::instance ()->loadFile (SessionFile); auto paths = parseCommandLine (app); NotesDir = ApvlvParams::instance ()->getGroupStringOrDefault ( "notes", "dir", NotesDir); ApvlvLog::instance ()->setLogFile (LogFile); string path = HelpPdf; if (!paths.empty ()) { path = paths.front (); paths.pop_front (); } if (!filesystem::is_regular_file (path) && !filesystem::is_directory (path)) { qFatal () << "File '" << path << "' is not readable."; return 1; } ApvlvView sView (nullptr); if (!sView.newTab (path)) { exit (1); } while (!paths.empty ()) { path = paths.front (); paths.pop_front (); auto apath = filesystem::absolute (path).string (); if (!sView.newTab (apath)) { qCritical () << "Can't open document: " << apath; } } QApplication::exec (); return 0; } // Local Variables: // mode: c++ // End: ================================================ FILE: src/testNote.cc ================================================ /* * This file is part of the apvlv package * * Copyright (C) 2008 Alf. * * Contact: Alf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2.0 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* @CPPFILE ApvlvEditor.cc * * Author: Alf */ #include #include #include "ApvlvNote.h" namespace apvlv { std::string NotesDir = "/tmp"; } using namespace std; using namespace apvlv; int main (int argc, const char *argv[]) { auto note = Note{}; note.setScore (8.0); note.addTag ("abc"); note.addTag ("def"); note.addTag ("hijklmn"); auto comment1 = Comment{}; comment1.commentText = "lksdflkasdlfkaj"; comment1.quoteText = "lksjdflkajdsklfjkaldklsdlkfasldkjf\nlksdjfkla\nsdflkasd\n"; comment1.begin = Location{ 0, 1.0, 2.0, 3 }; comment1.end = Location{ 0, 2.0, 3.0, 4 }; note.addComment (comment1); auto comment2 = Comment{}; comment2.commentText = "bbbbbbbbbbbbbbbbbbbbbbb"; comment2.quoteText = "bbbbbbbbbbbbbbbbbbbb\nlksdjfkla\nsdflkasd\n"; comment2.begin = Location{ 1, 1.0, 2.0, 5 }; comment2.end = Location{ 1, 2.0, 3.0, 8 }; note.addComment (comment2); note.addReference ("abcdehijklmn1"); note.addReference ("abcdehijklmn2"); note.addReference ("abcdehijklmn3"); note.addLink ("link1sldkfajlksdjfaldsk1"); note.addLink ("link1sldkfajlksdjfaldsk2"); note.addLink ("link1sldkfajlksdjfaldsk3"); auto fos = ofstream{ "/tmp/testNote1.md" }; note.dumpStream (fos); fos.close (); auto fis = ifstream{ "/tmp/testNote1.md" }; auto note1 = Note{}; note1.loadStream (fis); auto fos2 = ofstream{ "/tmp/testNote2.md" }; note1.dumpStream (fos2); fos2.close (); return 0; } ================================================ FILE: vcpkg-manifests/system-qt/vcpkg.json ================================================ { "name": "apvlv-system-qt", "version": "0.7.0", "description": "apvlv deps manifest when using system Qt (avoid building vcpkg qtbase)", "license": "GPL-3.0", "dependencies": [ "libmupdf", "cmark" ], "features": { "ocr": { "description": "OCR support", "dependencies": ["tesseract"] } } } ================================================ FILE: vcpkg.json ================================================ { "name": "apvlv", "version": "0.7.0", "description": "Alf's PDF/DJVU/EPUB Viewer like Vim", "homepage": "https://github.com/naihe2010/apvlv", "license": "GPL-3.0", "dependencies": [ "qtbase", "qtwebengine", "quazip", "libmupdf", { "name": "poppler", "features": ["qt"] }, "cmark", "tesseract" ] } ================================================ FILE: zh_CN.ts ================================================ ApvlvSearchDialog Case sensitive 大小写 Regular expression 正则表达式 ApvlvView File 文件 Open 打开 OpenDir 打开文件夹 New Tab 新建标签页 Close Tab 关闭标签页 Quit 退出 View 视图 ToolBar 工具栏 Horizontal Split 横向分割窗口 Vertical Split 纵向分割窗口 Close Split 关闭分割 Navigate 导航 Previous Page 前一页 Next Page 下一页 Tools 工具 Help 帮助 Toggle Content 显示/隐藏目录 QObject config file 配置文件 log file 日志文件 document path 文档路径 version number 版本号 help information 帮助信息 apvlv::ApvlvContent title 标题 Filter 过滤 Expand All 展开所有 Collapse All 收起所有 Refresh 刷新 Sort By Title 以标题排序 Sort By Modified Time 以更改时间排序 Sort By File Size 以文件大小排序 size 大小 modified time 更改时间 Title 标题 File Size 文件大小 Modified Time 更改时间 None Filter Title 过滤标题 Filter File Name 过滤文件名 Filter File Size >= 过滤文件>= Filter FileSize <= 过滤文件<= Filter Modified Time >= 过滤更改时间>= Filter Modified Time <= 过滤更改时间<= Filter Type is invalid 过滤类型错误 Confirm 确认 Delete This File 删除这个文件 Will delete the %1, confirm ? 将要删除%1,确定吗? Input new name of %1 输入%1的新文件名 Rename 重命名 Rename %1 to %2 failed 重命名%1为%2失败 Warning 警告 Rename File 重命名文件 Delete File 删除文件 apvlv::ApvlvCore no content 没有目录 the file has no content, if still display content? this file has no content, if close the content? 此文件没有目录,是否仍然显示目录? apvlv::ApvlvFrame Previous Page 前一页 Next Page 下一页 Default 默认 Fit Width 适应宽度 Fit Height 适应高度 Custom 自定义 text in clipboard 剪贴板文本 apvlv::ApvlvSearchDialog Case sensitive 大小写 Regular expression 正则表达式 ... …… Find Directory: 查找目录: apvlv::ApvlvToolStatus Previous Page 前一页 Next Page 后一页 Zoom In 放大 Zoom Out 缩小 OCR Copy OCR复制 OCR Parse OCR解析 apvlv::ApvlvView File 文件 文件 Open 打开 打开 Quit 退出 退出 Edit 编辑 编辑 Navigate 导航 导航 Tools 工具 工具 Help 帮助 帮助 Next Page 下一页 下一页 New Tab new tab 新建标签页 Close Tab close tab 关闭标签页 View 视图 ToolBar 工具栏 Horizontal Split 横向分割窗口 Vertical Split 纵向分割窗口 Close Split 关闭分割 Previous Page 前一页 Toggle Content 显示/隐藏目录 OpenDir 打开文件夹 打开文件夹 Search 查找 Advanced Search 高级查找 Back Search 反向查找 StatusBar Dired input url: 输入网址: OpenUrl 打开网址 Toggle Directory 切换目录 apvlv::Directory Title 标题 Modified Time 更改时间 File Size 文件大小 Sort By Title 以标题排序 Sort By Modified Time 以更改时间排序 Sort By File Size 以文件大小排序 Filter Title 过滤标题 Filter File Name 过滤文件名 Filter Modified Time >= 过滤更改时间>= Filter Modified Time <= 过滤更改时间<= Filter File Size >= 过滤文件>= Filter FileSize <= 过滤文件<= Refresh 刷新 Expand All 展开所有 Collapse All 收起所有 Input new name of %1 输入%1的新文件名 Rename 重命名 Rename %1 to %2 failed 重命名%1为%2失败 Warning 警告 Will delete the %1, confirm ? 将要删除%1,确定吗? Confirm 确认 Filter Type is invalid 过滤类型错误 Rename File 重命名文件 Delete File 删除文件 Tags 标签 Score 评分 Sort By Tags 以标签排序 Sort By Score 以评分排序 Filter File Size <= 过滤文件<= Filter Tag 过滤标签 Filter Score >= 过滤评分>= Filter Score <= 过滤评分<= load note of %s error 加载%s的笔记错误 error 错误 apvlv::DiredDialog Regular expression 正则表达式 Find Directory: 查找目录: ... …… apvlv::ImageContainer Copy 复制 Underline 划线 Comment 注释 Input 输入 apvlv::NoteDialog tag 标签 input tag: 输入标签: OK 确定 Cancel 取消 Input tag: 输入标签: apvlv::SearchDialog Case sensitive 区分大小写 Regular expression 正则表达式 Find Directory: 查找目录: ... …… apvlv::WebView Copy 复制 Underline 划线 Comment 注释 Input 输入