[
  {
    "path": ".clang-format",
    "content": "BasedOnStyle: GNU\nColumnLimit: 78\nAllowShortFunctionsOnASingleLine: Empty\nAllowShortLambdasOnASingleLine: Empty\n"
  },
  {
    "path": ".dir-locals.el",
    "content": "((nil (eval . (setq-local flycheck-clang-include-path\n                          '(\"/usr/include/poppler/glib\"\n                            \"/usr/include/glib-2.0\"\n                            \"/usr/lib64/glib-2.0/include\"\n                            \"/usr/include/cairo\"\n                            \"/usr/include/pixman-1\"\n                            \"/usr/include/freetype2\"\n                            \"/usr/include/libxml2\"\n                            \"/usr/include/poppler\"\n                            \"/usr/include/pango-1.0\"\n                            \"/usr/include/glib-2.0\"\n                            \"/usr/include/gtk-3.0\"\n                            \"/usr/include/gdk-pixbuf-2.0\"\n                            \"/usr/include/gio-unix-2.0\"\n                            \"/usr/include/atk-1.0\")))))\n"
  },
  {
    "path": ".github/workflows/cmake-multi-platform.yml",
    "content": "# 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.\n# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml\nname: CMake on multiple platforms\n\non:\n  push:\n    branches: [\"**\"]\n  pull_request:\n    branches: [\"master\"]\n\njobs:\n  build-linux:\n    runs-on: ${{ matrix.os }}\n    container: ${{ matrix.container }}\n\n    strategy:\n      # 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.\n      fail-fast: false\n\n      matrix:\n        include:\n          - os: ubuntu-latest\n            container: fedora:43\n          - os: ubuntu-latest\n            container: debian:13\n          - os: ubuntu-latest\n            container: rockylinux:9\n          - os: ubuntu-latest\n            container: archlinux:latest\n\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v4\n\n      - name: Prepare (Fedora 43)\n        if: matrix.container == 'fedora:43'\n        run: |\n          dnf install -y cmake cmark gcc-c++ \\\n            qt6-qtbase-devel qt6-qtwebengine-devel \\\n            qt6-qtpdf-devel ps2pdf libreofficekit-devel \\\n            quazip-qt6-devel poppler-qt6-devel \\\n            cmark-devel djvulibre-devel \\\n            mupdf-devel tesseract-devel \\\n            man ghostscript qt6-linguist \\\n            rpmbuild\n          mkdir -p $GITHUB_WORKSPACE/share/doc/apvlv/translations\n\n      - name: Prepare (Debian 13)\n        if: matrix.container == 'debian:13'\n        run: |\n          apt-get update\n          apt-get install -y cmake cmark g++ \\\n            qt6-base-dev qt6-webengine-dev \\\n            qt6-pdf-dev libreofficekit-dev \\\n            libquazip1-qt6-dev libpoppler-qt6-dev \\\n            libcmark-dev libdjvulibre-dev \\\n            libharfbuzz-dev \\\n            libmupdf-dev libtesseract-dev \\\n            man groff ghostscript qt6-tools-dev qt6-l10n-tools \\\n            pkg-config\n          export PATH=/usr/lib/qt6/bin:$PATH\n          mkdir -p $GITHUB_WORKSPACE/share/doc/apvlv/translations\n\n      - name: Prepare (RockyLinux 9)\n        if: matrix.container == 'rockylinux:9'\n        run: |\n          dnf install -y epel-release\n          dnf config-manager --enable crb devel\n          dnf install -y --nogpgcheck https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-$(rpm -E %rhel).noarch.rpm\n          dnf install -y --nogpgcheck https://mirrors.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-$(rpm -E %rhel).noarch.rpm\n          dnf install -y cmake cmark gcc-c++ \\\n            qt6-qtbase-devel qt6-qtwebengine-devel \\\n            qt6-qtpdf-devel libreofficekit-devel \\\n            cmark-devel djvulibre-devel \\\n            mupdf-devel tesseract-devel \\\n            man ghostscript qt6-linguist\n            # quazip-qt6-devel poppler-qt6-devel\n          mkdir -p $GITHUB_WORKSPACE/share/doc/apvlv/translations\n\n      - name: Prepare (Arch Linux)\n        if: matrix.container == 'archlinux:latest'\n        run: |\n          pacman -Syu --noconfirm\n          pacman -S --noconfirm base-devel cmake make gcc \\\n            qt6-base qt6-webengine \\\n            libreoffice-fresh-sdk jdk-openjdk \\\n            quazip-qt6 poppler-qt6 \\\n            pkg-config \\\n            cmark djvulibre \\\n            mupdf mujs tesseract \\\n            man-db groff ghostscript qt6-tools \\\n            gtk3\n          mkdir -p $GITHUB_WORKSPACE/share/doc/apvlv/translations\n\n      - name: Build (Linux)\n        run: |\n          test -d /usr/lib/qt6/bin && export PATH=/usr/lib/qt6/bin:$PATH\n          cmake -B build -S . -DCMAKE_BUILD_TYPE=Release\n          cmake --build build\n\n  build-windows:\n    name: Build (Windows)\n    runs-on: windows-latest\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v4\n\n      - name: Build with script\n        run: ./scripts/build.ps1\n\n"
  },
  {
    "path": ".gitignore",
    "content": "/build\n*~\ncscope.files\ncscope.out\n/.idea\n/.vscode\n/.cache\nshare/doc/apvlv/translations/*\nshare/doc/apvlv/Startup.pdf\n"
  },
  {
    "path": "AUTHORS",
    "content": "Alf <naihe2010@126.com>\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(apvlv VERSION 0.7.0)\n\n# vcpkg integration\nif(DEFINED CMAKE_TOOLCHAIN_FILE)\n    message(STATUS \"Using vcpkg toolchain: ${CMAKE_TOOLCHAIN_FILE}\")\nendif()\n\n# Include cmake modules\nlist(APPEND CMAKE_MODULE_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/cmake\")\ninclude(Options)\ninclude(CompileFlags)\ninclude(Dependencies)\n\n# Version info\nfind_package(Git)\nif(Git_FOUND)\n    execute_process(\n        COMMAND ${GIT_EXECUTABLE} log -1 --format=%h\n        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}\n        OUTPUT_VARIABLE VERSION_HASH\n        OUTPUT_STRIP_TRAILING_WHITESPACE\n    )\n    if(VERSION_HASH)\n        message(STATUS \"Git hash: ${VERSION_HASH}\")\n        set(PROJECT_PATCH ${VERSION_HASH})\n    endif()\nendif()\n\n# Package definitions\nif(WIN32)\n    # On Windows, use AppData\\Local or install directory\n    set(SYSCONFDIR \"$ENV{LOCALAPPDATA}/apvlv\" CACHE PATH \"System config directory\")\nelse()\n    set(SYSCONFDIR \"/etc\" CACHE PATH \"System config directory\")\nendif()\nadd_definitions(-DSYSCONFDIR=\"${SYSCONFDIR}\")\n\nadd_definitions(-DPACKAGE_NAME=\"apvlv\"\n    -DPACKAGE_VERSION=\"${PROJECT_VERSION}\"\n    -DPACKAGE_BUGREPORT=\"Alf <naihe2010@126.com>\"\n    -DRELEASE=\"rel\"\n)\n\nADD_SUBDIRECTORY(src)\n\nIF (NOT WIN32)\n    ADD_CUSTOM_TARGET(CopyCompileCommands ALL\n            COMMAND ${CMAKE_COMMAND} -E copy_if_different\n            \"${CMAKE_BINARY_DIR}/compile_commands.json\"\n            \"${CMAKE_SOURCE_DIR}/compile_commands.json\"\n            COMMENT \"Copying compile_commands.json to ${CMAKE_SOURCE_DIR}\"\n            DEPENDS ${CMAKE_BINARY_DIR}/compile_commands.json\n    )\nENDIF ()\n\nINSTALL(DIRECTORY share DESTINATION \".\")\nIF (WIN32)\n    INSTALL(FILES apvlvrc.example DESTINATION \".\")\nELSE (WIN32)\n    ADD_CUSTOM_TARGET(Startup.pdf\n            ALL\n            COMMAND \"man\" \"-t\" \"${CMAKE_SOURCE_DIR}/apvlv.1\" \"|\" \"ps2pdf\" \"-\" \"${CMAKE_SOURCE_DIR}/share/doc/apvlv/Startup.pdf\"\n            DEPENDS apvlv.1)\n    INSTALL(FILES apvlvrc.example DESTINATION ${SYSCONFDIR})\n    INSTALL(FILES apvlv.1 TYPE MAN)\nENDIF (WIN32)\n\nSET(CPACK_PACKAGE_VENDOR \"Alf\")\nSET(CPACK_PACKAGE_DESCRIPTION_SUMMARY \"apvlv - Alf's PDF/DJVU/EPUB Viewer like Vim\")\nSET(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_MAJOR})\nSET(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_MINOR})\nSET(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_PATCH})\n\nIF (UNIX)\n    SET(CPACK_SET_DESTDIR ON)\n    SET(CPACK_PACKAGE_CONTACT \"Alf <naihe2010@126.com>\")\n    SET(CPACK_GENERATOR DEB)\n    SET(CPACK_DEBIAN_PACKAGE_DEPENDS\n            \"qt6 quazip cmark\")\n    IF (EXISTS \"/etc/redhat-release\")\n        SET(CPACK_GENERATOR RPM)\n        SET(CPACK_RPM_PACKAGE_REQUIRES \"qt6-qtwebengine quazip-qt6 cmark\")\n    ENDIF ()\n    SET(CPACK_SOURCE_GENERATOR TGZ)\n    SET(CPACK_SOURCE_IGNORE_FILES\n            ${CMAKE_BINARY_DIR}\n            \".git\"\n            \".gitignore\"\n            \"win32\"\n            \"~$\"\n    )\nELSE (UNIX)\n    SET(CPACK_GENERATOR NSIS)\n    SET(CPACK_NSIS_CONTACT \"Alf <naihe2010@126.com>\")\n    SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS \"CreateShortCut '\\$DESKTOP\\\\\\\\apvlv.lnk' '\\$INSTDIR\\\\\\\\bin\\\\\\\\apvlv.exe'\")\n    SET(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS \"Delete '\\$DESKTOP\\\\\\\\apvlv.lnk'\")\nENDIF (UNIX)\n\nINCLUDE(CPack)\n"
  },
  {
    "path": "COPYING",
    "content": "\t\t    GNU GENERAL PUBLIC LICENSE\n\t\t       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\t\t\t    Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n\t\t    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n\t\t\t    NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n\t\t     END OF TERMS AND CONDITIONS\n\n\t    How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, write to the Free Software Foundation, Inc.,\n    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n"
  },
  {
    "path": "NEWS",
    "content": "apvlv 0.5.0\n==========\n\napvlv 0.4.0\n==========\nAdd a 'F' command for forwarding to a word in page when enable visual mode.\nuse mouse motion to select text, which support Ctrl mask.\nUse mouse select and popup menu to annotate text, underline or comment.\nWhen using shortcut 'v' or 'C-v' to selected content, shortcut 'A' to annotate text, shortcut 'U' to underline and shortcut 'C' to comment.\n\napvlv 0.3.0\n===========\ninner content toc display\nshortcut 'c' or command ':content' to toggle content\nshortcut '.' to repeat last action\nadd background option\nauto set background=black when set inverted=yes\nadd 'content_follow_mode' option for control follow mode of content view\n\napvlv 0.2.0\n===========\nAdd EPUB format support\n\napvlv 0.1.4\n===========\n\nAdd TXT format support\n\napvlv 0.1.3\n===========\n\nAdd HTML format support\n\napvlv 0.1.2\n===========\n\nJust swapped source from svn to git\n\napvlv 0.1.1\n===========\n\nNews features\n\n   * add '[int]s' to support skip some pages for some document\n   * sorted in dir mode by filename\n   * using 't' to open file in new tab window\n   * using 'T' to open directory in new tab window\n\napvlv 0.1.0\n===========\n\nNews features\n\n   * UMD file support\n   * using CMake for auto build tool\n   * add rpm and deb installation package\n\n===========\n\napvlv 0.0.9\nNews features\n\n   * More like vim's info file scheme\n\n   * Add `guioptions = \"mT\"` to support menu and tool bar\n\n   * Add command bar response when command is failed or command is not valid\n\n   * Add double click action on pdf pages for select a word, a line or a page\n     of text\n\n   * Mouse Copy Support: add left drag to select area, and right click to popup menu to copy to clipboard\n\n   * Key Copy Support: add select area by pressing 'v' or <C-v> and copy area by press 'y'\n\n   * add 'wrapscan' options in $HOME/.apvlvrc\n\n   * add search support of dir and content view\n\n   * add Startup.tex to source tar\n\n   * use \\ to support space in file name when :o[pen] or :doc\n\n   * use 'G' to go to the last page\n\n   * keep search direction when search with 'n' or 'N' after / or ?\n\n   * add autoreload parameter to support auto reload document or directory view\n\n   * add reverted pdf page feature\n\nBug Fix\n\n   * display '*' when input password to open pdf docs\n\n   * return false when search next but no first string was be searched.\n\n   * return true when sub child dir has pdf files\n\n   * fix a segfault when yank text\n\n   * fix a memory leak when destroy a apvlv doc\n\n   * fix the correct select mark when scroll up or down the page\n\n   * fix the k or j 's length in continuous mode\n  \n\napvlv 0.0.8\n===========\n\nNews features\n\n   * add DJVU format document viewing support \n\n   * add poppler-data support in Windows version\n\n   * add :w[rite] filename to save document\n\n   * add zw to fitwidth and zh to fitheight\n\n   * made the scrollbar as a option in $HOME/.apvlvrc\n\nBug Fix\n\n   * If a pdf file is not encrypted, not ask password when can't open it\n\n   * highlight correctly after zoom in or zoom out\n\n   * :z[oom] fitwidth or :z[oom] fitheight works\n\n\napvlv 0.0.7\n===========\n\nChanges for WARNING\n\n   * apvlv don't support negative integer now\n\n     Because the '-' cause many bugs in in <C-w>- to smaller a window, apvlv don't support negative integer from 0.0.7.4.\n\nNews features\n\n   * add shell command line options, -h, -v and -c\n\n   * add options to disable ~/.apvlvinfo\n\n   * add a option \"set autoscrolldoc=yes/no\" to toggle auto scroll a doc from end to 1st\n\n   * add a option \"set pdfcache=[number]\" to set the cache size of pdf object\n\n   * set pdfcache >= 2 limit for apvlv works correctly\n\n   * set no autoscroll and not continuous when pdf is a single page\n\n   * add global /etc/apvlvrc\n\n   * don't create ~/.apvlvrc automatically\n\n   * made fullscreen really works under some WM\n\n   * add key 'n' and 'N' to search and back search\n\nBug Fix\n\n   * fix file is not exist segment fault\n\n   * fix the command history segment fault\n\n   * add document of / and ? key which appear from 0.0.3\n\n   * fix lots of spelling errors in Startup.pdf\n\n\napvlv 0.0.6\n============\n\nNews features\n\n   * Warning !!! change 'goto' command's 'g' to 'G'\n\n   * add continuous view of pdf page\n     set in .apvlvrc \"set continuous=yes/no\"\n\n   * add argument to support weather auto scroll page when k,j to page's end or head\n     set in .apvlvrc \"set autoscrollpage=yes/no\"\n\n   * add Up and Down key to get previous and next command in Command Prompt\n\n   * add tab view\n     add :tabnew and 'gt' 'gg' 'gT' 'g[n]' command to support tab switch, change goto page command from 'g' to 'G'\n\n   * add content view\n         * k, j to select up, down\n         * h, l to expand, collapse\n         * <CR> open selected page\n         * 'o' open selected page in split window\n         * 't' open selected page in new tab\n\n   * add directory view by command 'O'\n         * k, j to select up, down\n         * h, l to expand, collapse\n         * <CR> open selected file\n         * 'o' open selected file in split window\n         * 't' open selected file in new tab\n\n   * add <C-]> goto a hyperlink and <C-t> to come back\n\n   * add open encrypted pdf with password support\n\n   * add open last directory\n\n   * add mouse wheel scrollup and down support\n\n   * add a padding argument to continuous page\n\n   * add :number to goto page\n\n Bug fix\n\n   * fix the windows open bug\n\n   * fix the keys can't work on 64bit platform\n\n   * fix some segment fault\n\n   * change the width, height of apvlv can resize\n\n\napvlv 0.0.5\n============\n\nNews features\n\n    * add cache module to make the display faster.\n\n    * add ':set [no]cache' to tell apvlv if use cache module.\n\n\napvlv 0.0.4\n=============\n\nNews features\n\n    * remove the status bar from the bottom, and add it to every window.\n\n    * make the active window highlight the status bar.\n\n    * add 'r' command to rotate page.\n\n    * add ':TOtext' to translate pdf doc page to a text file.\n\n    * add ':pr[int]' to print the document.\n\n    * add ':open file' to open a PDF document.\n\n    * add ':doc file' to set the doc to current window.\n\n    * support open multiple PDF files once, and load them in cache. can be set by ':doc file'.\n\nBug Fix\n   \n    * fix the absolutepath () bug.\n\n\napvlv 0.0.3\n==============\n\nNews features\n\n    * Replace word which like 'C-a' to like '<C-a>' at every necessary place.\n\n    * Support 'H', 'M' and 'L' to scroll one PDF page.\n\n    * Support multiple window view and support below command to control them.\n            0. ':sp', ':vsp', 'q', 'quit'\n            1. '<C-w> <C-Q>', '<C-w> q\n            2. '<C-w> k'\n            3. '<C-w> j'\n            4. '<C-w> h'\n            5. '<C-w> l'\n            6. '<C-w> <C-w>'\n            7. '<C-w> -'\n            8. '<C-w> +'\n\n    * Support multiple PDF Doc to be opened at the memory.\n\n    * Support map command mode\n            like 'map a :vsp'\n"
  },
  {
    "path": "README.md",
    "content": "# apvlv\n\napvlv is a PDF/EPUB/TXT/FB2/MOBI/CBZ/HTML ... Viewer Under Linux/WIN32 and its behaviour like Vim.\n\nApvlv is an open source software, was created by Alf and is hosted on [github](https://github.com/naihe2010/apvlv).\n\nNow, it is still growing.\n\nLike Vim, Apvlv makes people reading their PDF/EPUB files just like using Vim.\n\nSo, 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.\n\nAnd, Apvlv can understand that how many times you want to run the command.\n\nThe 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.\n\nWhat'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.\n\n# Dependencies\n\n+ Qt6 ( http://www.qt.org/ )\n+ Quazip ( https://github.com/stachenov/quazip )\n\n# Optional Dependencies\n\n+ Poppler-qt6 ( https://www.qt.io/ )\n+ MuPDF ( https://www.mupdf.com/ )\n+ Teeseract ( https://github.com/tesseract-ocr/tessdoc )\n\n# Download\n\n+ Releases (https://github.com/naihe2010/apvlv/releases)\n\n# Build\n\n1. Using cmake to generate Makefile.\n   \n   ```\n   cmake .\n   ```\n2. Execute make.\n   \n   ```\n   make\n   ```\n\n# Install\n\n+ Make a package and install it.\n  \n  ```\n  make package\n  ```\n+ Or install it directly.\n  \n  ```\n  sudo make install\n  ```\n\n# License\n\napvlv is licensed under the GNU General Public License (GPL).\n\n# Contact\n\n+ Email: Alf [naihe2010@126.com](mailto:naihe2010@126.com)\n\n# Develop Tools\n\n+ Vim (https://www.vim.org/)\n\n+ Emacs (https://www.gnu.org/software/emacs/)\n\n+ CLion (https://www.jetbrains.com/clion/)\n  \n  Thanks these great tools.\n\n# Github Actions\n![CI Status](https://github.com/naihe2010/apvlv/actions/workflows/cmake-multi-platform.yml/badge.svg)\n"
  },
  {
    "path": "THANKS",
    "content": "I would like to give thanks to the following for their support and\ncontributions:\n\n        - Ian Munsie <darkstarsword@gmail.com>, for he fixing some memory leaks in ApvlvPDF::pagesearch \n        - Nico <zon3r@yahoo.com>, for his inverted feature code contribution.\n          And, the code is copy from evince, and the author is Juanjo MarÃ­n.\n          Thanks !!!\n\n        - Daniel Friesel <foobar@derf.homelinux.org>, for his more beautiful man page\n        - Andrew Kudryashov <andrewinsilenthill@gmail.com>, for his help about Startup.pdf\n        - Adam <jiang.adam@gmail.com>, for his test and bug fix.\n        - Stefan Ritter <xeno@thehappy.de>, for his man page of apvlv, his careful test and pack on Debian\n\n        - Robert Smolinski <scottymcribs@gmail.com>, for his vimlike tabs code contribution. \n          And, he is a developer of apvlv then.\n\n        - Robby Workman <rworkman@slackbuilds.org>, for his bug fix about doc directory\n        - grandpa <lxdjob.gmail.com>, for his test on windows\n        - Ali Gholami Rudi <aligrudi@gmail.com>, for his bug fixes\n        - tocer <tocer@gmail.com>, for his test on linux and pack on ArchLinux.\n        - pk <pk0206@gmail.com>, for his advices\n"
  },
  {
    "path": "TODO",
    "content": ".      Bug fix.\n"
  },
  {
    "path": "apvlv.1",
    "content": ".Dd December 24, 2009\n.Dt apvlv 1\n.Os\n.Sh NAME\n.Nm apvlv\n.Nd PDF/DJVU/EPUB/HTML/TXT/FB2/CZW viewer with vim-like behaviour\n.Sh SYNOPSIS\n.Nm\n.Op options\n.Op file\n.Sh DESCRIPTION\napvlv is a PDF/DJVU/EPUB/HTML/TXT/FB2/CZW viewer, which behaves like vim.\n.Sh OPTIONS\n.Bl -tag -width \"v\"\n.It Fl c Ar file\nLoad configuration from\n.Ar file\ninstead of the default\n.Pa ~/.apvlvrc\n.It Fl h\nShow help message and exit\n.It Fl v\nShow version and exit\n.El\n.Sh COMMANDS\nThe following command keys can be used inside apvlv.\nSome of them may be prefixed by a number (as in pressing \"13G\"),\nthis is indicated by a\n.Ar count\nin their description.  Unless noted otherwise, the default value for the\nnumber is 1.\n.Bl -tag -width \"indent\"\n.It o\nDisplay file chooser to open a PDF/DJVU/EPUB/HTML/TXT file\n.It O\nSelect a directory to display\n.It t\nDisplay file chooser to open a PDF/DJVU/EPUB/HTML/TXT file in a new tab\n.It T\nSelect a directory to display in a new tab\n.It R\nReload the current file\n.It r\nRotate the document clockwise by 90 degrees\n.It G\nShow page number\n.Ar count\n.It gt\nShow next tab\n.It gT\nShow previous tab\n.It PageDown, C-f\nGo forward\n.Ar count\npages\n.It PageUp, C-b\nGo backward\n.Ar count\npages\n.It C-d\nGo forward\n.Ar count\nhalf pages\n.It C-u\nGo backward\n.Ar count\nhalf pages\n.It H\nScroll to page head\n.It M\nScroll to page middle\n.It L\nScroll to page bottom\n.It s\nskip some pages\n.Ar count\n.It C-p, Up, k\nScroll up\n.Ar count\nunits\n.It C-n, Down, j\nScroll down\n.Ar count\nunits\n.It Backspace, Left, h\nScroll left\n.Ar count\nunits\n.It Space, Right, l\nScroll right\n.Ar count\nunits\n.It / Ar string\nSearch forwards for\n.Ar string\n.It ? Ar string\nSearch backwards for\n.Ar string\n.It f\nToggle between fullscreen and window mode\n.It zi\nZoom in\n.It zo\nZoom out\n.It zw\nZoom to fit window width\n.It zh\nZoom to fit window height\n.It m Ar char\nMark the current position to\n.Ar char ,\nso that it can be recalled by pressing\n.Ar char\n.It ' Ar char\nReturn to the mark position\n.Ar char\n.It ''\nReturn to the last position\n.It q\nClose the current window\n.It ZZ\nquit the viewer with ZZ, like Vim\n.It c\ntoggle directory display\n.El\n.Sh SETTINGS\nThese can be set in ~/.apvlvrc with\n.Qq set Ar setting Op = Ar value .\n.Bl -tag -width \"indent\"\n.It fullscreen = yes/no\nEnable/Disable fullscreen\n.It width = Ar int\nDefault window width\n.It height = Ar int\nDefault window height\n.It fix_width = Ar int\nfixed width\n.It fix_height = Ar int\nfixed height\n.It defaultdir = Ar path\nDefault directory for the open dialogue\n.It zoom = Ar mode\nSet default zoom level\n.Bl -tag -width \"indent\"\n.It normal\nThe application sets the default zoom value\n.It fitwidth\nFit pages to window width\n.It fitheight\nFit pages to window height\n.It Ar float\n1.0 for 100%, 2.0 for 200%, etc.\n.El\n.It continuous = yes/no\nShow PDF/DJVU/EPUB/HTML/TXT pages continuously or not.\n.It autoscrollpage = yes/no\nEnable/Disable scrolling the pages when hitting a page tail/head\n.It noinfo = yes/no\nDisable/Enable the usage of ~/.apvlvinfo\n.It max_info = Ar int\nMax file information will be saved, default is 100\n.It scrollbar = yes/no\nSet show scrollbar or not\n.It wrapscan = yes/no\nSet wrapscan to search text or not\n.It doubleclick = Ar action\nSet default double click action\n.Bl -tag -width \"indent\"\n.It none\nSelection nothing\n.It word\nSelection a word under the cursor to clipboard\n.It line\nSelection a line under the cursor to clipboard\n.It page\nSelection a page under the cursor to clipboard\n.El\n.It guioptions = mTsS\nWeather display menu, toolbar, status and status tool.\n.It .pdf:engine = MuPDF/Poppler/QtPdf\nWhich engine to render .pdf file\n.It .epub:engine = MuPDF/Web\nWhich engine to render .epub file\n.It .fb2:engine = MuPDF/Web\nWhich engine to render .fb2 file\n.It ocr:lang = eng+chi_sim\nPretrained languages which tesseract load to process\n.It notes:dir = Ar dir\nDirectory to save ebook notes\n.It autoreload = Ar int\nIf auto reload document after some seconds\n.El\n.Bl -tag -width \"indent\"\n.It inverted = yes/no\nIf use inverted mode for pdf page\n.It background = Ar color\nSet background color\n.El\n.Sh PROMPT\nLike the COMMANDS, but prefixed with a colon:\n.Bl -tag -width \"indent\"\n.It :h[elp]\nDisplay the help document\n.It :q[uit]\nClose the current window\n.It :o[pen] Ar file\nOpen\n.Ar file\n.It :doc Ar file\nLoad\n.Ar file\ninto the current window\n.It :TOtext Op Ar file\nTranslate\n.Ar file\n(or the current page) to a text file\n.It :pr[int]\nPrint the current document\n.It :tabnew\nCreate a new tab\n.It :sp\nHorizontally split the current window\n.It :vsp\nVertically split the current window\n.It :fp, :forwardpage Op Ar int\nGo forward\n.Ar int\npages (1 by default)\n.It :bp, :prewardpage Op Ar int\nGo backward\n.Ar int\npages (1 by default)\n.It :g, :goto Ar int\nGo to page\n.Ar int\n.It :z[oom] Ar mode\nSet zoom to\n.Ar mode\n(see \"set zoom\" in SETTINGS)\n.It : Ns Ar int\nGo to page\n.Ar int\n.It :directory\ntoggle directory display\n.El\n.Sh AUTHORS\napvlv was written by Alf <naihe2010@126.com>.\n.Pp\nThis manual page was originally written by Stefan Ritter <xeno@thehappy.de> for the Debian project (but may be used by others), and was rewritten more beautifully by Daniel Friesel <foobar@derf.homelinux.org>.\n"
  },
  {
    "path": "apvlvrc.example",
    "content": "\" some map\n\n\" map n to <C-f> to goto next page\n\"map n <C-f>\n\n\" and p to prepage\n\"map p <C-b>\n\n\" map I to zi, and O to zo\n\"map I zi\n\"map O zo\n\n\" if start apvlv as fullscreen mode, default is no\n\"set fullscreen=no\n\n\" zoom value, default is fitwidth\n\" zoom has 4 styles\n\" a float type number\n\" fitwidth\n\" fitheight\n\" normal\n\"set zoom=fitwidth\n\n\" set window size\n\"set width=800\n\"set height=600\n\n\" set command timeout between two key press \n\"set commandtimeout=2000\n\n\" set default dir\n\"set defaultdir=C:\\\n\n\" set weather use continuous view\n\" make sure the autoscrollpage is set to \"yes\" if you want to set this to yes\n\"set continuous=yes\n\n\" set a pad to continuous page\n\"set continuouspad=2\n\n\" set if auto scroll page when at the end or begin of one pdf page\n\"set autoscrollpage=yes\n\n\" set if auto scroll doc from 1st page when goto the last page\n\"set autoscrolldoc=yes\n\n\" set if disable ~/.apvlvinfo, default is no\n\"set noinfo=no\n\n\" set if wrapscan text\n\"set wrapscan = yes\n\n\" set double click action\n\" option value is 'word', 'line' or 'page'\n\"set doubleclick = page\n\n\" set GUI options\n\" m means menu, T means toolbar\n\"set guioptions = mT\n\n\" set if reverted pdf page\n\"set reverted = no\n"
  },
  {
    "path": "cmake/CompileFlags.cmake",
    "content": "# Compiler flags configuration\n\n# C++ standard\nset(CMAKE_CXX_STANDARD 20)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\n\n# Common flags\nif(NOT WIN32)\n    set(CMAKE_CXX_FLAGS \"-Wall -fno-strict-aliasing\")\n\n    if(CMAKE_BUILD_TYPE STREQUAL \"Debug\")\n        set(CMAKE_CXX_FLAGS \"-D_DEBUG -g ${CMAKE_CXX_FLAGS}\")\n    else()\n        set(CMAKE_CXX_FLAGS \"-O2 ${CMAKE_CXX_FLAGS}\")\n    endif()\nelse()\n    add_definitions(-D_CRT_SECURE_NO_WARNINGS)\nendif()\n\n# Export compile commands\nset(CMAKE_EXPORT_COMPILE_COMMANDS ON)\n\n# Qt definitions\nadd_definitions(-DQT_MESSAGELOGCONTEXT)\n"
  },
  {
    "path": "cmake/Dependencies.cmake",
    "content": "# Dependency management for apvlv\n\n# Platform-specific dependency handling\nif(WIN32)\n    # Windows - use vcpkg via find_package (vcpkg provides CMake config files)\n    # Required dependencies\n    find_package(cmark REQUIRED)\n    find_package(quazip REQUIRED)\n    find_package(djvulibre REQUIRED)\n    \n    # Setup variables for compatibility with Unix code\n    if(TARGET cmark::cmark)\n        set(CMARK_FOUND TRUE)\n        get_target_property(CMARK_INCLUDE_DIRS cmark::cmark INTERFACE_INCLUDE_DIRECTORIES)\n        if(NOT CMARK_INCLUDE_DIRS)\n            get_target_property(CMARK_INCLUDE_DIRS cmark::cmark INCLUDE_DIRECTORIES)\n        endif()\n        set(CMARK_LIBRARIES cmark::cmark)\n    elseif(cmark_FOUND)\n        set(CMARK_FOUND TRUE)\n        if(cmark_INCLUDE_DIR)\n            set(CMARK_INCLUDE_DIRS ${cmark_INCLUDE_DIR})\n        endif()\n        if(cmark_LIBRARY)\n            set(CMARK_LIBRARIES ${cmark_LIBRARY})\n        endif()\n    endif()\n    \n    if(TARGET quazip::quazip)\n        set(QUAZIP_FOUND TRUE)\n        get_target_property(QUAZIP_INCLUDE_DIRS quazip::quazip INTERFACE_INCLUDE_DIRECTORIES)\n        if(NOT QUAZIP_INCLUDE_DIRS)\n            get_target_property(QUAZIP_INCLUDE_DIRS quazip::quazip INCLUDE_DIRECTORIES)\n        endif()\n        set(QUAZIP_LIBRARIES quazip::quazip)\n    elseif(quazip_FOUND)\n        set(QUAZIP_FOUND TRUE)\n        if(quazip_INCLUDE_DIR)\n            set(QUAZIP_INCLUDE_DIRS ${quazip_INCLUDE_DIR})\n        endif()\n        if(quazip_LIBRARY)\n            set(QUAZIP_LIBRARIES ${quazip_LIBRARY})\n        endif()\n    endif()\n    \n    # DjVu setup for Windows\n    if(TARGET djvulibre::djvulibre)\n        set(DJVULIBRE_FOUND TRUE)\n        get_target_property(DJVULIBRE_INCLUDE_DIR djvulibre::djvulibre INTERFACE_INCLUDE_DIRECTORIES)\n        if(DJVULIBRE_INCLUDE_DIR)\n            get_filename_component(DJVULIBRE_DIR ${DJVULIBRE_INCLUDE_DIR} DIRECTORY)\n        endif()\n    elseif(djvulibre_FOUND)\n        set(DJVULIBRE_FOUND TRUE)\n        if(djvulibre_INCLUDE_DIR)\n            set(DJVULIBRE_INCLUDE_DIR ${djvulibre_INCLUDE_DIR})\n            get_filename_component(DJVULIBRE_DIR ${djvulibre_INCLUDE_DIR} DIRECTORY)\n        endif()\n        if(djvulibre_LIBRARY)\n            get_filename_component(DJVULIBRE_LIB_DIR ${djvulibre_LIBRARY} DIRECTORY)\n            if(NOT DJVULIBRE_DIR)\n                set(DJVULIBRE_DIR ${DJVULIBRE_LIB_DIR})\n            endif()\n        endif()\n    endif()\n    \n    # Optional dependencies for Windows\n    if(APVLV_WITH_POPPLER)\n        find_package(Poppler QUIET)\n        if(Poppler_FOUND)\n            set(POPPLER_FOUND TRUE)\n            if(TARGET Poppler::poppler)\n                set(POPPLER_LIBRARIES Poppler::poppler)\n            elseif(poppler_LIBRARIES)\n                set(POPPLER_LIBRARIES ${poppler_LIBRARIES})\n            endif()\n        endif()\n    endif()\n    \n    if(APVLV_WITH_MUPDF)\n        find_package(mupdf QUIET)\n        if(mupdf_FOUND)\n            set(MUPDF_FOUND TRUE)\n            if(TARGET mupdf::mupdf)\n                set(MUPDF_STATIC_LIBRARIES mupdf::mupdf)\n            elseif(mupdf_LIBRARIES)\n                set(MUPDF_STATIC_LIBRARIES ${mupdf_LIBRARIES})\n            endif()\n        endif()\n    endif()\n    \n    if(APVLV_WITH_OCR)\n        find_package(tesseract QUIET)\n        if(tesseract_FOUND)\n            set(TESSERACT_FOUND TRUE)\n            if(TARGET tesseract::tesseract)\n                set(TESSERACT_LIBRARIES tesseract::tesseract)\n            elseif(tesseract_LIBRARIES)\n                set(TESSERACT_LIBRARIES ${tesseract_LIBRARIES})\n            endif()\n        endif()\n    endif()\nelse()\n    # Unix/Linux - use pkg-config\n    find_package(PkgConfig REQUIRED)\n    pkg_check_modules(CMARK libcmark REQUIRED)\n    pkg_check_modules(QUAZIP quazip1-qt6 REQUIRED)\n    \n    # Optional dependencies for Unix\n    if(APVLV_WITH_POPPLER)\n        pkg_check_modules(POPPLER poppler-qt6)\n    endif()\n\n    if(APVLV_WITH_MUPDF)\n        pkg_check_modules(MUPDF mupdf)\n    endif()\n\n    if(APVLV_WITH_OCR)\n        pkg_check_modules(TESSERACT tesseract)\n    endif()\nendif()\n\n# Find Qt6 components\nfind_package(Qt6 NAMES Qt6 COMPONENTS\n    Core Gui Widgets WebEngineWidgets Pdf PdfWidgets Xml PrintSupport\n    REQUIRED\n)\n\n# Setup Qt variables\nset(Qt_INCLUDE_DIRS\n    ${Qt6Core_INCLUDE_DIRS}\n    ${Qt6Gui_INCLUDE_DIRS}\n    ${Qt6Widgets_INCLUDE_DIRS}\n    ${Qt6WebEngineWidgets_INCLUDE_DIRS}\n    ${Qt6Pdf_INCLUDE_DIRS}\n    ${Qt6PdfWidgets_INCLUDE_DIRS}\n    ${Qt6Xml_INCLUDE_DIRS}\n    ${Qt6PrintSupport_INCLUDE_DIRS}\n)\n\nset(Qt_LIBRARIES\n    Qt6::Core Qt6::Gui Qt6::Widgets\n    Qt6::WebEngineWidgets Qt6::Pdf Qt6::PdfWidgets\n    Qt6::Xml Qt6::PrintSupport\n)\n"
  },
  {
    "path": "cmake/Options.cmake",
    "content": "# Build options for apvlv\noption(APVLV_WITH_MUPDF \"Enable MuPDF PDF engine\" ON)\noption(APVLV_WITH_POPPLER \"Enable Poppler PDF engine\" OFF)\noption(APVLV_WITH_DJVU \"Enable DjVu support\" ON)\noption(APVLV_WITH_OFFICE \"Enable Office document support\" ON)\noption(APVLV_WITH_OCR \"Enable OCR support\" ON)\n\n# Platform-specific defaults\nif(WIN32)\n    set(APVLV_WITH_POPPLER ON CACHE BOOL \"Enable Poppler PDF engine\" FORCE)\nendif()\n"
  },
  {
    "path": "scripts/build.ps1",
    "content": "param(\n    [string]$BuildDir = \"\",\n    [string]$BuildType = \"Release\",\n    [string]$VisualStudioVersion = \"\",\n    [string]$VcpkgDir = \"\",\n    [string]$VcpkgBuildTrees = \"\"\n)\n\n$ErrorActionPreference = \"Stop\"\n\nfunction Write-Error-Exit {\n    param([string]$Message)\n    Write-Host \"ERROR: $Message\" -ForegroundColor Red\n    exit 1\n}\n\nfunction Test-Command {\n    param([string]$Command)\n    return [bool](Get-Command $Command -ErrorAction SilentlyContinue)\n}\n\nfunction Get-VisualStudio {\n    $vsWhere = \"${env:ProgramFiles(x86)}\\Microsoft Visual Studio\\Installer\\vswhere.exe\"\n    $instances = @()\n\n    if (Test-Path $vsWhere) {\n        try {\n            $oldEncoding = [Console]::OutputEncoding\n            [Console]::OutputEncoding = [System.Text.Encoding]::UTF8\n\n            $json = & $vsWhere -all -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -format json -utf8\n\n            [Console]::OutputEncoding = $oldEncoding\n\n            if ($json) {\n                $rawInstances = $json | ConvertFrom-Json\n                if ($rawInstances -is [System.Management.Automation.PSCustomObject]) {\n                    $rawInstances = @($rawInstances)\n                }\n\n                foreach ($item in $rawInstances) {\n                    $majorVersion = $item.installationVersion.Split('.')[0]\n                    $generator = \"\"\n                    switch ($majorVersion) {\n                        \"18\" { $generator = \"Visual Studio 18 2026\" }\n                        \"17\" { $generator = \"Visual Studio 17 2022\" }\n                        \"16\" { $generator = \"Visual Studio 16 2019\" }\n                        \"15\" { $generator = \"Visual Studio 15 2017\" }\n                        Default { $generator = \"Visual Studio $majorVersion\" }\n                    }\n\n                    $instances += @{\n                        Path = $item.installationPath\n                        Version = $item.installationVersion\n                        Generator = $generator\n                    }\n                }\n            }\n        } catch {\n            [Console]::OutputEncoding = $oldEncoding\n        }\n    }\n\n    if ($instances.Count -eq 0) {\n        $fallback = @(\n            @{Path = \"${env:ProgramFiles}\\Microsoft Visual Studio\\2022\\Community\"; Version = \"17.0\"; Generator = \"Visual Studio 17 2022\"},\n            @{Path = \"${env:ProgramFiles}\\Microsoft Visual Studio\\2019\\Community\"; Version = \"16.0\"; Generator = \"Visual Studio 16 2019\"}\n        )\n\n        foreach ($item in $fallback) {\n            if (Test-Path $item.Path) {\n                $instances += $item\n            }\n        }\n    }\n\n    if ($instances.Count -eq 0) { return $null }\n\n    if ($VisualStudioVersion) {\n        $selected = $instances | Where-Object { $_.Version.StartsWith($VisualStudioVersion) } | Sort-Object Version -Descending | Select-Object -First 1\n        if ($selected) { return $selected }\n        Write-Host \"Warning: VS version '$VisualStudioVersion' not found, using latest\" -ForegroundColor Yellow\n    }\n\n    return $instances | Sort-Object Version -Descending | Select-Object -First 1\n}\n\n# Initialize paths\n$SrcDir = Split-Path -Parent $PSScriptRoot\nif (-not $BuildDir) { $BuildDir = Join-Path $env:USERPROFILE \"build\\apvlv\" }\n$BuildDir = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($BuildDir)\n\nif ($VcpkgDir) {\n    $VcpkgRoot = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($VcpkgDir)\n} else {\n    $VcpkgRoot = Join-Path $BuildDir \"vcpkg\"\n}\n$VcpkgInstalledDir = Join-Path $BuildDir \"vcpkg_installed\"\n$BuildDirPath = Join-Path $BuildDir \"build\"\nif ($VcpkgBuildTrees) {\n    $VcpkgBuildTreesPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($VcpkgBuildTrees)\n} else {\n    $VcpkgBuildTreesPath = \"\"\n}\n\nWrite-Host \"Build Configuration:\" -ForegroundColor Cyan\nWrite-Host \"  Build Directory: $BuildDir\"\nWrite-Host \"  Build Type: $BuildType\"\nWrite-Host \"  Source Directory: $SrcDir\"\n\n# Check dependencies\nWrite-Host \"Checking dependencies...\" -ForegroundColor Green\nif (-not (Test-Command \"git\")) { Write-Error-Exit \"Git not found\" }\nif (-not (Test-Command \"cmake\")) { Write-Error-Exit \"CMake not found\" }\n\n$vs = Get-VisualStudio\nif (-not $vs) { Write-Error-Exit \"Visual Studio with C++ tools not found\" }\nWrite-Host \"Using: $($vs.Generator)\" -ForegroundColor Green\n\n# Create build directory\nif (!(Test-Path $BuildDir)) {\n    Write-Host \"Creating build directory...\" -ForegroundColor Green\n    New-Item -ItemType Directory -Path $BuildDir | Out-Null\n}\n\n# Setup vcpkg\nif (!(Test-Path $VcpkgRoot) -or !(Test-Path \"$VcpkgRoot\\.git\")) {\n    Write-Host \"Cloning vcpkg...\" -ForegroundColor Green\n    if (Test-Path $VcpkgRoot) { Remove-Item -Recurse -Force $VcpkgRoot }\n    Push-Location $BuildDir\n    try {\n        git clone https://github.com/microsoft/vcpkg vcpkg\n        if ($LASTEXITCODE -ne 0) { Write-Error-Exit \"Failed to clone vcpkg\" }\n    } finally {\n        Pop-Location\n    }\n} else {\n    Write-Host \"Updating vcpkg...\" -ForegroundColor Green\n    Push-Location \"$VcpkgRoot\"\n    try {\n        $isShallow = (& git rev-parse --is-shallow-repository).Trim()\n        if ($isShallow -eq \"true\") {\n            git fetch --unshallow\n            if ($LASTEXITCODE -ne 0) { Write-Error-Exit \"Failed to unshallow vcpkg repository\" }\n        }\n        git pull --ff-only\n        if ($LASTEXITCODE -ne 0) { Write-Error-Exit \"Failed to update vcpkg\" }\n    } finally {\n        Pop-Location\n    }\n}\n\nif (!(Test-Path \"$VcpkgRoot\\vcpkg.exe\")) {\n    Write-Host \"Bootstrapping vcpkg...\" -ForegroundColor Green\n    Push-Location \"$VcpkgRoot\"\n    try {\n        .\\bootstrap-vcpkg.bat\n        if ($LASTEXITCODE -ne 0) { Write-Error-Exit \"Failed to bootstrap vcpkg\" }\n    } finally {\n        Pop-Location\n    }\n}\n\nWrite-Host \"Installing vcpkg dependencies...\" -ForegroundColor Green\n$env:VCPKG_INSTALLED_DIR = $VcpkgInstalledDir\nPush-Location \"$SrcDir\"\ntry {\n    $installArgs = @(\"install\", \"--triplet=x64-windows\", \"--clean-after-build\", \"--x-install-root=$VcpkgInstalledDir\")\n    if ($VcpkgBuildTreesPath) { $installArgs += \"--x-buildtrees-root=$VcpkgBuildTreesPath\" }\n    & \"$VcpkgRoot\\vcpkg.exe\" $installArgs\n    if ($LASTEXITCODE -ne 0) { Write-Error-Exit \"Failed to install dependencies\" }\n} finally {\n    Pop-Location\n}\nRemove-Item Env:VCPKG_INSTALLED_DIR -ErrorAction SilentlyContinue\n\n# Configure and build\nif (Test-Path $BuildDirPath) {\n    Write-Host \"Cleaning build directory...\" -ForegroundColor Green\n    Remove-Item -Recurse -Force $BuildDirPath\n}\n\nNew-Item -ItemType Directory -Path $BuildDirPath | Out-Null\n\nWrite-Host \"Configuring with CMake...\" -ForegroundColor Green\nPush-Location \"$BuildDirPath\"\ntry {\n    cmake \"$SrcDir\" `\n      -DCMAKE_TOOLCHAIN_FILE=\"$VcpkgRoot\\scripts\\buildsystems\\vcpkg.cmake\" `\n      -DVCPKG_TARGET_TRIPLET=x64-windows `\n      -DCMAKE_BUILD_TYPE=$BuildType `\n      -G \"$($vs.Generator)\" `\n      -A x64\n    if ($LASTEXITCODE -ne 0) { Write-Error-Exit \"CMake configuration failed\" }\n    Write-Host \"Building project...\" -ForegroundColor Green\n    $ParallelJobs = [Math]::Min([Environment]::ProcessorCount, 8)\n    cmake --build . --config $BuildType --parallel $ParallelJobs\n    if ($LASTEXITCODE -ne 0) { Write-Error-Exit \"Build failed\" }\n} finally {\n    Pop-Location\n}\n\nWrite-Host \"Build completed successfully!\" -ForegroundColor Green\nWrite-Host \"Output: $BuildDirPath\\$BuildType\" -ForegroundColor Cyan\n"
  },
  {
    "path": "scripts/build.sh",
    "content": "#!/bin/bash\n# Build script for apvlv using bash\n# Usage: ./build.sh [debug clean test package deps help] [build_path]\n\nset -e\n\n# Colors for output\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nNC='\\033[0m' # No Color\n\nprint_status() {\n    echo -e \"${GREEN}[INFO]${NC} $1\"\n}\n\nprint_warning() {\n    echo -e \"${YELLOW}[WARN]${NC} $1\"\n}\n\nprint_error() {\n    echo -e \"${RED}[ERROR]${NC} $1\"\n}\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nSRC_DIR=\"$(dirname \"$SCRIPT_DIR\")\"\n\ncleanup_vcpkg_locks() {\n    if [ -n \"$VCPKG_ROOT\" ] && [ -d \"$VCPKG_ROOT\" ]; then\n        print_status \"Cleaning up vcpkg lock files...\"\n        rm -f \"$VCPKG_ROOT/.vcpkg-root.lock\" 2>/dev/null || true\n        rm -f \"$VCPKG_ROOT/vcpkg.lock\" 2>/dev/null || true\n        if [ -n \"$VCPKG_DOWNLOADS\" ] && [ -d \"$VCPKG_DOWNLOADS\" ]; then\n            rm -f \"$VCPKG_DOWNLOADS/.lock\" 2>/dev/null || true\n        fi\n    fi\n}\n\ndetect_system_qt() {\n    SYSTEM_QT_FOUND=0\n    SYSTEM_QT_PREFIX=\"\"\n\n    if [ \"${USE_VCPKG_QT:-0}\" = \"1\" ]; then\n        print_status \"USE_VCPKG_QT=1 set, skipping system Qt detection\"\n        return\n    fi\n\n    print_status \"Checking for system Qt6 installation...\"\n\n    if command -v qmake6 &>/dev/null; then\n        SYSTEM_QT_PREFIX=$(qmake6 -query QT_INSTALL_PREFIX 2>/dev/null)\n        if [ -n \"$SYSTEM_QT_PREFIX\" ] && [ -d \"$SYSTEM_QT_PREFIX\" ]; then\n            print_status \"Found system Qt6 via qmake6: $SYSTEM_QT_PREFIX\"\n            SYSTEM_QT_FOUND=1\n            return\n        fi\n    fi\n\n    if command -v qmake &>/dev/null; then\n        local qt_version=$(qmake -query QT_VERSION 2>/dev/null)\n        if [[ \"$qt_version\" == 6.* ]]; then\n            SYSTEM_QT_PREFIX=$(qmake -query QT_INSTALL_PREFIX 2>/dev/null)\n            if [ -n \"$SYSTEM_QT_PREFIX\" ] && [ -d \"$SYSTEM_QT_PREFIX\" ]; then\n                print_status \"Found system Qt6 via qmake: $SYSTEM_QT_PREFIX\"\n                SYSTEM_QT_FOUND=1\n                return\n            fi\n        fi\n    fi\n\n    if command -v pkg-config &>/dev/null; then\n        if pkg-config --exists Qt6Core 2>/dev/null; then\n            local qt6_libdir=$(pkg-config --variable=libdir Qt6Core 2>/dev/null)\n            if [ -n \"$qt6_libdir\" ]; then\n                SYSTEM_QT_PREFIX=$(dirname \"$qt6_libdir\")\n                print_status \"Found system Qt6 via pkg-config: $SYSTEM_QT_PREFIX\"\n                SYSTEM_QT_FOUND=1\n                return\n            fi\n        fi\n    fi\n\n    local common_qt_paths=(\n        \"/usr/lib/qt6\"\n        \"/usr/lib64/qt6\"\n        \"/usr/share/qt6\"\n        \"/usr\"\n        \"/usr/local\"\n    )\n\n    for qt_path in \"${common_qt_paths[@]}\"; do\n        if [ -f \"$qt_path/lib/cmake/Qt6/Qt6Config.cmake\" ] ||\n            [ -f \"$qt_path/lib64/cmake/Qt6/Qt6Config.cmake\" ] ||\n            [ -f \"/usr/lib/cmake/Qt6/Qt6Config.cmake\" ]; then\n            SYSTEM_QT_PREFIX=\"$qt_path\"\n            print_status \"Found system Qt6 in: $SYSTEM_QT_PREFIX\"\n            SYSTEM_QT_FOUND=1\n            return\n        fi\n    done\n\n    if [ -f \"/usr/lib/cmake/Qt6/Qt6Config.cmake\" ] ||\n        [ -f \"/usr/lib64/cmake/Qt6/Qt6Config.cmake\" ]; then\n        SYSTEM_QT_PREFIX=\"/usr\"\n        print_status \"Found system Qt6 CMake config in /usr\"\n        SYSTEM_QT_FOUND=1\n        return\n    fi\n\n    print_warning \"System Qt6 not found, will use vcpkg Qt\"\n}\n\ncheck_system_qt_deps() {\n    if ! command -v pkg-config &>/dev/null; then\n        print_warning \"pkg-config not found; cannot validate system Qt dependencies.\"\n        return\n    fi\n\n    local missing=0\n\n    if ! pkg-config --exists Qt6Core 2>/dev/null; then\n        print_warning \"Missing system Qt6 pkg-config entry (Qt6Core).\"\n        missing=1\n    fi\n\n    if ! pkg-config --exists quazip1-qt6 2>/dev/null; then\n        print_warning \"Missing system QuaZIP for Qt6 (pkg-config: quazip1-qt6).\"\n        missing=1\n    fi\n\n    if ! pkg-config --exists libcmark 2>/dev/null; then\n        print_warning \"Missing system cmark (pkg-config: libcmark).\"\n        missing=1\n    fi\n\n    if [ \"$missing\" = \"1\" ]; then\n        print_warning \"System Qt mode selected, but some required system packages are missing.\"\n        print_warning \"Please install distro packages providing: Qt6, quazip-qt6, cmark.\"\n        print_warning \"Or force vcpkg Qt by setting USE_VCPKG_QT=1.\"\n    fi\n}\n\nexecute_in_build_dir() {\n    local cmd=\"$1\"\n    cd \"$BUILD_DIR\"\n    if ! eval \"$cmd\"; then\n        print_error \"Command failed: $cmd\"\n        exit 1\n    fi\n    cd \"$SRC_DIR\"\n}\n\nexecute_cmake_command() {\n    local cmake_args=(\"$@\")\n    if [ \"$SINGLE_CONFIG\" = \"false\" ]; then\n        cmake_args+=(\"--config\" \"$BUILD_TYPE\")\n    fi\n\n    print_status \"Running CMake command\"\n    if [ ! -d \"$BUILD_DIR\" ]; then\n        print_error \"Build directory not found: $BUILD_DIR\"\n        exit 1\n    fi\n\n    if ! (cd \"$BUILD_DIR\" && cmake \"${cmake_args[@]}\"); then\n        print_error \"CMake command failed\"\n        exit 1\n    fi\n}\n\nfind_and_run_test() {\n    find_file_in_configs \"testNote\" \"$BUILD_DIR\"\n    if [ \"$FILE_FOUND\" = \"1\" ]; then\n        for config in \"$BUILD_TYPE\" \"Release\" \"Debug\"; do\n            if [ -f \"$BUILD_DIR/$config/testNote\" ]; then\n                \"$BUILD_DIR/$config/testNote\"\n                if [ $? -ne 0 ]; then\n                    print_error \"Test execution failed.\"\n                    exit 1\n                fi\n                return\n            fi\n        done\n        if [ -f \"$BUILD_DIR/testNote\" ]; then\n            \"$BUILD_DIR/testNote\"\n            if [ $? -ne 0 ]; then\n                print_error \"Test execution failed.\"\n                exit 1\n            fi\n        fi\n    fi\n}\n\ncheck_tools() {\n    if ! command -v git &>/dev/null; then\n        print_error \"Git not found.\"\n        exit 1\n    fi\n\n    if ! command -v cmake &>/dev/null; then\n        print_error \"CMake not found.\"\n        exit 1\n    fi\n}\n\nsetup_vcpkg() {\n    git config --global --add safe.directory \"$VCPKG_ROOT\" 2>/dev/null || true\n\n    local VCPKG_CLONE_URL=\"https://github.com/microsoft/vcpkg\"\n\n    if [ ! -d \"$VCPKG_ROOT/.git\" ]; then\n        print_status \"Cloning vcpkg...\"\n        echo\n        if ! git clone --depth 1 '$VCPKG_CLONE_URL' '$VCPKG_ROOT'; then\n            print_error \"Failed to clone vcpkg after multiple attempts.\"\n            exit 1\n        fi\n    else\n        print_status \"Updating vcpkg...\"\n        echo\n        if ! git -C \"$VCPKG_ROOT\" pull --ff-only; then\n            print_error \"Failed to update vcpkg after multiple attempts.\"\n            echo\n            exit 1\n        fi\n    fi\n\n    if [ ! -f \"$VCPKG_ROOT/vcpkg\" ]; then\n        print_status \"Bootstrapping vcpkg...\"\n        echo\n\n        cleanup_vcpkg_locks\n\n        if ! cd \"$VCPKG_ROOT\" && ./bootstrap-vcpkg.sh; then\n            print_error \"Failed to bootstrap vcpkg after multiple attempts.\"\n            exit 1\n        fi\n    fi\n\n    if [ ! -f \"$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake\" ]; then\n        print_error \"vcpkg toolchain file not found.\"\n        exit 1\n    fi\n}\n\ninstall_dependencies() {\n    print_status \"Installing vcpkg dependencies...\"\n\n    cleanup_vcpkg_locks\n\n    detect_system_qt\n\n    local vcpkg_args=(\n        \"install\"\n        \"--triplet=$VCPKG_TRIPLET\"\n        \"--clean-after-build\"\n        \"--x-install-root=$VCPKG_INSTALLED_DIR\"\n    )\n\n    if [ \"$APVLV_WITH_OCR\" = \"1\" ]; then\n        vcpkg_args+=(\"--x-feature=ocr\")\n    fi\n    if [ \"$APVLV_WITH_MUPDF\" = \"1\" ]; then\n        vcpkg_args+=(\"--x-feature=mupdf\")\n    fi\n\n    local manifest_root=\"$SRC_DIR\"\n    if [ \"$SYSTEM_QT_FOUND\" = \"1\" ]; then\n        manifest_root=\"$SRC_DIR/vcpkg-manifests/system-qt\"\n        check_system_qt_deps\n        print_status \"Using system Qt. vcpkg manifest root: $manifest_root\"\n    else\n        print_status \"Using vcpkg Qt. vcpkg manifest root: $manifest_root\"\n    fi\n\n    export VCPKG_INSTALLED_DIR\n\n    if ! cd \"$SRC_DIR\" && \"$VCPKG_ROOT/vcpkg\" ${vcpkg_args[*]} --x-manifest-root='$manifest_root'; then\n        print_error \"Failed to install vcpkg dependencies after multiple attempts.\"\n        exit 1\n    fi\n}\n\nensure_vcpkg_and_deps() {\n    setup_vcpkg\n    install_dependencies\n}\n\nclean_build() {\n    print_status \"Cleaning build directory: $BUILD_DIR\"\n    if [ -d \"$BUILD_DIR\" ]; then\n        rm -rf \"$BUILD_DIR\"\n        if [ $? -ne 0 ]; then\n            print_error \"Failed to remove build directory.\"\n            exit 1\n        fi\n    fi\n    mkdir -p \"$BUILD_DIR\" || {\n        print_error \"Failed to create build directory.\"\n        exit 1\n    }\n}\n\nconfigure() {\n    local cmake_cmd=\"cmake \\\"$SRC_DIR\\\"\"\n\n    cmake_cmd+=\" -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake\"\n    cmake_cmd+=\" -DVCPKG_TARGET_TRIPLET=$VCPKG_TRIPLET\"\n\n    if [ \"$SYSTEM_QT_FOUND\" = \"1\" ]; then\n        print_status \"Configuring CMake to use system Qt from: $SYSTEM_QT_PREFIX\"\n        cmake_cmd+=\" -DCMAKE_PREFIX_PATH=\\\"$SYSTEM_QT_PREFIX;$VCPKG_INSTALLED_DIR/$VCPKG_TRIPLET\\\"\"\n        cmake_cmd+=\" -DVCPKG_MANIFEST_MODE=OFF\"\n    fi\n\n    local vcpkg_manifest_features=()\n    if [ \"$APVLV_WITH_MUPDF\" = \"1\" ]; then\n        cmake_cmd+=\" -DAPVLV_WITH_MUPDF=ON\"\n        vcpkg_manifest_features+=(\"mupdf\")\n    fi\n    if [ \"$APVLV_WITH_OCR\" = \"1\" ]; then\n        cmake_cmd+=\" -DAPVLV_WITH_OCR=ON\"\n        vcpkg_manifest_features+=(\"ocr\")\n    fi\n\n    if [ ${#vcpkg_manifest_features[@]} -gt 0 ] && [ \"$SYSTEM_QT_FOUND\" != \"1\" ]; then\n        cmake_cmd+=\" -DVCPKG_MANIFEST_FEATURES=$(\n            IFS=';'\n            echo \"${vcpkg_manifest_features[*]}\"\n        )\"\n    fi\n\n    if command -v ninja &>/dev/null; then\n        cmake_cmd+=' -G \"Ninja\"'\n    else\n        cmake_cmd+=' -G \"Unix Makefiles\"'\n    fi\n\n    execute_in_build_dir \"$cmake_cmd\"\n}\n\nbuild() {\n    execute_cmake_command \"--build\" \".\" \"--parallel\"\n}\n\ncreate_package() {\n    execute_cmake_command \"--build\" \".\" \"--target\" \"package\" \"--parallel\"\n}\n\nrun_tests() {\n    find_and_run_test\n}\n\nfind_executable() {\n    find_file_in_configs \"apvlv\" \"$BUILD_DIR\"\n}\n\nfind_file_in_configs() {\n    local filename=\"$1\"\n    local search_base=\"$2\"\n\n    FILE_FOUND=0\n    for config in \"$BUILD_TYPE\" \"Release\" \"Debug\"; do\n        if [ -f \"$search_base/$config/$filename\" ]; then\n            print_status \"Found: $search_base/$config/$filename\"\n            FILE_FOUND=1\n            return\n        fi\n    done\n\n    if [ -f \"$search_base/$filename\" ]; then\n        print_status \"Found: $search_base/$filename\"\n        FILE_FOUND=1\n    fi\n\n    if [ \"$FILE_FOUND\" = \"0\" ]; then\n        print_warning \"Could not locate $filename.\"\n    fi\n}\n\nexecute_build() {\n    case \"${ACTION,,}\" in\n        \"clean\")\n            print_status \"Cleaning build directory...\"\n            clean_build\n            exit 0\n            ;;\n        \"test\")\n            print_status \"Running tests...\"\n            run_tests\n            exit 0\n            ;;\n        \"deps\")\n            print_status \"Setting up vcpkg and installing dependencies...\"\n            ensure_vcpkg_and_deps\n            print_status \"Dependencies installed successfully.\"\n            exit 0\n            ;;\n    esac\n\n    check_tools\n\n    print_status \"Ensuring vcpkg and dependencies are installed...\"\n    ensure_vcpkg_and_deps\n\n    print_status \"Creating build directory if it doesn't exist: $BUILD_DIR\"\n    mkdir -p \"$BUILD_DIR\" || print_warning \"Failed to create build directory, continuing...\"\n\n    clean_build\n    configure\n\n    print_status \"Building $BUILD_TYPE version...\"\n    build\n\n    if [ \"${ACTION,,}\" = \"package\" ]; then\n        print_status \"Creating package...\"\n        create_package\n    fi\n\n    find_executable\n\n    print_status \"Build completed successfully!\"\n}\n\nshow_help() {\n    echo \"Usage: build.sh [debug clean test package deps help] [build_path]\"\n    echo\n\n    echo \"Options:\"\n    echo \"    debug    - Build in debug mode\"\n    echo \"    clean    - Clean build directory\"\n    echo \"    test     - Run tests\"\n    echo \"    package  - Create installer package\"\n    echo \"    deps     - Install vcpkg dependencies only\"\n    echo \"    help     - Show this help message\"\n    echo \"    build_path - Optional path for global build directory (default: ~/build/apvlv)\"\n    echo\n\n    echo \"Environment Variables:\"\n    echo \"    GLOBAL_BUILD_DIR  - Path to global build directory\"\n    echo \"    VCPKG_ROOT        - Path to vcpkg installation (default: \\$GLOBAL_BUILD_DIR/vcpkg)\"\n    echo \"    VCPKG_TARGET_TRIPLET - Target triplet for vcpkg\"\n    echo \"    APVLV_WITH_MUPDF  - Set to 1 to enable MuPDF support\"\n    echo \"    APVLV_WITH_OCR    - Set to 1 to enable OCR support\"\n    echo \"    USE_VCPKG_QT      - Set to 1 to force using vcpkg Qt instead of system Qt\"\n    echo \"    \"\n    echo \"vcpkg Manifests:\"\n    echo \"    Default: uses $SRC_DIR/vcpkg.json (includes qtbase)\"\n    echo \"    System Qt mode: uses $SRC_DIR/vcpkg-manifests/system-qt/vcpkg.json (no qtbase)\"\n    echo\n\n    echo \"Qt Detection:\"\n    echo \"    The script will automatically detect system Qt6 installation.\"\n    echo \"    If found, system Qt will be used instead of vcpkg Qt.\"\n    echo \"    Set USE_VCPKG_QT=1 to force using vcpkg Qt.\"\n    echo\n\n    echo \"Examples:\"\n    echo \"    build.sh                      - Build release version in default directory\"\n    echo \"    build.sh debug                - Build debug version in default directory\"\n    echo \"    build.sh ~/apvlv_build        - Build release version in specified directory\"\n    echo \"    build.sh debug ~/apvlv_build  - Build debug version in specified directory\"\n    echo \"    build.sh deps                 - Install dependencies only\"\n    echo \"    build.sh package              - Create installer\"\n    echo \"    USE_VCPKG_QT=1 build.sh       - Force using vcpkg Qt\"\n}\n\n#-------------------# MAIN SCRIPT# -------------------------------\n\n# Set default values\nBUILD_TYPE=\"${BUILD_TYPE:-Release}\"\nACTION=\"${ACTION:-build}\"\nGLOBAL_BUILD_DIR=\"${GLOBAL_BUILD_DIR:-$HOME/build/apvlv}\"\nVCPKG_ROOT=\"${VCPKG_ROOT:-$GLOBAL_BUILD_DIR/vcpkg}\"\nBUILD_DIR=\"${BUILD_DIR:-$GLOBAL_BUILD_DIR/build}\"\nVCPKG_INSTALLED_DIR=\"${VCPKG_INSTALLED_DIR:-$GLOBAL_BUILD_DIR/vcpkg_installed}\"\nVCPKG_TRIPLET=\"${VCPKG_TARGET_TRIPLET:-x64-linux}\"\n\n# Environment variables for features\nUSE_VCPKG_QT=\"${USE_VCPKG_QT:-0}\"\n\n# System Qt detection results (will be set by detect_system_qt)\nSYSTEM_QT_FOUND=0\nSYSTEM_QT_PREFIX=\"\"\n\n# Determine if we're using a single-config generator\nSINGLE_CONFIG=\"true\"\nif command -v ninja &>/dev/null; then\n    SINGLE_CONFIG=\"false\"\nfi\n\n# Parse arguments\nwhile [ $# -gt 0 ]; do\n    case \"${1,,}\" in\n        \"debug\")\n            BUILD_TYPE=\"Debug\"\n            ;;\n        \"clean\")\n            ACTION=\"clean\"\n            ;;\n        \"test\")\n            ACTION=\"test\"\n            ;;\n        \"package\")\n            ACTION=\"package\"\n            ;;\n        \"deps\")\n            ACTION=\"deps\"\n            ;;\n        \"help\")\n            show_help\n            exit 0\n            ;;\n\n        *)\n            # Assume it's a build path\n            GLOBAL_BUILD_DIR=\"$1\"\n            VCPKG_ROOT=\"$GLOBAL_BUILD_DIR/vcpkg\"\n            BUILD_DIR=\"$GLOBAL_BUILD_DIR/build\"\n            VCPKG_INSTALLED_DIR=\"$GLOBAL_BUILD_DIR/vcpkg_installed\"\n            ;;\n    esac\n    shift\ndone\n\n# Adjust triplet based on build type\nif [ \"$BUILD_TYPE\" = \"Debug\" ]; then\n    if [[ ! \"$VCPKG_TRIPLET\" =~ -debug$ ]]; then\n        VCPKG_TRIPLET=\"${VCPKG_TRIPLET}-debug\"\n    fi\nelse\n    if [[ \"$VCPKG_TRIPLET\" =~ -debug$ ]]; then\n        VCPKG_TRIPLET=\"${VCPKG_TRIPLET%-debug}\"\n    fi\nfi\n\nprint_status \"Build type: $BUILD_TYPE\"\nprint_status \"VCPKG triplet: $VCPKG_TRIPLET\"\nprint_status \"Using build directory: $GLOBAL_BUILD_DIR\"\n\nexecute_build\n\nexit 0\n"
  },
  {
    "path": "share/applications/apvlv.desktop",
    "content": "[Desktop Entry]\nVersion=1.0\nType=Application\nName=apvlv\nComment=A minimalistic document viewer\nComment[de]=Ein minimalistischer Dokumenten-Betrachter\nComment[fr]=Un visionneur de document minimaliste\nComment[ru]=Минималистичный просмотрщик документов\nComment[tr]=Minimalist bir belge görüntüleyicisi\nComment[es_CL]=Un visor de documentos minimalista\nComment[uk_UA]=Легкий переглядач документів\nComment[it]=Un visualizzatore di documenti minimalista\nComment[pl]=Minimalistyczna przeglądarka dokumentów\nExec=apvlv %f\nTerminal=false\nCategories=Office;Viewer;\nMimeType=application/pdf;application/epub+zip;image/vnd.djvu;\nKeywords=vim;pdf;\nIcon=x-office-document\n"
  },
  {
    "path": "share/scripts/internal.js",
    "content": "// internal.js\nfunction getSelectionOffset(index) {\n    const selection = window.getSelection();\n\n    if (!selection.rangeCount || index >= selection.rangeCount) {\n        return [null, null];\n    }\n\n    const createOffsetRange = (container, offset) => {\n        const range = document.createRange();\n        range.setStart(document.documentElement, 0);\n        range.setEnd(container, offset);\n        return range.toString().length;\n    };\n\n    try {\n        const range = selection.getRangeAt(index);\n        return [createOffsetRange(range.startContainer, range.startOffset), createOffsetRange(range.endContainer, range.endOffset)];\n    } catch (error) {\n        console.error('Error accessing selection range:', error);\n        return [null, null];\n    }\n}\n\nfunction underlineTextNode(textNode, startOffset, endOffset, tooltipText) {\n    if (!(textNode instanceof Text)) {\n        throw new Error('Invalid text node provided');\n    }\n\n    const textContent = textNode.nodeValue;\n    const validEndOffset = endOffset === -1 ? textContent.length : endOffset;\n\n    if (startOffset < 0 || validEndOffset > textContent.length || startOffset > validEndOffset) {\n        throw new Error('Invalid offset values');\n    }\n\n    const parent = textNode.parentNode;\n    if (!parent) {\n        throw new Error('Text node has no parent element');\n    }\n\n    const beforeText = textContent.slice(0, startOffset);\n    const underlinedText = textContent.slice(startOffset, validEndOffset);\n    const afterText = textContent.slice(validEndOffset);\n\n    const underlineElement = document.createElement('u');\n    underlineElement.textContent = underlinedText;\n    if (tooltipText.length !== 0) {\n        underlineElement.setAttribute('title', tooltipText);\n    }\n\n    const fragment = document.createDocumentFragment();\n    if (beforeText) fragment.appendChild(document.createTextNode(beforeText));\n    fragment.appendChild(underlineElement);\n    if (afterText) fragment.appendChild(document.createTextNode(afterText));\n\n    parent.replaceChild(fragment, textNode);\n}\n\nfunction traverseTextNodes(root, callback) {\n    const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null,);\n\n    let node;\n    while ((node = walker.nextNode())) {\n        if (callback(node) === false) break;\n    }\n}\n\nfunction underlineByOffset(startOffset, endOffset, tooltip_msg) {\n    if (startOffset >= endOffset || startOffset < 0) {\n        throw new Error('Invalid offset range');\n    }\n\n    let currentOffset = 0;\n    const nodesInfo = {\n        start: {node: null, offset: 0}, end: {node: null, offset: 0}, between: []\n    };\n\n    traverseTextNodes(document.documentElement, (textNode) => {\n        const nodeLength = textNode.nodeValue.length;\n        const nodeEnd = currentOffset + nodeLength;\n\n        if (!nodesInfo.start.node && currentOffset <= startOffset && nodeEnd > startOffset) {\n            nodesInfo.start.node = textNode;\n            nodesInfo.start.offset = startOffset - currentOffset;\n        }\n\n        if (!nodesInfo.end.node && currentOffset <= endOffset && nodeEnd > endOffset) {\n            nodesInfo.end.node = textNode;\n            nodesInfo.end.offset = endOffset - currentOffset;\n            return false;\n        }\n\n        if (nodesInfo.start.node && !nodesInfo.end.node && textNode !== nodesInfo.start.node) {\n            nodesInfo.between.push(textNode);\n        }\n\n        currentOffset = nodeEnd;\n        return true;\n    });\n\n    if (nodesInfo.start.node && nodesInfo.end.node) {\n        underlineTextNode(nodesInfo.start.node, nodesInfo.start.offset, nodesInfo.start.node === nodesInfo.end.node ? nodesInfo.end.offset : -1, tooltip_msg);\n\n        nodesInfo.between.forEach(node => {\n            underlineTextNode(node, 0, -1, tooltip_msg);\n        });\n\n        if (nodesInfo.start.node !== nodesInfo.end.node) {\n            underlineTextNode(nodesInfo.end.node, 0, nodesInfo.end.offset, tooltip_msg);\n        }\n    }\n}\n\nfunction scrollByTimes(times, h, v) {\n    window.scrollBy(times * h, times * v);\n}\n\nfunction scrollToAnchor(anchor) {\n    if (typeof anchor === 'string' && anchor.startsWith('#')) {\n        const element = document.getElementById(anchor.substr(1));\n        if (element) {\n            element.scrollIntoView();\n        }\n    }\n}\n\nfunction scrollToPosition(xrate, yrate) {\n    const x = window.screenX * xrate;\n    const y = (document.body.offsetHeight - window.innerHeight) * yrate;\n    window.scroll(x, y);\n}\n\nfunction dispatchKeydownEvent(keyCode) {\n    const event = new KeyboardEvent('keydown', {\n        keyCode: keyCode, bubbles: true, cancelable: true\n    });\n    document.dispatchEvent(event);\n}"
  },
  {
    "path": "src/ApvlvCmds.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvCmds.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <Qt>\n#include <cctype>\n#include <cstring>\n\n#include \"ApvlvCmds.h\"\n#include \"ApvlvView.h\"\n\nnamespace apvlv\n{\nusing namespace std;\nusing namespace Qt;\n\nStringKeyMap Command::mKeyMap = {\n  { \"<BS>\", Key_Backspace },      { \"<Tab>\", Key_Tab },\n  { \"<CR>\", Key_Return },         { \"<Esc>\", Key_Escape },\n  { \"<Space>\", Key_Space },       { \"<lt>\", Key_Less },\n  { \"<Bslash>\", Key_Backslash },  { \"<Bar>\", Key_Bar },\n  { \"<Del>\", Key_Delete },        { \"<Up>\", Key_Up },\n  { \"<Down>\", Key_Down },         { \"<Left>\", Key_Left },\n  { \"<Right>\", Key_Right },       { \"<Help>\", Key_Help },\n  { \"<Insert>\", Key_Insert },     { \"<Home>\", Key_Home },\n  { \"<End>\", Key_End },           { \"<PageUp>\", Key_PageUp },\n  { \"<PageDown>\", Key_PageDown }, { \"<KP_Up>\", Key_Up },\n  { \"<KP_Down>\", Key_Down },      { \"<KP_Left>\", Key_Left },\n  { \"<KP_Right>\", Key_Right },    { \"<KP_Prior>\", Key_MediaPrevious },\n  { \"<KP_Next>\", Key_MediaNext }, { \"<KP_Home>\", Key_Home },\n  { \"<KP_End>\", Key_End },\n};\n\nCommandMap ApvlvCmds::mMaps;\n\nstatic int\nkeyToControlChar (QKeyEvent *key)\n{\n  int char_key = key->key ();\n  if (key->modifiers () & Qt::ShiftModifier)\n    {\n      char_key = toupper (char_key);\n    }\n  else\n    {\n      char_key = tolower (char_key);\n    }\n  if (key->modifiers () & ControlModifier)\n    char_key = ctrlValue (char_key);\n\n  return char_key;\n}\n\nconstexpr static bool\nisModifierKey (uint k)\n{\n  switch (k)\n    {\n    case Key_Shift:\n    case Key_CapsLock:\n    case Key_Meta:\n    case Key_Alt:\n    case Key_Super_L:\n    case Key_Super_R:\n    case Key_Hyper_L:\n    case Key_Hyper_R:\n    case Key_Control:\n      return true;\n    default:\n      return false;\n    }\n}\n\nCommand::Command ()\n    : mType (CmdType::CT_CMD), mHasPreCount (false), mPreCount (1),\n      mOrigin (nullptr)\n{\n}\n\nvoid\nCommand::type (CmdType type)\n{\n  mType = type;\n}\n\nCmdType\nCommand::type ()\n{\n  return mType;\n}\n\nvoid\nCommand::push (string_view sv, CmdType type)\n{\n  mType = type;\n\n  mHasPreCount = false;\n  mPreCount = 1;\n\n  auto s = sv.data ();\n  if (isdigit (*s))\n    {\n      mHasPreCount = true;\n      mPreCount = (signed int)strtol (s, nullptr, 10);\n      do\n        {\n          s++;\n        }\n      while (isdigit (*s));\n    }\n\n  if (*s == ':' || *s == '/' || *s == '?')\n    {\n      mStrCommand = s;\n      mType = CmdType::CT_STRING;\n\n      size_t off = mStrCommand.find (\"<CR>\");\n      if (off != string::npos)\n        {\n          mStrCommand.erase (off, mStrCommand.length () - off);\n          mType = CmdType::CT_STRING_RETURN;\n          mNext = make_unique<Command> ();\n          mNext->push (s + off + 4);\n        }\n      qDebug () << \"set string type command: [\" << mStrCommand << \"]\";\n      return;\n    }\n\n  while (*s != '\\0')\n    {\n      s = append (s);\n    }\n}\n\nvoid\nCommand::process (ApvlvView *view)\n{\n  if (type () == CmdType::CT_STRING)\n    {\n      view->promptCommand (mStrCommand.c_str ());\n    }\n  else if (type () == CmdType::CT_STRING_RETURN)\n    {\n      view->run (mStrCommand.c_str ());\n    }\n  else\n    {\n      for (uint k = 0; k < keyVals ()->size (); ++k)\n        {\n          int key = keyval (k);\n          if (key > 0)\n            view->process (mHasPreCount, preCount (), keyval (k));\n        }\n    }\n\n  if (next () != nullptr)\n    {\n      next ()->process (view);\n    }\n}\n\nbool\nCommand::append (QKeyEvent *key)\n{\n  if (isModifierKey (key->key ()))\n    return false;\n\n  auto char_key = keyToControlChar (key);\n  mKeyVals.push_back (char_key);\n  return true;\n}\n\nconst char *\nCommand::append (const char *s)\n{\n  size_t len;\n  const char *e = strchr (s, '>');\n\n  len = strlen (s);\n\n  if (len >= 4 && *s == '<' && (*e != '\\0' && *(s + 2) != '-'))\n    {\n      e++;\n      for (const auto &it : mKeyMap)\n        {\n          if (it.first.compare (0, e - s, s) == 0)\n            {\n              mKeyVals.push_back (it.second);\n              return e;\n            }\n        }\n    }\n\n  if (len >= 5 && s[0] == '<' && s[2] == '-' && s[4] == '>')\n    {\n      if (s[1] == 'C')\n        {\n          mKeyVals.push_back (ctrlValue (s[3]));\n        }\n      else\n        {\n          qWarning () << \"Can't recognize the symbol: \" << s;\n        }\n      return s + 5;\n    }\n  else\n    {\n      mKeyVals.push_back (s[0]);\n      return s + 1;\n    }\n}\n\nvoid\nCommand::setPreCount (int precount)\n{\n  mPreCount = precount;\n  mHasPreCount = true;\n}\n\nint\nCommand::preCount () const\n{\n  return mPreCount;\n}\n\nvoid\nCommand::origin (Command *ori)\n{\n  mOrigin = ori;\n}\n\nCommand *\nCommand::origin ()\n{\n  return mOrigin;\n}\n\nCommandKeyList *\nCommand::keyVals ()\n{\n  return &mKeyVals;\n}\n\nCommandKeyList\nCommand::keyvalv ()\n{\n  return mKeyVals;\n}\n\nCommand *\nCommand::next ()\n{\n  return mNext.get ();\n}\n\nint\nCommand::keyval (uint id)\n{\n  return id >= mKeyVals.size () ? -1 : mKeyVals[id];\n}\n\nvoid\nApvlvCmds::buildCommandMap (string_view os, string_view ms)\n{\n  Command fir;\n  fir.push (os);\n\n  auto *secp = new Command ();\n  secp->push (ms);\n\n  for (auto &mMap : mMaps)\n    {\n      if (mMap.first == fir.keyvalv ())\n        {\n          delete mMap.second;\n          mMap.second = secp;\n          return;\n        }\n    }\n\n  mMaps[fir.keyvalv ()] = secp;\n}\n\nApvlvCmds::ApvlvCmds (ApvlvView *view)\n{\n  mView = view;\n\n  mState = CmdState::CMD_OK;\n\n  mTimeoutTimer = make_unique<QTimer> (this);\n  QObject::connect (mTimeoutTimer.get (), SIGNAL (timeout ()), this,\n                    SLOT (timeoutCallback ()));\n}\n\nApvlvCmds::~ApvlvCmds ()\n{\n  if (mTimeoutTimer->isActive ())\n    {\n      mTimeoutTimer->stop ();\n    }\n}\n\nvoid\nApvlvCmds::append (QKeyEvent *gev)\n{\n  if (mTimeoutTimer->isActive ())\n    {\n      mTimeoutTimer->stop ();\n    }\n\n  if (mState == CmdState::GETTING_CMD)\n    {\n      CommandKeyList v = mCmdHead->keyvalv ();\n      v.push_back (keyToControlChar (gev));\n      CmdReturn r = isMapCommand (&v);\n      if (r == CmdReturn::NO_MATCH)\n        {\n          process (mCmdHead.get ());\n          mCmdHead.release ();\n          mState = CmdState::CMD_OK;\n        }\n    }\n\n  if (mCmdHead == nullptr)\n    mCmdHead = make_unique<Command> ();\n\n  if (mState == CmdState::CMD_OK)\n    {\n      if (isdigit (int (gev->key ())) && gev->key () != '0')\n        {\n          auto c = char (gev->key ());\n          mCountString += c;\n          mState = CmdState::GETTING_COUNT;\n          mTimeoutTimer->start (3000);\n          return;\n        }\n    }\n\n  else if (mState == CmdState::GETTING_COUNT)\n    {\n      if (isdigit (int (gev->key ())))\n        {\n          auto c = char (gev->key ());\n          mCountString += c;\n          mTimeoutTimer->start (3000);\n          return;\n        }\n      else\n        {\n          if (!mCountString.empty ())\n            {\n              mCmdHead->setPreCount (\n                  int (strtol (mCountString.c_str (), nullptr, 10)));\n              mCountString = \"\";\n            }\n        }\n    }\n\n  bool valid = mCmdHead->append (gev);\n  if (!valid)\n    {\n      mTimeoutTimer->start (3000);\n      return;\n    }\n\n  mState = CmdState::GETTING_CMD;\n  CmdReturn ret = isMapCommand (mCmdHead->keyVals ());\n  if (ret == CmdReturn::NEED_MORE)\n    {\n      mTimeoutTimer->start (3000);\n      return;\n    }\n\n  Command *pcmd;\n  if (ret == CmdReturn::MATCH)\n    {\n      pcmd = getMapCommand (mCmdHead.get ());\n      pcmd->origin (mCmdHead.get ());\n      process (pcmd);\n      pcmd->origin (nullptr);\n      pcmd = nullptr;\n    }\n  else\n    {\n      pcmd = process (mCmdHead.get ());\n    }\n\n  mCmdHead.reset (pcmd);\n  mState = CmdState::CMD_OK;\n}\n\nCommand *\nApvlvCmds::process (Command *cmd)\n{\n  uint times = 1;\n  Command *orig = cmd->origin ();\n  if (orig != nullptr)\n    {\n      times = orig->preCount ();\n    }\n\n  for (uint i = 0; i < times; ++i)\n    {\n      cmd->process (mView);\n    }\n  return orig;\n}\n\nCmdReturn\nApvlvCmds::isMapCommand (CommandKeyList *ack)\n{\n  for (auto &mMap : mMaps)\n    {\n      if (*ack == mMap.first)\n        {\n          return CmdReturn::MATCH;\n        }\n      else\n        {\n          uint i;\n          for (i = 0; i < ack->size (); ++i)\n            {\n              if ((*ack)[i] != mMap.first[i])\n                break;\n            }\n\n          if (i == ack->size ())\n            {\n              return CmdReturn::NEED_MORE;\n            }\n        }\n    }\n\n  return CmdReturn::NO_MATCH;\n}\n\nCommand *\nApvlvCmds::getMapCommand (Command *cmd)\n{\n  auto it = mMaps.find (*cmd->keyVals ());\n  return it != mMaps.end () ? it->second : nullptr;\n}\n\nvoid\nApvlvCmds::timeoutCallback ()\n{\n  if (mCmdHead != nullptr)\n    {\n      process (mCmdHead.get ());\n      mCmdHead.release ();\n    }\n  mState = CmdState::CMD_OK;\n}\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/ApvlvCmds.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvCmds.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_CMDS_H_\n#define _APVLV_CMDS_H_\n\n#include <QKeyEvent>\n#include <QTimer>\n#include <map>\n#include <vector>\n\nnamespace apvlv\n{\nenum class CmdType\n{\n  CT_CMD,\n  CT_STRING,\n  CT_STRING_RETURN\n};\n\nenum class CmdState\n{\n  GETTING_COUNT,\n  GETTING_CMD,\n  CMD_OK,\n};\n\n// command type\nenum class CmdStatusType\n{\n  CMD_NONE,\n  CMD_MESSAGE,\n  CMD_CMD\n};\n\n// function return type\nenum class CmdReturn\n{\n  MATCH,\n  NEED_MORE,\n  NO_MATCH,\n};\n\n// because every unsigned char is < 256, so use this marco to stand for\n// Ctrl+char, Shift+char\nconstexpr int\nctrlValue (int c)\n{\n  return c + 256;\n}\n\nusing StringKeyMap = std::map<std::string, int>;\n\nclass Command;\nusing CommandKeyList = std::vector<int>;\nusing CommandMap = std::map<CommandKeyList, Command *>;\n\nclass ApvlvView;\nclass Command final\n{\npublic:\n  Command ();\n\n  ~Command () = default;\n\n  void process (ApvlvView *view);\n\n  void push (std::string_view s, CmdType type = CmdType::CT_CMD);\n\n  bool append (QKeyEvent *key);\n\n  const char *append (const char *s);\n\n  void type (CmdType type);\n\n  CmdType type ();\n\n  CommandKeyList *keyVals ();\n\n  CommandKeyList keyvalv ();\n\n  void setPreCount (int precount);\n\n  [[nodiscard]] int preCount () const;\n\n  int keyval (uint id);\n\n  Command *next ();\n\n  void origin (Command *cmd);\n\n  Command *origin ();\n\nprivate:\n  static StringKeyMap mKeyMap;\n\n  // command type\n  CmdType mType;\n\n  // if it has count\n  bool mHasPreCount;\n\n  // how to describe this command in .apvlvrc\n  // like <C-d><C-w>, <S-b>s, or :run, :vsp, ...\n  std::string mStrCommand;\n\n  // key's value list\n  CommandKeyList mKeyVals;\n\n  // cmd's pre count\n  int mPreCount;\n\n  // next command\n  std::unique_ptr<Command> mNext;\n\n  // when a key is map to other, this is the origin cmd.\n  // after a mapped key was processed, return to this cmds\n  Command *mOrigin;\n};\n\nclass ApvlvCmds : public QObject\n{\n  Q_OBJECT\npublic:\n  explicit ApvlvCmds (ApvlvView *view);\n\n  ~ApvlvCmds () override;\n\n  void append (QKeyEvent *gev);\n\n  static void buildCommandMap (std::string_view os, std::string_view ms);\n\nprivate:\n  Command *process (Command *cmd);\n\n  static CmdReturn isMapCommand (CommandKeyList *ack);\n\n  static Command *getMapCommand (Command *cmd);\n\n  static CommandMap mMaps;\n\n  std::unique_ptr<Command> mCmdHead;\n\n  // command view\n  ApvlvView *mView;\n\n  CmdState mState;\n\n  std::unique_ptr<QTimer> mTimeoutTimer;\n\n  std::string mCountString;\n\nprivate slots:\n  void timeoutCallback ();\n};\n}\n\n#endif\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/ApvlvCompletion.cc",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2008> Alf\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvCompletion.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QDebug>\n#include <filesystem>\n#include <string>\n\n#include \"ApvlvCompletion.h\"\n#include \"ApvlvUtil.h\"\n\nnamespace apvlv\n{\nusing namespace std;\n\nstring\nApvlvCompletion::complete (const string &prefix)\n{\n  auto iter = std::ranges::find_if (mItems,\n                                    [prefix] (const string &item)\n                                      {\n                                        return item.find (prefix) == 0;\n                                      });\n  if (iter != mItems.cend ())\n    return *iter;\n  else\n    return \"\";\n}\n\nvoid\nApvlvCompletion::addItems (const vector<string> &items)\n{\n  mItems.insert (mItems.end (), items.begin (), items.end ());\n}\n\nvoid\nApvlvCompletion::addPath (const string &path)\n{\n  vector<string> items;\n\n  auto fspath = filesystem::path{ path };\n  auto filename = fspath.filename ();\n  auto dirname = fspath.parent_path ();\n  for (auto &entry : filesystem::directory_iterator (dirname))\n    {\n      auto entry_filename = entry.path ().filename ().string ();\n      if (filename.empty () || entry_filename.find (filename.string ()) == 0)\n        {\n          auto item = entry.path ().string ()\n                      + (entry.is_directory () ? PATH_SEP_S : \"\");\n          qDebug () << \"add a item: \" << item;\n          items.emplace_back (item);\n        }\n    }\n\n  addItems (items);\n}\n\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/ApvlvCompletion.h",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2008> Alf\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvCompletion.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n#ifndef _APVLV_COMPLETION_H_\n#define _APVLV_COMPLETION_H_\n\n#include <string>\n#include <vector>\n\nnamespace apvlv\n{\n\nclass ApvlvCompletion final\n{\npublic:\n  explicit ApvlvCompletion (const std::vector<std::string> &items)\n      : mItems (items)\n  {\n  }\n  ApvlvCompletion () = default;\n  ~ApvlvCompletion () = default;\n\n  void addItems (const std::vector<std::string> &items);\n  void addPath (const std::string &path);\n\n  std::string complete (const std::string &np);\n\nprivate:\n  std::vector<std::string> mItems;\n};\n\n};\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/ApvlvDirectory.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvFrame.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QApplication>\n#include <QFile>\n#include <QHeaderView>\n#include <QInputDialog>\n#include <QLocale>\n#include <QMessageBox>\n#include <QTimeZone>\n#include <stack>\n\n#include \"ApvlvDirectory.h\"\n#include \"ApvlvFrame.h\"\n#include \"ApvlvNote.h\"\n#include \"ApvlvNoteWidget.h\"\n#include \"ApvlvParams.h\"\n#include \"ApvlvUtil.h\"\n\nnamespace apvlv\n{\nusing namespace std;\n\nstd::vector<const char *> Directory::ColumnString = {\n  QT_TR_NOOP (\"Title\"),     QT_TR_NOOP (\"Modified Time\"),\n  QT_TR_NOOP (\"File Size\"), QT_TR_NOOP (\"Tags\"),\n  QT_TR_NOOP (\"Score\"),\n};\nstd::vector<const char *> Directory::SortByColumnString = {\n  QT_TR_NOOP (\"Sort By Title\"),     QT_TR_NOOP (\"Sort By Modified Time\"),\n  QT_TR_NOOP (\"Sort By File Size\"), QT_TR_NOOP (\"Sort By Tags\"),\n  QT_TR_NOOP (\"Sort By Score\"),\n};\nstd::vector<const char *> Directory::FilterTypeString = {\n  QT_TR_NOOP (\"Filter Title\"),\n  QT_TR_NOOP (\"Filter File Name\"),\n  QT_TR_NOOP (\"Filter Modified Time >=\"),\n  QT_TR_NOOP (\"Filter Modified Time <=\"),\n  QT_TR_NOOP (\"Filter File Size >=\"),\n  QT_TR_NOOP (\"Filter File Size <=\"),\n  QT_TR_NOOP (\"Filter Tag\"),\n  QT_TR_NOOP (\"Filter Score >=\"),\n  QT_TR_NOOP (\"Filter Score <=\"),\n};\n\nvoid\nContentTree::keyPressEvent (QKeyEvent *event)\n{\n  event->ignore ();\n}\n\nDirectory::Directory ()\n{\n  setLayout (&mLayout);\n  mLayout.addWidget (&mToolBar, 0);\n  mLayout.addWidget (&mTreeWidget);\n  setupToolBar ();\n  setupTree ();\n  auto guioptions\n      = ApvlvParams::instance ()->getStringOrDefault (\"guioptions\");\n  if (guioptions.find ('S') == string::npos)\n    {\n      mToolBar.hide ();\n    }\n  QTimer::singleShot (50, this, SLOT (selectFirstItem ()));\n}\n\nvoid\nDirectory::setupToolBar ()\n{\n  mToolBar.addWidget (&mFilterText);\n  QObject::connect (&mFilterText, SIGNAL (textEdited (const QString &)), this,\n                    SLOT (onFilter ()));\n  mToolBar.addSeparator ();\n  mToolBar.addWidget (&mFilterType);\n  for (auto const &str : FilterTypeString)\n    {\n      mFilterType.addItem (tr (str));\n    }\n  mToolBar.addSeparator ();\n  QObject::connect (&mFilterType, SIGNAL (activated (int)), this,\n                    SLOT (onFilter ()));\n\n  auto refresh = mToolBar.addAction (tr (\"Refresh\"));\n  refresh->setIcon (QIcon::fromTheme (QIcon::ThemeIcon::ViewRefresh));\n  QObject::connect (refresh, SIGNAL (triggered (bool)), this,\n                    SLOT (onRefresh ()));\n  mToolBar.addSeparator ();\n\n  auto expand_all = mToolBar.addAction (tr (\"Expand All\"));\n  expand_all->setIcon (QIcon::fromTheme (QIcon::ThemeIcon::ListAdd));\n  QObject::connect (expand_all, SIGNAL (triggered (bool)), &mTreeWidget,\n                    SLOT (expandAll ()));\n  auto collapse_all = mToolBar.addAction (tr (\"Collapse All\"));\n  collapse_all->setIcon (QIcon::fromTheme (QIcon::ThemeIcon::ListRemove));\n  QObject::connect (collapse_all, SIGNAL (triggered (bool)), &mTreeWidget,\n                    SLOT (collapseAll ()));\n  mToolBar.addSeparator ();\n\n  mToolBar.addWidget (&mSortType);\n  for (auto const &str : SortByColumnString)\n    {\n      mSortType.addItem (tr (str));\n    }\n  QObject::connect (&mSortType, SIGNAL (activated (int)), this,\n                    SLOT (sortBy (int)));\n}\n\nvoid\nDirectory::setupTree ()\n{\n  mTreeWidget.setColumnCount (3);\n  mTreeWidget.setColumnWidth (static_cast<int> (Column::Title), 300);\n  mTreeWidget.setColumnWidth (static_cast<int> (Column::MTime), 150);\n  mTreeWidget.setColumnWidth (static_cast<int> (Column::FileSize), 50);\n  mTreeWidget.setColumnWidth (static_cast<int> (Column::Tags), 100);\n  mTreeWidget.setColumnWidth (static_cast<int> (Column::Score), 50);\n  mTreeWidget.setSortingEnabled (false);\n  mTreeWidget.setHeaderHidden (false);\n  QStringList labels;\n  for (auto const &str : ColumnString)\n    {\n      labels.append (tr (str));\n    }\n  mTreeWidget.setHeaderLabels (labels);\n\n  auto headerview = mTreeWidget.header ();\n  headerview->setSectionsClickable (true);\n  QObject::connect (headerview, SIGNAL (sectionClicked (int)), this,\n                    SLOT (sortBy (int)));\n\n  mTreeWidget.setVerticalScrollMode (\n      QAbstractItemView::ScrollMode::ScrollPerItem);\n  mTreeWidget.setSelectionBehavior (\n      QAbstractItemView::SelectionBehavior::SelectRows);\n  mTreeWidget.setSelectionMode (\n      QAbstractItemView::SelectionMode::ExtendedSelection);\n\n  mTypeIcons[FileIndexType::DIR] = QIcon (IconDir.c_str ());\n  mTypeIcons[FileIndexType::FILE] = QIcon (IconFile.c_str ());\n  mTypeIcons[FileIndexType::PAGE] = QIcon (IconPage.c_str ());\n\n  QObject::connect (&mTreeWidget,\n                    SIGNAL (itemActivated (QTreeWidgetItem *, int)), this,\n                    SLOT (onRowActivated (QTreeWidgetItem *, int)));\n  QObject::connect (&mTreeWidget,\n                    SIGNAL (itemDoubleClicked (QTreeWidgetItem *, int)), this,\n                    SLOT (onRowDoubleClicked ()));\n  mTreeWidget.setContextMenuPolicy (Qt::ContextMenuPolicy::CustomContextMenu);\n  QObject::connect (&mTreeWidget,\n                    SIGNAL (customContextMenuRequested (const QPoint &)),\n                    this, SLOT (onContextMenuRequest (const QPoint &)));\n}\n\nbool\nDirectory::isReady ()\n{\n  return (mTreeWidget.topLevelItemCount () > 0);\n}\n\nvoid\nDirectory::setIndex (const FileIndex &index)\n{\n  using enum FileIndexType;\n  if (mTreeWidget.topLevelItemCount () == 0 || index.type == DIR)\n    {\n      refreshIndex (index);\n      return;\n    }\n\n  auto cur_index = currentItemFileIndex ();\n  if (mIndex.type == DIR && cur_index->type == FILE && index.type == FILE)\n    {\n      if (cur_index->mChildrenIndex.empty ())\n        {\n          auto cur_item = selectedTreeItem ();\n          cur_index->moveChildChildren (index);\n          for (auto &child : cur_index->mChildrenIndex)\n            {\n              setIndex (child, cur_item);\n            }\n        }\n    }\n}\n\nvoid\nDirectory::preloadTags (const std::string &path)\n{\n  if (path.empty ())\n    {\n      return;\n    }\n\n  const auto &exts = FileFactory::supportFileExts ();\n  for (const auto &entry :\n       filesystem::recursive_directory_iterator (filesystem::path (path)))\n    {\n      if (!entry.is_regular_file ())\n        continue;\n\n      const auto &p = entry.path ();\n      if (std::find (exts.begin (), exts.end (), p.extension ())\n          == exts.end ())\n        continue;\n\n      const auto &np = Note::notePathOfPath (p.string ());\n      const auto &n = make_unique<Note> ();\n      if (n->load (np))\n        {\n          const auto &tags = n->tag ();\n          for (const auto &tag : tags)\n            {\n              mTags.append (QString::fromLocal8Bit (tag));\n            }\n        }\n    }\n}\n\nvoid\nDirectory::setItemSelected (QTreeWidgetItem *item)\n{\n  auto selitems = mTreeWidget.selectedItems ();\n  for (auto selitem : selitems)\n    {\n      selitem->setSelected (false);\n    }\n\n  auto parent = item->parent ();\n  while (parent)\n    {\n      mTreeWidget.expandItem (parent);\n      parent = parent->parent ();\n    }\n\n  item->setSelected (true);\n  if (item->isExpanded ())\n    mTreeWidget.collapseItem (item);\n  mTreeWidget.scrollToItem (item);\n}\n\nvoid\nDirectory::setIndex (FileIndex &index, QTreeWidgetItem *root_itr)\n{\n  auto itr = new QTreeWidgetItem ();\n  setFileIndexToTreeItem (itr, &index);\n\n  root_itr->addChild (itr);\n\n  for (auto &child : index.mChildrenIndex)\n    {\n      setIndex (child, itr);\n    }\n}\n\nvoid\nDirectory::refreshIndex (const FileIndex &index)\n{\n  mTreeWidget.clear ();\n\n  mIndex = index;\n\n  for (auto &child : mIndex.mChildrenIndex)\n    {\n      setIndex (child, mTreeWidget.invisibleRootItem ());\n    }\n\n  sortItems (mTreeWidget.invisibleRootItem ());\n\n  preloadTags (mIndex.path);\n\n  QTimer::singleShot (50, this, SLOT (selectFirstItem ()));\n}\n\nvoid\nDirectory::setFileIndexToTreeItem (QTreeWidgetItem *item, FileIndex *index)\n{\n  using enum Column;\n  auto variant = QVariant::fromValue<FileIndex *> (index);\n  item->setData (static_cast<int> (Title), Qt::UserRole, variant);\n  item->setText (static_cast<int> (Title),\n                 QString::fromLocal8Bit (index->title.c_str ()));\n  item->setIcon (static_cast<int> (Title), mTypeIcons[index->type]);\n  item->setToolTip (static_cast<int> (Title),\n                    QString::fromLocal8Bit (index->path));\n\n  using enum FileIndexType;\n  if (index->type == FILE)\n    {\n      const auto date = QDateTime::fromSecsSinceEpoch (\n          index->mtime, QTimeZone::systemTimeZone ());\n      item->setText (static_cast<int> (MTime),\n                     date.toString (\"yyyy-MM-dd HH:mm:ss\"));\n      auto size = QLocale ().formattedDataSize (index->size);\n      item->setText (static_cast<int> (FileSize), size);\n      item->setText (static_cast<int> (Tags),\n                     QString::fromLocal8Bit (index->tags));\n      item->setToolTip (static_cast<int> (Tags),\n                        QString::fromLocal8Bit (index->tags));\n      const auto score\n          = index->score > 0 ? QString::number (index->score) : \"\";\n      item->setText (static_cast<int> (Score), score);\n    }\n}\n\nFileIndex *\nDirectory::getFileIndexFromTreeItem (QTreeWidgetItem *item)\n{\n  if (item == nullptr)\n    return nullptr;\n\n  auto varaint = item->data (static_cast<int> (Column::Title), Qt::UserRole);\n  auto index = varaint.value<FileIndex *> ();\n  return index;\n}\n\nFileIndex *\nDirectory::treeItemToFileIndex (QTreeWidgetItem *item)\n{\n  while (item != nullptr)\n    {\n      auto index = getFileIndexFromTreeItem (item);\n      if (index->type == FileIndexType::FILE)\n        return index;\n      else\n        item = item->parent ();\n    }\n\n  return nullptr;\n}\n\nvoid\nDirectory::filterItemBy (QTreeWidgetItem *root, const filterFunc &filter_func)\n{\n  std::stack<QTreeWidgetItem *> item_stack;\n  for (auto i = 0; i < root->childCount (); ++i)\n    {\n      auto item = root->child (i);\n      item_stack.push (item);\n    }\n\n  while (!item_stack.empty ())\n    {\n      auto item = item_stack.top ();\n      item_stack.pop ();\n\n      auto index = getFileIndexFromTreeItem (item);\n      auto [is_filter, same_as_file] = filter_func (index);\n      if (is_filter)\n        {\n          item->setHidden (false);\n\n          auto parent = item->parent ();\n          while (parent)\n            {\n              parent->setHidden (false);\n              parent = parent->parent ();\n            }\n        }\n      else\n        {\n          item->setHidden (true);\n        }\n\n      if (index->type == FileIndexType::FILE && same_as_file)\n        {\n          setItemChildrenFilter (item, is_filter);\n        }\n      else\n        {\n          for (int i = 0; i < item->childCount (); ++i)\n            {\n              auto child_item = item->child (i);\n              item_stack.push (child_item);\n            }\n        }\n    }\n}\n\nvoid\nDirectory::setItemChildrenFilter (QTreeWidgetItem *root, bool is_filter)\n{\n  filterItemBy (root,\n                [is_filter] (const FileIndex *a) -> filterFuncReturn\n                  {\n                    return { is_filter, false };\n                  });\n}\n\nQTreeWidgetItem *\nDirectory::findTreeWidgetItem (QTreeWidgetItem *itr, FileIndexType type,\n                               const string &path, int pn,\n                               const string &anchor)\n{\n  if (itr == nullptr)\n    return nullptr;\n\n  auto index = getFileIndexFromTreeItem (itr);\n  if (index == nullptr || index->type != type)\n    {\n      for (auto ind = 0; ind < itr->childCount (); ++ind)\n        {\n          auto child_itr = itr->child (ind);\n          auto citr = findTreeWidgetItem (child_itr, type, path, pn, anchor);\n          if (citr)\n            return citr;\n        }\n\n      return nullptr;\n    }\n\n  auto file_index = treeItemToFileIndex (itr);\n  if (file_index == nullptr)\n    {\n      if (index->page == pn && (anchor.empty () || index->anchor == anchor))\n        {\n          return itr;\n        }\n\n      return nullptr;\n    }\n\n  if (file_index->path == path && index->page == pn\n      && (anchor.empty () || index->anchor == anchor))\n    {\n      return itr;\n    }\n\n  return nullptr;\n}\n\nbool\nDirectory::setCurrentIndex (const string &path, int pn, const string &anchor)\n{\n  auto itr = selectedTreeItem ();\n\n  auto fitr = findTreeWidgetItem (itr, FileIndexType::PAGE, path, pn, anchor);\n\n  if (fitr == nullptr)\n    fitr = findTreeWidgetItem (mTreeWidget.invisibleRootItem (),\n                               FileIndexType::PAGE, path, pn, anchor);\n\n  if (fitr == nullptr)\n    fitr = findTreeWidgetItem (mTreeWidget.invisibleRootItem (),\n                               FileIndexType::FILE, path, pn, anchor);\n\n  if (fitr)\n    {\n      setItemSelected (fitr);\n      return true;\n    }\n\n  return false;\n}\n\nvoid\nDirectory::scrollUp (int times)\n{\n  auto item = selectedTreeItem ();\n  if (item == nullptr)\n    return;\n\n  auto parent = item->parent ();\n  if (parent == nullptr)\n    {\n      parent = mTreeWidget.invisibleRootItem ();\n    }\n\n  auto index = parent->indexOfChild (item);\n  if (index > 0)\n    {\n      auto new_index = index > times ? index - times : 0;\n      auto itr = parent->child (new_index);\n      setItemSelected (itr);\n    }\n}\n\nvoid\nDirectory::scrollDown (int times)\n{\n  auto item = selectedTreeItem ();\n  if (item == nullptr)\n    return;\n\n  auto parent = item->parent ();\n  if (parent == nullptr)\n    {\n      parent = mTreeWidget.invisibleRootItem ();\n    }\n\n  auto index = parent->indexOfChild (item);\n  auto count = parent->childCount ();\n  auto new_index = index + times < count ? index + times : count - 1;\n  auto itr = parent->child (new_index);\n  setItemSelected (itr);\n}\n\nvoid\nDirectory::scrollLeft (int times)\n{\n  auto item = selectedTreeItem ();\n  if (item == nullptr)\n    return;\n\n  if (item->parent () == nullptr)\n    return;\n\n  auto parent = item->parent ();\n\n  while (--times > 0 && parent->parent ())\n    {\n      parent = parent->parent ();\n    }\n\n  setItemSelected (parent);\n}\n\nvoid\nDirectory::scrollRight (int times)\n{\n  auto item = selectedTreeItem ();\n  if (item == nullptr)\n    return;\n\n  if (item->childCount () == 0)\n    return;\n\n  item = item->child (0);\n\n  while (--times > 0 && item->childCount () > 0)\n    {\n      item = item->child (0);\n    }\n  setItemSelected (item);\n}\n\nvoid\nDirectory::tag ()\n{\n  auto item = selectedTreeItem ();\n  auto cur = getFileIndexFromTreeItem (item);\n  if (cur == nullptr)\n    return;\n  if (cur->type != FileIndexType::FILE)\n    return;\n  auto path = Note::notePathOfPath (cur->path);\n  auto note = make_unique<Note> ();\n  filesystem::path note_path (path);\n  if (exists (note_path))\n    {\n      if (note->load (path) == false)\n        {\n          auto msg = QString (tr (\"load note of %s error\")).arg (cur->path.c_str());\n          QMessageBox::warning (nullptr, tr (\"error\"), msg);\n          return;\n        }\n    }\n\n  filesystem::path file_path{ cur->path };\n  string filename = file_path.filename ();\n  auto ans = NoteDialog::getTag (filename, note->tag (), mTags);\n  if (ans.isEmpty ())\n    {\n      return;\n    }\n\n  note->addTag (ans.toStdString ());\n  if (!exists (note_path))\n    {\n      note->dump (path);\n    }\n\n  auto tags = note->tagString ();\n  if (cur->tags != tags)\n    {\n      cur->tags = tags;\n      item->setText (static_cast<int> (Column::Tags),\n                     QString::fromLocal8Bit (cur->tags));\n    }\n}\n\nvoid\nDirectory::onFileRename ()\n{\n  auto items = mTreeWidget.selectedItems ();\n  if (items.isEmpty ())\n    return;\n\n  auto item = items[0];\n  auto index = getFileIndexFromTreeItem (item);\n  if (index->type != FileIndexType::FILE)\n    return;\n\n  auto qpath = QString::fromLocal8Bit (index->path);\n  auto text = QString (tr (\"Input new name of %1\")).arg (qpath);\n  auto user_text = QInputDialog::getText (this, tr (\"Rename\"), text,\n                                          QLineEdit::Normal, qpath);\n  auto nname = user_text.trimmed ();\n  if (nname.isEmpty ())\n    return;\n\n  if (QFile (qpath).rename (nname))\n    {\n      index->path = nname.toStdString ();\n      index->title = index->path.substr (index->path.rfind ('/') + 1);\n      setFileIndexToTreeItem (item, index);\n    }\n  else\n    {\n      text = QString (tr (\"Rename %1 to %2 failed\")).arg (qpath).arg (nname);\n      QMessageBox::warning (this, tr (\"Warning\"), text);\n    }\n}\n\nvoid\nDirectory::onFileDelete ()\n{\n  auto items = mTreeWidget.selectedItems ();\n  if (items.isEmpty ())\n    return;\n\n  auto msg_res = QMessageBox::No;\n  for (auto item : items)\n    {\n      auto index = getFileIndexFromTreeItem (item);\n      if (index->type != FileIndexType::FILE)\n        continue;\n\n      auto qpath = QString::fromLocal8Bit (index->path);\n      auto text\n          = QString (tr (\"Will delete the \\n%1, confirm ?\")).arg (qpath);\n      if (msg_res == QMessageBox::No || msg_res == QMessageBox::Yes)\n        {\n          auto buttons = QMessageBox::Yes | QMessageBox::No;\n          if (items.size () > 1)\n            buttons = QMessageBox::Yes | QMessageBox::YesToAll\n                      | QMessageBox::No | QMessageBox::NoToAll;\n          msg_res = QMessageBox::question (this, tr (\"Confirm\"), text,\n                                           buttons, QMessageBox::No);\n        }\n\n      if (msg_res == QMessageBox::NoToAll)\n        return;\n\n      else if (msg_res == QMessageBox::No)\n        continue;\n\n      auto parent = item->parent ();\n      if (parent == nullptr)\n        {\n          auto offset = mTreeWidget.indexOfTopLevelItem (item);\n          mIndex.removeChild (*index);\n          item = mTreeWidget.takeTopLevelItem (offset);\n          delete item;\n        }\n      else\n        {\n          auto pindex = getFileIndexFromTreeItem (parent);\n          pindex->removeChild (*index);\n          parent->removeChild (item);\n        }\n\n      qDebug () << \"delete \" << qpath;\n      QFile::remove (qpath);\n    }\n}\n\nvoid\nDirectory::onRefresh ()\n{\n  refreshIndex (mIndex);\n}\n\nvoid\nDirectory::onFilter ()\n{\n  auto root = mTreeWidget.invisibleRootItem ();\n  auto cur = mFilterType.currentIndex ();\n  auto type = static_cast<FilterType> (cur);\n  auto text = mFilterText.text ().trimmed ();\n  if (text.isEmpty ())\n    {\n      setItemChildrenFilter (root, true);\n      return;\n    }\n\n  QDateTime datetime;\n  qint64 qsize;\n  decltype (FileIndex::size) size;\n  float score;\n\n  switch (type)\n    {\n    case FilterType::Title:\n      filterItemBy (root,\n                    [text] (const FileIndex *a) -> filterFuncReturn\n                      {\n                        return { a->title.find (text.toStdString ())\n                                     != string::npos,\n                                 false };\n                      });\n      break;\n\n    case FilterType::FileName:\n      filterItemBy (root,\n                    [text] (const FileIndex *a) -> filterFuncReturn\n                      {\n                        return { a->type == FileIndexType::FILE\n                                     && a->title.find (text.toStdString ())\n                                            != string::npos,\n                                 true };\n                      });\n      break;\n\n    case FilterType::MTimeBe:\n      datetime = QDateTime::fromString (text);\n      filterItemBy (root,\n                    [datetime] (const FileIndex *a) -> filterFuncReturn\n                      {\n                        return { a->type == FileIndexType::FILE\n                                     && a->mtime\n                                            >= datetime.toMSecsSinceEpoch (),\n                                 true };\n                      });\n      break;\n\n    case FilterType::MTimeLe:\n      datetime = QDateTime::fromString (text);\n      filterItemBy (root,\n                    [datetime] (const FileIndex *a) -> filterFuncReturn\n                      {\n                        return { a->type == FileIndexType::FILE\n                                     && a->mtime\n                                            <= datetime.toMSecsSinceEpoch (),\n                                 true };\n                      });\n      break;\n\n    case FilterType::FileSizeBe:\n      qsize = parseFormattedDataSize (text);\n      size = static_cast<decltype (size)> (qsize);\n      filterItemBy (root,\n                    [size] (const FileIndex *a) -> filterFuncReturn\n                      {\n                        return { a->type == FileIndexType::FILE\n                                     && a->size >= size,\n                                 true };\n                      });\n      break;\n\n    case FilterType::FileSizeLe:\n      qsize = parseFormattedDataSize (text);\n      size = static_cast<decltype (size)> (qsize);\n      filterItemBy (root,\n                    [size] (const FileIndex *a) -> filterFuncReturn\n                      {\n                        return { a->type == FileIndexType::FILE\n                                     && a->size <= size,\n                                 true };\n                      });\n      break;\n\n    case FilterType::Tags:\n      filterItemBy (root,\n                    [text] (const FileIndex *a) -> filterFuncReturn\n                      {\n                        return { a->type == FileIndexType::FILE\n                                     && a->tags.find (text.toStdString ())\n                                            != string::npos,\n                                 true };\n                      });\n      break;\n\n    case FilterType::ScoreBe:\n      score = QString (text).toFloat ();\n      filterItemBy (root,\n                    [score] (const FileIndex *a) -> filterFuncReturn\n                      {\n                        return { a->type == FileIndexType::FILE\n                                     && a->score >= score,\n                                 true };\n                      });\n      break;\n\n    case FilterType::ScoreLe:\n      score = QString (text).toFloat ();\n      filterItemBy (root,\n                    [score] (const FileIndex *a) -> filterFuncReturn\n                      {\n                        return { a->type == FileIndexType::FILE\n                                     && a->score <= score,\n                                 true };\n                      });\n      break;\n\n    default:\n      qWarning () << tr (\"Filter Type is invalid\");\n      break;\n    }\n\n  mTreeWidget.expandAll ();\n}\n\nvoid\nDirectory::sortItems (QTreeWidgetItem *tree_iter)\n{\n  stack<QTreeWidgetItem *> needSort;\n  needSort.push (tree_iter);\n\n  using itemState = pair<QTreeWidgetItem *, bool>;\n  while (!needSort.empty ())\n    {\n      auto root = needSort.top ();\n      needSort.pop ();\n\n      vector<itemState> item_list;\n      for (auto i = 0; i < root->childCount (); ++i)\n        {\n          auto item = root->child (i);\n          auto index = getFileIndexFromTreeItem (item);\n          if (index->type != FileIndexType::PAGE)\n            item_list.emplace_back (item, item->isExpanded ());\n        }\n\n      std::ranges::for_each (\n          item_list,\n          [this, &needSort] (itemState &p)\n            {\n              if (p.first->childCount () > 1)\n                {\n                  auto index = getFileIndexFromTreeItem (p.first);\n                  if (index && index->type == FileIndexType::DIR)\n                    {\n                      needSort.push (p.first);\n                    }\n                }\n            });\n\n      if (mSortAscending)\n        {\n          switch (mSortColumn)\n            {\n              using enum Column;\n            case Title:\n              std::ranges::sort (item_list, std::ranges::greater (),\n                                 [this] (itemState &p)\n                                   {\n                                     auto index\n                                         = getFileIndexFromTreeItem (p.first);\n                                     return index ? index->title : \"\";\n                                   });\n              break;\n            case MTime:\n              std::ranges::sort (item_list, std::ranges::greater (),\n                                 [this] (itemState &p)\n                                   {\n                                     auto index\n                                         = getFileIndexFromTreeItem (p.first);\n                                     return index ? index->mtime : 0;\n                                   });\n              break;\n            case FileSize:\n              std::ranges::sort (item_list, std::ranges::greater (),\n                                 [this] (itemState &p)\n                                   {\n                                     auto index\n                                         = getFileIndexFromTreeItem (p.first);\n                                     return index ? index->size : 0;\n                                   });\n              break;\n            case Tags:\n              std::ranges::sort (item_list, std::ranges::greater (),\n                                 [this] (itemState &p)\n                                   {\n                                     auto index\n                                         = getFileIndexFromTreeItem (p.first);\n                                     return index ? index->tags : \"\";\n                                   });\n              break;\n            case Score:\n              std::ranges::sort (item_list, std::ranges::greater (),\n                                 [this] (itemState &p)\n                                   {\n                                     auto index\n                                         = getFileIndexFromTreeItem (p.first);\n                                     return index ? index->score : 0.0f;\n                                   });\n              break;\n            }\n        }\n      else\n        {\n          switch (mSortColumn)\n            {\n              using enum Column;\n            case Title:\n              std::ranges::sort (item_list, std::ranges::less (),\n                                 [this] (itemState &p)\n                                   {\n                                     auto index\n                                         = getFileIndexFromTreeItem (p.first);\n                                     return index ? index->title : \"\";\n                                   });\n              break;\n            case MTime:\n              std::ranges::sort (item_list, std::ranges::less (),\n                                 [this] (itemState &p)\n                                   {\n                                     auto index\n                                         = getFileIndexFromTreeItem (p.first);\n                                     return index ? index->mtime : 0;\n                                   });\n              break;\n            case FileSize:\n              std::ranges::sort (item_list, std::ranges::less (),\n                                 [this] (itemState &p)\n                                   {\n                                     auto index\n                                         = getFileIndexFromTreeItem (p.first);\n                                     return index ? index->size : 0;\n                                   });\n              break;\n            case Tags:\n              std::ranges::sort (item_list, std::ranges::less (),\n                                 [this] (itemState &p)\n                                   {\n                                     auto index\n                                         = getFileIndexFromTreeItem (p.first);\n                                     return index ? index->tags : \"\";\n                                   });\n              break;\n            case Score:\n              std::ranges::sort (item_list, std::ranges::less (),\n                                 [this] (itemState &p)\n                                   {\n                                     auto index\n                                         = getFileIndexFromTreeItem (p.first);\n                                     return index ? index->score : 0.0f;\n                                   });\n              break;\n            }\n        }\n\n      mTreeWidget.setUpdatesEnabled (false);\n      for (auto i = 0;\n           static_cast<decltype (item_list.size ())> (i) < item_list.size ();\n           ++i)\n        {\n          auto p = item_list[i];\n          auto oi = root->indexOfChild (p.first);\n          if (oi != i)\n            {\n              root->takeChild (oi);\n              root->insertChild (i, p.first);\n              p.first->setExpanded (p.second);\n            }\n        }\n      mTreeWidget.setUpdatesEnabled (true);\n    }\n\n  mTreeWidget.update ();\n}\n\nvoid\nDirectory::onRowActivated ([[maybe_unused]] QTreeWidgetItem *item,\n                           [[maybe_unused]] int column)\n{\n  mFrame->directoryShowPage (currentItemFileIndex (), true);\n  mFrame->toggledControlDirectory (true);\n}\n\nvoid\nDirectory::onRowDoubleClicked ()\n{\n  parentWidget ()->setFocus ();\n}\n\nvoid\nDirectory::onContextMenuRequest (const QPoint &point)\n{\n  auto items = mTreeWidget.selectedItems ();\n  if (items.isEmpty ())\n    return;\n\n  auto item = items[0];\n  auto index = getFileIndexFromTreeItem (item);\n  if (index->type == FileIndexType::FILE)\n    {\n      mItemMenu.clear ();\n      if (items.size () == 1)\n        {\n          auto rename_action = mItemMenu.addAction (tr (\"Rename File\"));\n          QObject::connect (rename_action, SIGNAL (triggered (bool)), this,\n                            SLOT (onFileRename ()));\n        }\n      if (std::all_of (items.begin (), items.end (),\n                       [this] (QTreeWidgetItem *a)\n                         {\n                           auto i = getFileIndexFromTreeItem (a);\n                           return i && i->type == FileIndexType::FILE;\n                         }))\n        {\n          auto del_action = mItemMenu.addAction (tr (\"Delete File\"));\n          del_action->setIcon (\n              QIcon::fromTheme (QIcon::ThemeIcon::EditDelete));\n          QObject::connect (del_action, SIGNAL (triggered (bool)), this,\n                            SLOT (onFileDelete ()));\n        }\n      mItemMenu.popup (mTreeWidget.mapToGlobal (point));\n    }\n}\n\nvoid\nDirectory::selectFirstItem ()\n{\n  if (setCurrentIndex (mFrame->filename (), mFrame->pageNumber (), \"\"))\n    return;\n\n  if (mTreeWidget.topLevelItemCount () > 0)\n    {\n      auto itr = mTreeWidget.topLevelItem (0);\n      setItemSelected (itr);\n    }\n}\n\nFileIndex *\nDirectory::currentItemFileIndex ()\n{\n  auto item = selectedTreeItem ();\n  auto index = getFileIndexFromTreeItem (item);\n  return index;\n}\n\nFileIndex *\nDirectory::currentFileFileIndex ()\n{\n  auto item = selectedTreeItem ();\n  auto index = treeItemToFileIndex (item);\n  return index;\n}\n\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/ApvlvDirectory.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvFrame.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_CONTENT_H_\n#define _APVLV_CONTENT_H_\n\n#include <QComboBox>\n#include <QMenu>\n#include <QTimer>\n#include <QToolBar>\n#include <QTreeWidgetItem>\n#include <QVBoxLayout>\n#include <iostream>\n#include <map>\n#include <string>\n\n#include \"ApvlvFile.h\"\n#include \"ApvlvUtil.h\"\n#include \"ApvlvWidget.h\"\n\nnamespace apvlv\n{\n\nclass ContentTree : public QTreeWidget\n{\nprotected:\n  void keyPressEvent (QKeyEvent *event) override;\n};\n\nclass ApvlvFrame;\nclass Directory final : public QFrame\n{\n  Q_OBJECT\npublic:\n  Directory ();\n\n  ~Directory () override = default;\n\n  bool isReady ();\n\n  enum class Column : int\n  {\n    Title = 0,\n    MTime,\n    FileSize,\n    Tags,\n    Score\n  };\n  static std::vector<const char *> ColumnString;\n  static std::vector<const char *> SortByColumnString;\n\n  enum class FilterType : int\n  {\n    Title = 0,\n    FileName,\n    MTimeBe,\n    MTimeLe,\n    FileSizeBe,\n    FileSizeLe,\n    Tags,\n    ScoreBe,\n    ScoreLe\n  };\n  static std::vector<const char *> FilterTypeString;\n\n  FileIndex *currentItemFileIndex ();\n\n  FileIndex *currentFileFileIndex ();\n\n  QTreeWidgetItem *findTreeWidgetItem (QTreeWidgetItem *itr,\n                                       FileIndexType type,\n                                       const std::string &path, int pn,\n                                       const std::string &anchor);\n\n  bool setCurrentIndex (const std::string &path, int pn,\n                        const std::string &anchor);\n\n  void\n  setFrame (ApvlvFrame *frame)\n  {\n    mFrame = frame;\n  }\n\n  void\n  focusFilter ()\n  {\n    QTimer::singleShot (50, &mFilterText, SLOT (setFocus ()));\n  }\n\n  void scrollUp (int times);\n\n  void scrollDown (int times);\n\n  void scrollLeft (int times);\n\n  void scrollRight (int times);\n\n  void tag ();\n\n  void\n  setActive (bool active)\n  {\n    if (active)\n      {\n        QTimer::singleShot (50, &mTreeWidget, SLOT (setFocus ()));\n      }\n    else\n      {\n        mTreeWidget.clearFocus ();\n      }\n  }\n\n  bool\n  isActive ()\n  {\n    return mTreeWidget.hasFocus ();\n  }\n\nprivate:\n  QVBoxLayout mLayout;\n  QToolBar mToolBar;\n  ApvlvLineEdit mFilterText;\n  QComboBox mFilterType;\n  QComboBox mSortType;\n  ContentTree mTreeWidget;\n\n  QMenu mItemMenu;\n\n  std::map<FileIndexType, QIcon> mTypeIcons;\n\n  FileIndex mIndex;\n  QStringList mTags;\n  Column mSortColumn{ Column::Title };\n\n  ApvlvFrame *mFrame{ nullptr };\n\n  bool mSortAscending{ true };\n\n  void setupToolBar ();\n  void setupTree ();\n\n  QTreeWidgetItem *\n  selectedTreeItem ()\n  {\n    auto selitems = mTreeWidget.selectedItems ();\n    return selitems.isEmpty () ? nullptr : selitems[0];\n  }\n  void setItemSelected (QTreeWidgetItem *item);\n\n  void setIndex (FileIndex &index, QTreeWidgetItem *root_itr);\n  void refreshIndex (const FileIndex &index);\n\n  void setFileIndexToTreeItem (QTreeWidgetItem *item, FileIndex *index);\n  FileIndex *getFileIndexFromTreeItem (QTreeWidgetItem *item);\n\n  FileIndex *treeItemToFileIndex (QTreeWidgetItem *item);\n\n  using filterFuncReturn = std::tuple<bool, bool>;\n  using filterFunc = std::function<filterFuncReturn (const FileIndex *)>;\n  void filterItemBy (QTreeWidgetItem *root, const filterFunc &filter_func);\n  void setItemChildrenFilter (QTreeWidgetItem *root, bool is_filter);\n\nprivate slots:\n  void onFileRename ();\n  void onFileDelete ();\n  void onRefresh ();\n  void onFilter ();\n  void\n  sortBy (int method)\n  {\n    mSortAscending = !mSortAscending;\n    mSortColumn = static_cast<Column> (method);\n    sortItems (mTreeWidget.invisibleRootItem ());\n  }\n\n  void sortItems (QTreeWidgetItem *root);\n\n  void onRowActivated (QTreeWidgetItem *item, int column);\n  void onRowDoubleClicked ();\n  void onContextMenuRequest (const QPoint &point);\n  void selectFirstItem ();\n  void setIndex (const FileIndex &index);\n  void preloadTags (const std::string &path);\n};\n}\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/ApvlvDired.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvDired.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QFileDialog>\n#include <QPushButton>\n#include <regex>\n\n#include \"ApvlvDired.h\"\n\nnamespace apvlv\n{\nusing namespace std;\n\nDiredDialog::DiredDialog (QWidget *parent) : QDialog (parent)\n{\n  setLayout (&mVboxLayout);\n}\n\nvoid\nDiredDialog::activateItem (QListWidgetItem *item)\n{\n  auto words = item->data (Qt::UserRole).toStringList ();\n  auto path = words[0];\n  auto pn = words[1].toInt ();\n  emit loadFile (path.toStdString (), pn);\n  accept ();\n}\n\n}\n"
  },
  {
    "path": "src/ApvlvDired.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvDired.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_DIRED_H_\n#define _APVLV_DIRED_H_\n\n#include <QDialog>\n#include <QLineEdit>\n#include <QListWidget>\n#include <QTimer>\n#include <QVBoxLayout>\n#include <string>\n#include <thread>\n\nnamespace apvlv\n{\n\nclass DiredDialog : public QDialog\n{\n  Q_OBJECT\npublic:\n  explicit DiredDialog (QWidget *parent = nullptr);\n  ~DiredDialog () override = default;\n\nsignals:\n  void loadFile (const std::string &path, int pn);\n\nprivate:\n  QVBoxLayout mVboxLayout;\n\nprivate slots:\n  void activateItem (QListWidgetItem *item);\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "src/ApvlvEditor.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvEditor.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include \"ApvlvEditor.h\"\n\nnamespace apvlv\n{\n\nEditor::Editor (QWidget *parent) : QTextEdit (parent)\n{\n  auto font = currentFont ();\n  mFontFamily = font.family ();\n  mFontPointSize = font.pointSizeF ();\n  mFontWeight = font.weight ();\n  mFontPixelSize = font.pixelSize ();\n}\n\nvoid\nEditor::setZoomrate (double zm)\n{\n  auto font = currentFont ();\n  auto updated = false;\n  if (mFontPointSize != -1)\n    {\n      auto pointsize = mFontPointSize * zm;\n      font.setPointSizeF (pointsize);\n      updated = true;\n    }\n  else if (mFontPixelSize != -1)\n    {\n      auto pixelsize = mFontPixelSize * zm;\n      font.setPixelSize (static_cast<int> (pixelsize));\n      updated = true;\n    }\n  if (updated)\n    {\n      setFont (font);\n      update ();\n    }\n}\n\n}\n"
  },
  {
    "path": "src/ApvlvEditor.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvEditor.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_EDITOR_H_\n#define _APVLV_EDITOR_H_\n\n#include <QTextEdit>\n\nnamespace apvlv\n{\n\nclass Editor : public QTextEdit\n{\n  Q_OBJECT\npublic:\n  explicit Editor (QWidget *parent = nullptr);\n  ~Editor () override = default;\n\n  void setZoomrate (double zm);\n\nprivate:\n  QString mFontFamily;\n  QFont::Weight mFontWeight;\n  qreal mFontPointSize;\n  int mFontPixelSize;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "src/ApvlvFile.cc",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2008>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE File.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QBuffer>\n#include <QPainter>\n#include <QPrintDialog>\n#include <QPrinter>\n#include <algorithm>\n#include <filesystem>\n#include <functional>\n#include <iostream>\n#include <optional>\n#include <sstream>\n#include <utility>\n\n#include \"ApvlvFile.h\"\n#include \"ApvlvUtil.h\"\n#include \"ApvlvWebViewWidget.h\"\n\nnamespace apvlv\n{\n\nusing namespace std;\n\nconst string html_template = \"<?xml version='1.0' encoding='UTF-8'?>\\n\"\n                             \"<html xmlns=\\\"http://www.w3.org/1999/xhtml\\\" \"\n                             \"lang=\\\"en\\\" xml:lang=\\\"en\\\">\\n\"\n                             \"  <head>\\n\"\n                             \"    <title></title>\\n\"\n                             \"    <meta http-equiv=\\\"Content-Type\\\" \"\n                             \"content=\\\"text/html; charset=utf-8\\\"/>\\n\"\n                             \"  </head>\\n\"\n                             \"  <body>\\n\"\n                             \"    <div content>\"\n                             \"      <image src=%s />\"\n                             \"    </div>\"\n                             \"  </body>\\n\"\n                             \"</html>\\n\";\n\nmap<string, vector<string>> FileFactory::mSupportMimeTypes;\nmap<string, FileFactory::ExtClassList> FileFactory::mSupportClass;\n\nconst map<string, vector<string>> &\nFileFactory::supportMimeTypes ()\n{\n  return mSupportMimeTypes;\n}\n\nvector<string>\nFileFactory::supportFileExts ()\n{\n  unordered_set<string> extSet;\n  for (const auto &pair : mSupportMimeTypes)\n    {\n      extSet.insert (pair.second.begin (), pair.second.end ());\n    }\n  vector<string> exts (extSet.begin (), extSet.end ());\n  return exts;\n}\n\nostream &\nFileFactory::typeEngineDescription (ostream &os)\n{\n  os << \"Engines: \" << endl;\n  for (auto &pair : mSupportClass)\n    {\n      string ext = pair.first;\n      string engines;\n      std::ranges::for_each (pair.second,\n                             [&engines] (ExtClass &cls)\n                               {\n                                 engines.append (\" \");\n                                 engines.append (cls.first);\n                               });\n      os << \"\\t\" << pair.first << \":\\t\\t\" << engines << endl;\n    }\n  os << endl;\n  return os;\n}\n\nint\nFileFactory::registerClass (const string &name, const function<File *()> &fun,\n                            initializer_list<string> exts)\n{\n  mSupportMimeTypes.insert ({ name, exts });\n  std::ranges::for_each (exts,\n                         [=] (const string &t)\n                           {\n                             auto iter = mSupportClass.find (t);\n                             if (iter != mSupportClass.end ())\n                               {\n                                 auto &cls_list = iter->second;\n                                 cls_list.emplace_back (name, fun);\n                               }\n                             else\n                               {\n                                 auto cls_list\n                                     = ExtClassList{ { name, fun } };\n                                 mSupportClass.insert ({ t, cls_list });\n                               }\n                           });\n  return static_cast<int> (mSupportMimeTypes.size ());\n}\n\noptional<FileFactory::ExtClass>\nFileFactory::findMatchClass (const std::string &filename)\n{\n  auto ext = filenameExtension (filename);\n  if (ext.empty ())\n    return nullopt;\n\n  if (mSupportClass.find (ext) == mSupportClass.end ())\n    return nullopt;\n\n  auto cls_list = mSupportClass[ext];\n  if (cls_list.size () == 1)\n    return cls_list[0];\n\n  auto cls_name\n      = ApvlvParams::instance ()->getGroupStringOrDefault (ext, \"engine\", \"\");\n  if (cls_name.empty ())\n    return cls_list[0];\n\n  for (auto const &cls : cls_list)\n    {\n      if (strcasecmp (cls.first.c_str (), cls_name.c_str ()) == 0)\n        return cls;\n    }\n\n  return cls_list[0];\n}\n\nunique_ptr<File>\nFileFactory::loadFile (const string &filename)\n{\n  auto cls = findMatchClass (filename);\n  if (!cls.has_value ())\n    {\n      qWarning () << \"no engine to open \" << filename;\n      return nullptr;\n    }\n\n  auto file = unique_ptr<File> (cls->second ());\n  if (file->setFilename (filename))\n    return file;\n  else\n    {\n      qWarning () << \"open \" << QString::fromLocal8Bit (filename) << \" error\";\n      return nullptr;\n    }\n}\n\nFile::~File ()\n{\n  mPages.clear ();\n  srcPages.clear ();\n  srcMimeTypes.clear ();\n}\n\nunique_ptr<SearchFileMatch>\nFile::grepFile (const string &seq, bool is_case, bool is_regex,\n                atomic<bool> &is_abort)\n{\n  vector<SearchPageMatch> page_matches;\n  auto pageSum = sum ();\n  for (auto pn = 0; pn < pageSum; ++pn)\n    {\n      if (is_abort.load () == true)\n        {\n          qDebug () << \"grep \" << QString::fromLocal8Bit (mFilename)\n                    << \" abort before page \" << pn;\n          return nullptr;\n        }\n\n      auto size = pageSizeF (pn, 0);\n      string content;\n      if (pageText (pn, { 0, 0, size.width, size.height }, content) == false)\n        continue;\n\n      istringstream iss{ content };\n      string line;\n      SearchMatchList matches;\n      while (getline (iss, line))\n        {\n          if (is_abort.load () == true)\n            {\n              qDebug () << \"grep \" << QString::fromLocal8Bit (mFilename)\n                        << \" abort at page \" << pn;\n              return nullptr;\n            }\n\n          auto founds = apvlv::grep (line, seq, is_case, is_regex);\n          for (auto const &found : founds)\n            {\n              SearchMatch match{ line.substr (found.first, found.second),\n                                 line, found.first, found.second };\n              matches.push_back (match);\n            }\n        }\n      if (!matches.empty ())\n        {\n          page_matches.push_back ({ pn, matches });\n        }\n    }\n\n  if (page_matches.empty ())\n    return nullptr;\n\n  auto file_match = make_unique<SearchFileMatch> ();\n  file_match->filename = getFilename ();\n  file_match->page_matches = std::move (page_matches);\n  return file_match;\n}\n\nbool\nFile::pageRenderToImage (int pn, double zm, int rot, QImage *img)\n{\n  return false;\n}\n\nbool\nFile::pageRenderToWebView (int pn, double zm, int rot, WebView *webview)\n{\n  webview->setZoomFactor (zm);\n  QUrl pdfuri = QString (\"apvlv:///%1-%2-%3-%4.html\")\n                    .arg (pn)\n                    .arg (zm)\n                    .arg (rot)\n                    .arg (rand ());\n  webview->load (pdfuri);\n  return true;\n}\n\nstring\nFile::pathMimeType (const string &path)\n{\n  if (srcMimeTypes.find (path) != srcMimeTypes.end ())\n    return srcMimeTypes[path];\n  else if (QString::fromLocal8Bit (path).endsWith (\".png\"))\n    return \"image/png\";\n  else\n    return \"text/html\";\n}\n\nint\nFile::pathPageNumber (const string &path)\n{\n  if (srcPages.find (path) != srcPages.end ())\n    return srcPages[path];\n  return -1;\n}\n\noptional<QByteArray>\nFile::pathContent (const string &path)\n{\n  auto words = QString::fromLocal8Bit (path).split (\"-\");\n  int pn = words[0].toInt ();\n  double zm = words[1].toDouble ();\n  int rot = words[2].toInt ();\n\n  if (QString::fromLocal8Bit (path).endsWith (\".html\"))\n    return pathContentHtml (pn, zm, rot);\n  else\n    return pathContentPng (pn, zm, rot);\n}\n\noptional<QByteArray>\nFile::pathContentHtml (int pn, double zm, int rot)\n{\n  auto src = QString (\"apvlv:///%1-%2-%3-%4.png\")\n                 .arg (pn)\n                 .arg (zm)\n                 .arg (rot)\n                 .arg (rand ());\n  auto html = QString::asprintf (html_template.c_str (),\n                                 src.toStdString ().c_str ());\n  return QByteArray::fromStdString (html.toStdString ());\n}\n\noptional<QByteArray>\nFile::pathContentPng (int pn, double zm, int rot)\n{\n  QImage image;\n  if (pageRenderToImage (pn, zm, rot, &image) == false)\n    return nullopt;\n\n  QByteArray array;\n  QBuffer buffer (&array);\n  buffer.open (QIODevice::WriteOnly);\n  image.save (&buffer, \"PNG\");\n  buffer.close ();\n  return array;\n}\n\nbool\nFile::print (int page)\n{\n  QPrinter printer;\n  QPrintDialog dialog (&printer);\n  if (dialog.exec () != QDialog::Accepted)\n    return false;\n\n  QPainter painter (&printer);\n  int startPage = (page >= 0) ? page : 0;\n  int endPage = (page >= 0) ? page : sum () - 1;\n\n  for (int pn = startPage; pn <= endPage; ++pn)\n    {\n      if (pn > startPage)\n        printer.newPage ();\n\n      QImage image;\n      if (pageRenderToImage (pn, 1.0, 0, &image))\n        {\n          QRect rect = painter.viewport ();\n          QSize size = image.size ();\n          size.scale (rect.size (), Qt::KeepAspectRatio);\n          painter.drawImage (0, 0, image.scaled (size));\n        }\n    }\n  return true;\n}\n\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/ApvlvFile.h",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2008>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE File.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_FILE_H_\n#define _APVLV_FILE_H_\n\n#include <map>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"ApvlvFileIndex.h\"\n#include \"ApvlvNote.h\"\n#include \"ApvlvParams.h\"\n#include \"ApvlvSearch.h\"\n\nnamespace apvlv\n{\n\nenum class DISPLAY_TYPE\n{\n  IMAGE,\n  HTML,\n  CUSTOM,\n};\n\n//\n// link to an url, or a page num\n//\nstruct ApvlvLink\n{\n  int mPage;\n};\n\nstruct ApvlvCover\n{\n  std::string content;\n  std::string mime_type;\n};\n\nusing ApvlvLinks = std::vector<ApvlvLink>;\n\nstruct ApvlvPoint\n{\n  double x;\n  double y;\n};\n\nstruct Size\n{\n  int width;\n  int height;\n};\n\nstruct SizeF\n{\n  double width;\n  double height;\n};\n\n/*\n * position of a search result, or just an area\n */\nstruct Rectangle\n{\n  double p1x;\n  double p1y;\n  double p2x;\n  double p2y;\n};\n\nusing CharRectangle = Rectangle;\n\nstruct WordRectangle\n{\n  std::string word;\n  std::vector<CharRectangle> rect_list;\n};\n\nusing WordListRectangle = std::vector<WordRectangle>;\n\nclass WebView;\nclass FileWidget;\nclass File\n{\npublic:\n  virtual ~File ();\n\n  virtual bool load (const std::string &filename) = 0;\n\n  /* File methods */\n  [[nodiscard]] virtual DISPLAY_TYPE\n  getDisplayType () const\n  {\n    return DISPLAY_TYPE::HTML;\n  }\n\n  virtual FileWidget *\n  getWidget ()\n  {\n    return nullptr;\n  }\n\n  const std::string &\n  getFilename ()\n  {\n    return mFilename;\n  }\n\n  bool\n  setFilename (const std::string &filename)\n  {\n    if (load (filename))\n      {\n        mFilename = filename;\n\n        auto note_path = Note::notePathOfFile (this);\n        mNote.load (note_path);\n\n        return true;\n      }\n\n    return false;\n  }\n\n  const ApvlvCover &\n  getCover ()\n  {\n    return mCover;\n  }\n\n  Note *\n  getNote ()\n  {\n    return &mNote;\n  }\n\n  const FileIndex &\n  getIndex ()\n  {\n    return mIndex;\n  }\n\n  virtual std::unique_ptr<SearchFileMatch>\n  grepFile (const std::string &seq, bool is_case, bool is_regex,\n            std::atomic<bool> &is_abort);\n\n  virtual int\n  sum ()\n  {\n    return 1;\n  };\n\n  // Page methods\n  Size\n  pageSize (int page, int rot)\n  {\n    auto sizef = pageSizeF (page, rot);\n    return { static_cast<int> (sizef.width),\n             static_cast<int> (sizef.height) };\n  }\n\n  virtual SizeF\n  pageSizeF (int page, int rot)\n  {\n    return { 0, 0 };\n  }\n\n  virtual int\n  pageNumberWrap (int page)\n  {\n    auto scrdoc\n        = ApvlvParams::instance ()->getBoolOrDefault (\"autoscrolldoc\");\n    int c = sum ();\n\n    if (page >= 0 && page < c)\n      {\n        return page;\n      }\n    else if (page >= c && scrdoc)\n      {\n        return page % c;\n      }\n    else if (page < 0 && scrdoc)\n      {\n        while (page < 0)\n          page += c;\n        return page;\n      }\n    else\n      {\n        return -1;\n      }\n  }\n\n  virtual bool pageRenderToImage (int pn, double zm, int rot, QImage *img);\n\n  virtual bool pageRenderToWebView (int pn, double zm, int rot,\n                                    WebView *webview);\n\n  // some ebooks only have image as page\n  virtual bool\n  pageIsOnlyImage (int pn)\n  {\n    return false;\n  }\n\n  virtual std::unique_ptr<ApvlvLinks>\n  pageLinks (int pn)\n  {\n    return nullptr;\n  }\n\n  virtual bool\n  pageText (int pn, const Rectangle &rect, std::string &text)\n  {\n    return false;\n  }\n\n  virtual std::unique_ptr<WordListRectangle>\n  pageSearch (int pn, const char *str)\n  {\n    return nullptr;\n  }\n\n  virtual std::optional<std::vector<Rectangle>>\n  pageHighlight (int pn, const ApvlvPoint &pa, const ApvlvPoint &pb)\n  {\n    return std::nullopt;\n  }\n\n  // path methods\n  virtual std::optional<QByteArray> pathContent (const std::string &path);\n\n  virtual std::string pathMimeType (const std::string &path);\n\n  virtual int pathPageNumber (const std::string &path);\n\n  virtual bool print (int page = -1);\n\nprotected:\n  File () {}\n\n  std::string mFilename;\n  FileIndex mIndex;\n  std::vector<std::string> mPages;\n  std::map<std::string, int> srcPages;\n  std::map<std::string, std::string> srcMimeTypes;\n  ApvlvCover mCover;\n  Note mNote;\n\nprivate:\n  std::optional<QByteArray> pathContentHtml (int, double, int);\n  std::optional<QByteArray> pathContentPng (int, double, int);\n};\n\nclass FileFactory\n{\npublic:\n  static int registerClass (const std::string &name,\n                            const std::function<File *()> &fun,\n                            std::initializer_list<std::string> exts);\n\n  static const std::map<std::string, std::vector<std::string>> &\n  supportMimeTypes ();\n\n  static std::vector<std::string> supportFileExts ();\n\n  static std::ostream &typeEngineDescription (std::ostream &os);\n\n  using ExtClass = std::pair<std::string, std::function<File *()>>;\n  using ExtClassList = std::vector<ExtClass>;\n\n  static std::optional<ExtClass> findMatchClass (const std::string &filename);\n  static std::unique_ptr<File> loadFile (const std::string &filename);\n\nprivate:\n  static std::map<std::string, std::vector<std::string>> mSupportMimeTypes;\n  static std::map<std::string, ExtClassList> mSupportClass;\n};\n\n#define FILE_TYPE_DECLARATION(cls)                                           \\\nprivate:                                                                     \\\n  static int class_id_of_##cls\n#define FILE_TYPE_DEFINITION(name, cls, ...)                                 \\\n  int cls::class_id_of_##cls = FileFactory::registerClass (                  \\\n      name,                                                                  \\\n      [] () -> File *                                                        \\\n        {                                                                    \\\n          return new cls ();                                                 \\\n        },                                                                   \\\n      __VA_ARGS__)\n\n};\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/ApvlvFileIndex.cc",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2008>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE File.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QBuffer>\n#include <QDebug>\n#include <algorithm>\n#include <filesystem>\n#include <iostream>\n\n#include \"ApvlvFile.h\"\n#include \"ApvlvFileIndex.h\"\n#include \"ApvlvUtil.h\"\n\nnamespace apvlv\n{\n\nusing namespace std;\n\nvoid\nFileIndex::loadDirectory (const string &path1)\n{\n  auto exts = FileFactory::supportFileExts ();\n\n  try\n    {\n      for (auto &entry : filesystem::directory_iterator (\n               path1,\n               filesystem::directory_options::follow_directory_symlink))\n        {\n          if (entry.is_directory ())\n            {\n              auto index\n                  = FileIndex (entry.path ().filename ().string (), 0,\n                               entry.path ().string (), FileIndexType::DIR);\n              index.loadDirectory (entry.path ().string ());\n              auto last = entry.last_write_time ();\n              index.mtime = filesystemTimeToMSeconds (last);\n              if (!index.mChildrenIndex.empty ())\n                {\n                  mChildrenIndex.emplace_back (index);\n                }\n            }\n          else if (entry.file_size () > 0)\n            {\n              auto file_ext = filenameExtension (entry.path ().string ());\n              if (find (exts.cbegin (), exts.cend (), file_ext)\n                  != exts.cend ())\n                {\n                  auto index = FileIndex (entry.path ().filename ().string (),\n                                          0, entry.path ().string (),\n                                          FileIndexType::FILE);\n                  index.size = static_cast<int64_t> (entry.file_size ());\n                  auto last = entry.last_write_time ();\n                  index.mtime = filesystemTimeToMSeconds (last);\n                  mChildrenIndex.emplace_back (index);\n                }\n            }\n        }\n    }\n  catch (filesystem::filesystem_error &err)\n    {\n      qWarning () << \"file system error: \" << err.what ();\n    }\n}\n\nvoid\nFileIndex::moveChildChildren (const FileIndex &other_index)\n{\n  Q_ASSERT (type == FileIndexType::FILE);\n  Q_ASSERT (other_index.type == FileIndexType::FILE);\n  mChildrenIndex = other_index.mChildrenIndex;\n}\n\nvoid\nFileIndex::removeChild (const FileIndex &child)\n{\n  mChildrenIndex.remove (child);\n}\n\nFileIndex::FileIndex (const string &title, int page, const string &path,\n                      FileIndexType type)\n{\n  this->title = title;\n  this->page = page;\n  this->path = path;\n  this->type = type;\n  if (const filesystem::path p (path); exists (p))\n    {\n      auto note_path = Note::notePathOfPath (path);\n      if (const filesystem::path np (note_path); exists (np))\n        {\n          const auto note = make_unique<Note> ();\n          note->load (note_path);\n          this->score = note->score ();\n          this->tags = note->tagString ();\n        }\n    }\n}\n\nFileIndex::~FileIndex () = default;\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/ApvlvFileIndex.h",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2008>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE File.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_FILE_INDEX_H_\n#define _APVLV_FILE_INDEX_H_\n\n#include <QImage>\n#include <list>\n#include <string>\n\n#include \"ApvlvSearch.h\"\n\nnamespace apvlv\n{\n\nenum class FileIndexType\n{\n  PAGE,\n  FILE,\n  DIR\n};\n\nclass FileIndex\n{\npublic:\n  FileIndex () = default;\n  FileIndex (const std::string &title, int page, const std::string &path,\n             FileIndexType type);\n  ~FileIndex ();\n\n  friend bool\n  operator== (const FileIndex &a, const FileIndex &b)\n  {\n    return a.title == b.title && a.page == b.page && a.path == b.path\n           && a.anchor == b.anchor;\n  }\n\n  void loadDirectory (const std::string &path1);\n  void moveChildChildren (const FileIndex &other_index);\n  void removeChild (const FileIndex &child);\n\n  /* public variables */\n  FileIndexType type{ FileIndexType::PAGE };\n  std::string title;\n  int page{ 0 };\n  std::string path;\n  std::string anchor;\n  std::list<FileIndex> mChildrenIndex;\n\n  /* public file variables */\n  std::int64_t size{ 0 };\n  std::int64_t mtime{ 0 };\n  std::string tags;\n  float score{ 0.0f };\n};\n\n}\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/ApvlvFileWidget.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvFileWidget.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include \"ApvlvFileWidget.h\"\n#include \"ApvlvParams.h\"\n\nnamespace apvlv\n{\n\nusing namespace std;\n\nvoid\nFileWidget::scroll (int times, int w, int h)\n{\n  // need impl in child\n}\n\ndouble\nFileWidget::scrollRate ()\n{\n  if (mValScrollBar == nullptr)\n    return mScrollValue;\n\n  double maxv = mValScrollBar->maximum () - mValScrollBar->minimum ();\n  double val = mValScrollBar->value () / maxv;\n  if (val > 1.0)\n    {\n      return 1.00;\n    }\n  else if (val > 0.0)\n    {\n      return val;\n    }\n  else\n    {\n      return 0.00;\n    }\n}\n\nvoid\nFileWidget::scrollTo (double s, double y)\n{\n  if (!mValScrollBar)\n    return;\n\n  auto maxv = mValScrollBar->maximum () - mValScrollBar->minimum ();\n  auto val = static_cast<int> (y * maxv);\n  mValScrollBar->setValue (val);\n}\n\nvoid\nFileWidget::scrollUp (int times)\n{\n  if (mValScrollBar == nullptr)\n    return;\n\n  auto const kj_pixels = ApvlvParams::instance ()->getIntOrDefault (\n      \"kj_pixels\", APVLV_KJ_PIXELS_DEFAULT);\n  auto rate = kj_pixels * times;\n  if (mValScrollBar->value () - rate >= mValScrollBar->minimum ())\n    {\n      mValScrollBar->setValue (mValScrollBar->value () - rate);\n    }\n  else if (mValScrollBar->value () > mValScrollBar->minimum ())\n    {\n      mValScrollBar->setValue (mValScrollBar->minimum ());\n    }\n  else\n    {\n      auto params = ApvlvParams::instance ();\n      if (params->getBoolOrDefault (\"autoscrollpage\"))\n        {\n          if (mPageNumber == 0)\n            {\n              if (params->getBoolOrDefault (\"autoscrolldoc\"))\n                {\n                  showPage (mFile->sum () - 1, 1.0);\n                }\n            }\n          else\n            {\n              showPage (mPageNumber - 1, 1.0);\n            }\n        }\n    }\n}\n\nvoid\nFileWidget::scrollDown (int times)\n{\n  if (!mValScrollBar)\n    return;\n\n  auto const kj_pixels = ApvlvParams::instance ()->getIntOrDefault (\n      \"kj_pixels\", APVLV_KJ_PIXELS_DEFAULT);\n  auto rate = kj_pixels * times;\n  if (mValScrollBar->value () + rate <= mValScrollBar->maximum ())\n    {\n      mValScrollBar->setValue (mValScrollBar->value () + rate);\n    }\n  else if (mValScrollBar->value () < mValScrollBar->maximum ())\n    {\n      mValScrollBar->setValue (mValScrollBar->maximum ());\n    }\n  else\n    {\n      auto params = ApvlvParams::instance ();\n      if (params->getBoolOrDefault (\"autoscrollpage\"))\n        {\n          if (mPageNumber == mFile->sum () - 1)\n            {\n              if (params->getBoolOrDefault (\"autoscrolldoc\"))\n                {\n                  showPage (0, 0.0);\n                }\n            }\n          else\n            {\n              showPage (mPageNumber + 1, 0.0);\n            }\n        }\n    }\n}\n\nvoid\nFileWidget::scrollLeft (int times)\n{\n  if (!mHalScrollBar)\n    return;\n\n  auto const hl_pixels = ApvlvParams::instance ()->getIntOrDefault (\n      \"hl_pixels\", APVLV_HL_PIXELS_DEFAULT);\n  auto val = mHalScrollBar->value () - hl_pixels * times;\n  if (val > mHalScrollBar->minimumWidth ())\n    {\n      mHalScrollBar->setValue (val);\n    }\n  else\n    {\n      mHalScrollBar->setValue (mHalScrollBar->minimumWidth ());\n    }\n}\n\nvoid\nFileWidget::scrollRight (int times)\n{\n  if (!mHalScrollBar)\n    return;\n\n  auto const hl_pixels = ApvlvParams::instance ()->getIntOrDefault (\n      \"hl_pixels\", APVLV_HL_PIXELS_DEFAULT);\n  auto val = mHalScrollBar->value () + hl_pixels * times;\n  if (val + mHalScrollBar->width () < mHalScrollBar->maximumWidth ())\n    {\n      mHalScrollBar->setValue (val);\n    }\n  else\n    {\n      mHalScrollBar->setValue (mHalScrollBar->maximumWidth ()\n                               - mHalScrollBar->width ());\n    }\n}\n\n}\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/ApvlvFileWidget.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvFileWidget.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_FILE_WIDGET_H_\n#define _APVLV_FILE_WIDGET_H_\n\n#include <QScrollBar>\n#include <string>\n\n#include \"ApvlvFile.h\"\n\nnamespace apvlv\n{\n\nconst int APVLV_HL_PIXELS_DEFAULT = 40;\nconst int APVLV_KJ_PIXELS_DEFAULT = 15;\n\nconst double APVLV_ZOOMRATE_DEFAULT = 1.3f;\nconst int INVALID_PAGENUM = -1;\n\nclass File;\nclass FileWidget : public QObject\n{\n  Q_OBJECT\npublic:\n  FileWidget () = default;\n\n  ~FileWidget () override = default;\n\n  [[nodiscard]] virtual QWidget *widget () = 0;\n\n  [[nodiscard]] virtual File *\n  file () const\n  {\n    return mFile;\n  }\n\n  virtual void\n  setFile (File *file)\n  {\n    mFile = file;\n  }\n\n  virtual int\n  pageNumber ()\n  {\n    return mPageNumber;\n  }\n\n  virtual std::string\n  anchor ()\n  {\n    return mAnchor;\n  }\n\n  virtual double\n  zoomrate ()\n  {\n    return mZoomrate;\n  }\n\n  virtual void\n  showPage (int pn, double rate)\n  {\n    mPageNumber = pn;\n    mScrollValue = rate;\n  }\n\n  virtual void\n  showPage (int pn, const std::string &anchor)\n  {\n    mPageNumber = pn;\n    mAnchor = anchor;\n  }\n\n  virtual void scroll (int times, int w, int h);\n\n  virtual double scrollRate ();\n\n  virtual void scrollTo (double s, double y);\n\n  virtual void scrollUp (int times);\n\n  virtual void scrollDown (int times);\n\n  virtual void scrollLeft (int times);\n\n  virtual void scrollRight (int times);\n\n  virtual void\n  setZoomrate (double zm)\n  {\n    mZoomrate = zm;\n  }\n\n  virtual void\n  setRotate (int rot)\n  {\n    mRotate = rot;\n  }\n\n  virtual int\n  rotate ()\n  {\n    return mRotate;\n  }\n\n  void\n  setAnchor (const std::string &anchor)\n  {\n    mAnchor = anchor;\n  }\n\n  virtual void\n  setSearchSelect (int select)\n  {\n    mSearchSelect = select;\n  }\n\n  [[nodiscard]] virtual int\n  searchSelect () const\n  {\n    return mSearchSelect;\n  }\n\n  virtual void\n  setSearchStr (const std::string &str)\n  {\n    mSearchStr = str;\n  }\n\n  [[nodiscard]] virtual std::string\n  searchStr () const\n  {\n    return mSearchStr;\n  }\n\n  virtual void\n  setSearchResults (const WordListRectangle &wlr)\n  {\n    mSearchResults = wlr;\n  }\n\n  virtual const WordListRectangle &\n  searchResults ()\n  {\n    return mSearchResults;\n  }\n\n  virtual void\n  setSelects (const std::vector<Rectangle> &rect_list)\n  {\n    mSelects = rect_list;\n  }\n\n  virtual const std::vector<Rectangle> &\n  selects ()\n  {\n    return mSelects;\n  }\n\nprotected:\n  QScrollBar *mValScrollBar{ nullptr };\n  QScrollBar *mHalScrollBar{ nullptr };\n\n  File *mFile{ nullptr };\n\n  int mPageNumber{ INVALID_PAGENUM };\n  double mScrollValue{ 0.0f };\n  std::string mAnchor;\n  double mZoomrate{ APVLV_ZOOMRATE_DEFAULT };\n  int mRotate{ 0 };\n\n  std::string mSearchStr;\n  WordListRectangle mSearchResults;\n  int mSearchSelect{ 0 };\n\n  std::vector<Rectangle> mSelects;\n};\n}\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/ApvlvFrame.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvFrame.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QBuffer>\n#include <QClipboard>\n#include <QMessageBox>\n#include <filesystem>\n#include <fstream>\n#include <memory>\n\n#include \"ApvlvFileWidget.h\"\n#include \"ApvlvFrame.h\"\n#include \"ApvlvImageWidget.h\"\n#include \"ApvlvInfo.h\"\n#include \"ApvlvParams.h\"\n#include \"ApvlvView.h\"\n#include \"ApvlvWeb.h\"\n#include \"ApvlvWebViewWidget.h\"\n\nnamespace apvlv\n{\n\nusing namespace std;\nusing namespace Qt;\nusing namespace CommandModeType;\n\nstd::vector<const char *> ApvlvFrame::ZoomLabel = {\n  QT_TR_NOOP (\"Default\"),\n  QT_TR_NOOP (\"Fit Width\"),\n  QT_TR_NOOP (\"Fit Height\"),\n  QT_TR_NOOP (\"Custom\"),\n};\n\nApvlvFrame::ApvlvFrame (ApvlvView *view) : mToolStatus (this)\n{\n  mInuse = true;\n\n  mView = view;\n\n  mProCmd = 0;\n\n  mZoomMode = ZoomMode::NORMAL;\n\n  mSearchResults = nullptr;\n  mSearchStr = \"\";\n\n  setLayout (&mVbox);\n\n  mPaned.setHandleWidth (4);\n  mDirectoryWidth = DEFAULT_CONTENT_WIDTH;\n\n  auto f_width = ApvlvParams::instance ()->getIntOrDefault (\"fix_width\");\n  auto f_height = ApvlvParams::instance ()->getIntOrDefault (\"fix_height\");\n\n  if (f_width > 0 && f_height > 0)\n    {\n      mPaned.setFixedSize (f_width, f_height);\n\n      mVbox.addLayout (&mHBoxLayout, 1);\n      mHBoxLayout.addWidget (&mPaned, 0);\n    }\n  else\n    {\n      mVbox.addWidget (&mPaned, 1);\n    }\n\n  mDirectory.setFrame (this);\n  QObject::connect (this, SIGNAL (indexGenerited (const FileIndex &)),\n                    &mDirectory, SLOT (setIndex (const FileIndex &)));\n\n  // left is Directory\n  mPaned.addWidget (&mDirectory);\n\n  // Right is Text\n  mPaned.addWidget (&mTextFrame);\n  mTextFrame.setLayout (&mTextLayout);\n  mTextLayout.addWidget (&mToolStatus, 0);\n  auto guiopt = ApvlvParams::instance ()->getStringOrDefault (\"guioptions\");\n  if (guiopt.find ('S') == string::npos)\n    {\n      mToolStatus.hide ();\n    }\n\n  mVbox.addWidget (&mStatus, 0);\n  if (guiopt.find ('s') == string::npos)\n    {\n      mStatus.hide ();\n    }\n  qDebug () << \"ApvlvFrame: \" << this << \" be created\";\n}\n\nApvlvFrame::~ApvlvFrame ()\n{\n  qDebug () << \"ApvlvFrame: \" << this << \" be freed\";\n  saveLastPosition (mFilestr);\n}\n\nvoid\nApvlvFrame::inuse (bool use)\n{\n  mInuse = use;\n}\n\nbool\nApvlvFrame::inuse ()\n{\n  return mInuse;\n}\n\nbool\nApvlvFrame::loadUri (const string &uri)\n{\n  mFile = make_unique<ApvlvWEB> ();\n  mFile->load (uri);\n  setWidget (mFile->getDisplayType ());\n  refresh (0, 0.0);\n  return true;\n}\n\nconst char *\nApvlvFrame::filename ()\n{\n  return mFilestr.empty () ? nullptr : mFilestr.c_str ();\n}\n\nbool\nApvlvFrame::print (int ct)\n{\n  if (!mFile)\n    return false;\n  return mFile->print (ct);\n}\n\nint\nApvlvFrame::getSkip ()\n{\n  return mSkip;\n}\n\nvoid\nApvlvFrame::setSkip (int ct)\n{\n  mSkip = ct;\n}\n\nvoid\nApvlvFrame::toggleDirectory ()\n{\n  auto show = !isShowDirectory ();\n  toggleDirectory (show);\n}\n\nvoid\nApvlvFrame::toggleDirectory (bool show)\n{\n  if (show)\n    {\n      if (!mDirectory.isReady ())\n        {\n          qWarning () << \"file \" << mFilestr << \" has no directory\";\n          show = false;\n        }\n    }\n\n  auto sizes = mPaned.sizes ();\n  if (show)\n    {\n      mDirIndex = {};\n      if (sizes[0] == 0)\n        {\n          auto psize = mPaned.size ();\n          sizes = { mDirectoryWidth, psize.width () - mPaned.handleWidth ()\n                                         - DEFAULT_CONTENT_WIDTH };\n          mPaned.setSizes (sizes);\n        }\n    }\n  else\n    {\n      if (sizes[0] != 0)\n        mDirectoryWidth = sizes[0];\n\n      auto psize = mPaned.size ();\n      sizes = { 0, psize.width () - mPaned.handleWidth () };\n      mPaned.setSizes (sizes);\n    }\n}\n\nvoid\nApvlvFrame::setActive (bool act)\n{\n  mActive = act;\n\n  auto wid = mWidget->widget ();\n  if (act)\n    {\n      QTimer::singleShot (50, wid, SLOT (setFocus ()));\n    }\n  else\n    {\n      wid->clearFocus ();\n      clearFocus ();\n    }\n\n  if (mActive && filename ())\n    {\n      auto path = filesystem::path (filename ());\n      auto base = path.filename ();\n      mView->setTitle (base.string ());\n    }\n\n  mStatus.setActive (act);\n}\n\nvoid\nApvlvFrame::setDirIndex (const string &path)\n{\n  mDirIndex = { \"\", 0, path, FileIndexType::DIR };\n  mDirIndex.loadDirectory (path);\n  emit indexGenerited (mDirIndex);\n  toggleDirectory (true);\n}\n\nbool\nApvlvFrame::toggledControlDirectory (bool is_right)\n{\n  if (!isShowDirectory ())\n    {\n      return false;\n    }\n\n  if (auto controlled = isControlledDirectory (); !controlled && !is_right)\n    {\n      mDirectory.setActive (true);\n      return true;\n    }\n  else if (controlled && is_right)\n    {\n      mDirectory.setActive (false);\n      return true;\n    }\n\n  return false;\n}\n\nbool\nApvlvFrame::isShowDirectory ()\n{\n  auto sizes = mPaned.sizes ();\n  return sizes[0] > 1;\n}\n\nbool\nApvlvFrame::isControlledDirectory ()\n{\n  if (!isShowDirectory ())\n    return false;\n\n  return mDirectory.isActive ();\n}\n\nApvlvFrame *\nApvlvFrame::findByWidget (QWidget *widget)\n{\n  for (auto doc = widget; doc != nullptr; doc = doc->parentWidget ())\n    {\n      if (doc->inherits (\"apvlv::ApvlvFrame\"))\n        return dynamic_cast<ApvlvFrame *> (doc);\n    }\n\n  return nullptr;\n}\n\nApvlvStatus::ApvlvStatus ()\n{\n  setFrameShape (QFrame::NoFrame);\n  setLayout (&mLayout);\n}\n\nvoid\nApvlvStatus::setActive (bool act)\n{\n  auto children = findChildren<QLabel *> ();\n  for (auto child : children)\n    {\n      if (child)\n        {\n          child->setEnabled (act);\n        }\n    }\n}\n\nvoid\nApvlvStatus::showMessages (const vector<string> &msgs)\n{\n  auto children = findChildren<QLabel *> ();\n  vector<QWidget *> newlabels;\n  for (std::size_t ind = 0; ind < msgs.size (); ++ind)\n    {\n      if (children.size () > (qsizetype)ind)\n        {\n          auto label = children[ind];\n          label->setText (QString::fromLocal8Bit (msgs[ind]));\n        }\n      else\n        {\n          auto label = new QLabel ();\n          label->setText (QString::fromLocal8Bit (msgs[ind]));\n          newlabels.push_back (label);\n        }\n    }\n\n  auto hbox = layout ();\n  for (auto label : newlabels)\n    {\n      hbox->addWidget (label);\n    }\n}\n\nApvlvToolStatus::ApvlvToolStatus (ApvlvFrame *frame) : mFrame (frame)\n{\n  auto paction = addAction (QIcon::fromTheme (QIcon::ThemeIcon::GoPrevious),\n                            tr (\"Previous Page\"));\n  QObject::connect (paction, SIGNAL (triggered (bool)), mFrame,\n                    SLOT (previousPage ()));\n  auto naction = addAction (QIcon::fromTheme (QIcon::ThemeIcon::GoNext),\n                            tr (\"Next Page\"));\n  QObject::connect (naction, SIGNAL (triggered (bool)), mFrame,\n                    SLOT (nextPage ()));\n\n  mPageValue.setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed);\n  QObject::connect (&mPageValue, SIGNAL (editingFinished ()), this,\n                    SLOT (gotoPage ()));\n  addWidget (&mPageValue);\n  addWidget (&mPageSum);\n  addSeparator ();\n\n  addWidget (&mScrollRate);\n  addSeparator ();\n\n  auto iaction = addAction (QIcon::fromTheme (QIcon::ThemeIcon::ZoomIn),\n                            tr (\"Zoom In\"));\n  QObject::connect (iaction, SIGNAL (triggered (bool)), mFrame,\n                    SLOT (zoomIn ()));\n  auto oaction = addAction (QIcon::fromTheme (QIcon::ThemeIcon::ZoomOut),\n                            tr (\"Zoom Out\"));\n  QObject::connect (oaction, SIGNAL (triggered (bool)), mFrame,\n                    SLOT (zoomOut ()));\n\n  addWidget (&mZoomType);\n  for (auto const &str : ApvlvFrame::ZoomLabel)\n    {\n      mZoomType.addItem (ApvlvFrame::tr (str));\n    }\n  QObject::connect (&mZoomType, SIGNAL (currentIndexChanged (int)), mFrame,\n                    SLOT (setZoomMode (int)));\n  mZoomType.setLineEdit (&mZoomValue);\n  addSeparator ();\n\n#ifdef APVLV_WITH_OCR\n  addWidget (&mOcrParse);\n  mOcrParse.setText (tr (\"OCR Parse\"));\n  QObject::connect (&mOcrParse, SIGNAL (checkStateChanged (Qt::CheckState)),\n                    mFrame, SLOT (ocrParse ()));\n\n  addAction (&mOcrCopy);\n  mOcrCopy.setIcon (QIcon::fromTheme (QIcon::ThemeIcon::Scanner));\n  mOcrCopy.setText (tr (\"OCR Copy\"));\n  QObject::connect (&mOcrCopy, SIGNAL (triggered (bool)), mFrame,\n                    SLOT (ocrCopy ()));\n#endif\n}\n\nvoid\nApvlvToolStatus::updateValue (int pn, int totpn, double zm, double sr)\n{\n  mPageValue.setText (QString::number (pn));\n  mPageSum.setText (QString::fromLocal8Bit (\"/%1\").arg (totpn));\n  if (mZoomType.currentIndex () == 3)\n    {\n      mZoomValue.setText (\n          QString::fromLocal8Bit (\"%1%\").arg (static_cast<int> (zm * 100)));\n    }\n  mScrollRate.setText (\n      QString::fromLocal8Bit (\"%1%\").arg (static_cast<int> (sr * 100)));\n\n#ifdef APVLV_WITH_OCR\n  auto need_ocr = mFrame->mFile->pageIsOnlyImage (mFrame->pageNumber ());\n  mOcrParse.setEnabled (need_ocr);\n  mOcrCopy.setEnabled (need_ocr);\n#endif\n}\n\nvoid\nApvlvToolStatus::gotoPage ()\n{\n  auto text = mPageValue.text ().trimmed ();\n  auto pn = text.toInt ();\n  if (pn != mFrame->pageNumber ())\n    {\n      mFrame->showPage (pn, 0.f);\n    }\n}\n\nCmdReturn\nApvlvFrame::subProcess ([[maybe_unused]] int ct, uint key)\n{\n  uint procmd = mProCmd;\n  mProCmd = 0;\n  switch (procmd)\n    {\n    case 'm':\n      markposition (char (key));\n      break;\n\n    case '\\'':\n      jump (char (key));\n      break;\n\n    case 'z':\n      if (key == 'i')\n        {\n          zoomIn ();\n        }\n      else if (key == 'o')\n        {\n          zoomOut ();\n        }\n      else if (key == 'h')\n        {\n          setZoomMode (static_cast<int> (ZoomMode::FITHEIGHT));\n        }\n      else if (key == 'w')\n        {\n          setZoomMode (static_cast<int> (ZoomMode::FITWIDTH));\n        }\n      break;\n\n    default:\n      return CmdReturn::NO_MATCH;\n      break;\n    }\n\n  return CmdReturn::MATCH;\n}\n\nvoid\nApvlvFrame::previousPage ()\n{\n  previousPage (1);\n}\n\nvoid\nApvlvFrame::nextPage ()\n{\n  nextPage (1);\n}\n\nvoid\nApvlvFrame::setZoomMode (int mode)\n{\n  if (mode < static_cast<int> (ZoomMode::CUSTOM))\n    {\n      switch (static_cast<ZoomMode> (mode))\n        {\n          using enum ZoomMode;\n        case NORMAL:\n          setZoomString (\"normal\");\n          break;\n        case FITWIDTH:\n          setZoomString (\"fitwidth\");\n          break;\n        case FITHEIGHT:\n          setZoomString (\"fitheight\");\n          break;\n        case CUSTOM:\n          break;\n        }\n    }\n\n  updateStatus ();\n}\n\nvoid\nApvlvFrame::zoomIn ()\n{\n  auto zoomrate = mWidget->zoomrate ();\n  setZoomrate (zoomrate * 1.1);\n  updateStatus ();\n}\n\nvoid\nApvlvFrame::zoomOut ()\n{\n  auto zoomrate = mWidget->zoomrate ();\n  setZoomrate (zoomrate / 1.1);\n  updateStatus ();\n}\n\n#ifdef APVLV_WITH_OCR\nvoid\nApvlvFrame::ocrParse ()\n{\n  auto meta = mWidget->widget ()->metaObject ();\n  qDebug () << \"widget is \" << meta->className ();\n  if (!mWidget->widget ()->inherits (\"apvlv::ApvlvImage\"))\n    return;\n\n  auto image = dynamic_cast<ApvlvImage *> (mWidget->widget ());\n  auto state = mToolStatus.mOcrParse.checkState ();\n  image->ocrDisplay (state == Qt::Checked);\n}\n\nvoid\nApvlvFrame::ocrCopy ()\n{\n  auto meta = mWidget->widget ()->metaObject ();\n  qDebug () << \"widget is \" << meta->className ();\n  if (!mWidget->widget ()->inherits (\"apvlv::ApvlvImage\"))\n    return;\n\n  auto image = dynamic_cast<ApvlvImage *> (mWidget->widget ());\n  auto text = image->ocrGetText ();\n#ifdef QT_DEBUG\n  QMessageBox::information (this, tr (\"text in clipboard\"),\n                            QString::fromLocal8Bit (text.get ()));\n#endif\n  auto clipboard = QGuiApplication::clipboard ();\n  clipboard->setText (text.get ());\n}\n#endif\n\nvoid\nApvlvFrame::wheelEvent (QWheelEvent *event)\n{\n  auto angel = event->angleDelta ();\n  if (angel.y () > 0)\n    {\n      mWidget->scrollUp (1);\n      updateStatus ();\n    }\n  else\n    {\n      mWidget->scrollDown (1);\n      updateStatus ();\n    }\n}\n\nCmdReturn\nApvlvFrame::process (int has, int ct, uint key)\n{\n  emit focusIn ();\n\n  if (mProCmd != 0)\n    {\n      return subProcess (ct, key);\n    }\n\n  if (!has)\n    {\n      ct = 1;\n    }\n\n  switch (key)\n    {\n    case Key_PageDown:\n    case ctrlValue ('f'):\n      nextPage (ct);\n      break;\n    case Key_PageUp:\n    case ctrlValue ('b'):\n      previousPage (ct);\n      break;\n    case ctrlValue ('d'):\n      halfNextPage (ct);\n      break;\n    case ctrlValue ('u'):\n      halfPreviousPage (ct);\n      break;\n    case ':':\n    case '/':\n    case '?':\n    case 'F':\n      if (isControlledDirectory ())\n        {\n          mDirectory.focusFilter ();\n        }\n      else\n        {\n          mView->promptCommand (char (key));\n          return CmdReturn::NEED_MORE;\n        }\n    case 'H':\n      mWidget->scrollTo (0.0, 0.0);\n      break;\n    case 'M':\n      mWidget->scrollTo (0.0, 0.5);\n      break;\n    case 'L':\n      mWidget->scrollTo (0.0, 1.0);\n      break;\n    case '0':\n      mWidget->scrollLeft (INT_MAX);\n      break;\n    case '$':\n      mWidget->scrollRight (INT_MAX);\n      break;\n    case ctrlValue ('p'):\n    case Key_Up:\n    case 'k':\n      if (isControlledDirectory ())\n        {\n          mDirectory.scrollUp (ct);\n        }\n      else\n        {\n          mWidget->scrollUp (ct);\n          updateStatus ();\n        }\n      break;\n    case ctrlValue ('n'):\n    case ctrlValue ('j'):\n    case Key_Down:\n    case 'j':\n      if (isControlledDirectory ())\n        {\n          mDirectory.scrollDown (ct);\n        }\n      else\n        {\n          mWidget->scrollDown (ct);\n          updateStatus ();\n        }\n      break;\n    case Key_Backspace:\n    case Key_Left:\n    case ctrlValue ('h'):\n    case 'h':\n      if (isControlledDirectory ())\n        {\n          mDirectory.scrollLeft (ct);\n        }\n      else\n        {\n          mWidget->scrollLeft (ct);\n          updateStatus ();\n        }\n      break;\n    case Key_Space:\n    case Key_Right:\n    case ctrlValue ('l'):\n    case 'l':\n      if (isControlledDirectory ())\n        {\n          mDirectory.scrollRight (ct);\n        }\n      else\n        {\n          mWidget->scrollRight (ct);\n          updateStatus ();\n        }\n      break;\n    case Key_Return:\n      directoryShowPage (mDirectory.currentItemFileIndex (), false);\n      break;\n    case 'R':\n      reload ();\n      break;\n    case ctrlValue (']'):\n      gotoLink (ct);\n      break;\n    case ctrlValue ('t'):\n      returnLink (ct);\n      break;\n    case 't':\n      if (isControlledDirectory ())\n        {\n          mDirectory.tag ();\n        }\n      else\n        {\n          mView->newTab (HelpPdf);\n          mView->open ();\n        }\n      break;\n    case 'T':\n      mView->newTab (HelpPdf);\n      mView->openDir ();\n      break;\n    case 'o':\n      mView->open ();\n      break;\n    case 'O':\n      mView->openDir ();\n      break;\n    case 'r':\n      rotate (ct);\n      break;\n    case 'G':\n      markposition ('\\'');\n      if (!has)\n        {\n          showPage (mFile->sum () - 1, 0.0);\n        }\n      else\n        {\n          showPage (ct - 1, 0.0);\n        }\n      break;\n    case 'm':\n    case '\\'':\n    case 'z':\n      mProCmd = key;\n      return CmdReturn::NEED_MORE;\n      break;\n    case 'n':\n      if (mSearchCmd == SEARCH)\n        {\n          markposition ('\\'');\n          search (\"\", false);\n        }\n      else if (mSearchCmd == BACKSEARCH)\n        {\n          markposition ('\\'');\n          search (\"\", true);\n        }\n      break;\n    case 'N':\n      if (mSearchCmd == SEARCH)\n        {\n          markposition ('\\'');\n          search (\"\", true);\n        }\n      else if (mSearchCmd == BACKSEARCH)\n        {\n          markposition ('\\'');\n          search (\"\", false);\n        }\n      break;\n    case 's':\n      setSkip (ct);\n      break;\n    case 'c':\n      toggleDirectory ();\n      break;\n    default:\n      return CmdReturn::NO_MATCH;\n      break;\n    }\n\n  return CmdReturn::MATCH;\n}\n\nApvlvFrame *\nApvlvFrame::clone ()\n{\n  auto *ndoc = new ApvlvFrame (mView);\n  ndoc->loadFile (mFilestr, false, false);\n  ndoc->showPage (mWidget->pageNumber (), mWidget->scrollRate ());\n  return ndoc;\n}\n\nvoid\nApvlvFrame::setZoomrate (double zm)\n{\n  mZoomMode = ZoomMode::CUSTOM;\n  mWidget->setZoomrate (zm);\n}\n\nvoid\nApvlvFrame::setZoomString (const char *z)\n{\n  auto zoomrate = mWidget->zoomrate ();\n  if (z != nullptr)\n    {\n      string_view sv (z);\n      if (sv == \"normal\")\n        {\n          mZoomMode = ZoomMode::NORMAL;\n          zoomrate = 1.2;\n        }\n      else if (sv == \"fitwidth\")\n        {\n          mZoomMode = ZoomMode::FITWIDTH;\n        }\n      else if (sv == \"fitheight\")\n        {\n          mZoomMode = ZoomMode::FITHEIGHT;\n        }\n      else\n        {\n          double d = strtod (z, nullptr);\n          if (d > 0)\n            {\n              mZoomMode = ZoomMode::CUSTOM;\n              zoomrate = d;\n            }\n        }\n    }\n\n  if (mFile)\n    {\n      int pn = std::max (0, pageNumber () - 1);\n      auto size = mFile->pageSizeF (pn, 0);\n\n      if (size.width > 0 && size.height > 0)\n        {\n          auto wid = mWidget->widget ();\n          if (mZoomMode == ZoomMode::FITWIDTH)\n            {\n              auto x_root = wid->width ();\n              zoomrate = x_root / size.width;\n            }\n          else if (mZoomMode == ZoomMode::FITHEIGHT)\n            {\n              auto y_root = wid->height ();\n              zoomrate = y_root / size.height;\n            }\n        }\n      mWidget->setZoomrate (zoomrate);\n    }\n}\n\nbool\nApvlvFrame::saveLastPosition (const string &filename)\n{\n  if (filename.empty () || HelpPdf == filename\n      || ApvlvParams::instance ()->getBoolOrDefault (\"noinfo\", false))\n    {\n      return false;\n    }\n\n  bool ret = ApvlvInfo::instance ()->updateFile (\n      mWidget->pageNumber (), mSkip, mWidget->scrollRate (), filename);\n  return ret;\n}\n\nbool\nApvlvFrame::loadLastPosition (const string &filename)\n{\n  if (filename.empty () || HelpPdf == filename\n      || ApvlvParams::instance ()->getBoolOrDefault (\"noinfo\"))\n    {\n      showPage (0, 0.0);\n      return false;\n    }\n\n  bool ret = false;\n\n  auto optfp = ApvlvInfo::instance ()->file (filename);\n  if (optfp)\n    {\n      // correctly check\n      showPage (optfp.value ()->page, 0.0);\n      setSkip (optfp.value ()->skip);\n    }\n  else\n    {\n      showPage (0, 0.0);\n      ApvlvInfo::instance ()->updateFile (0, 0.0, mWidget->zoomrate (),\n                                          filename);\n    }\n\n  return ret;\n}\n\nbool\nApvlvFrame::reload ()\n{\n  return loadFile (mFilestr, false, isShowDirectory ());\n}\n\nint\nApvlvFrame::pageNumber ()\n{\n  return mWidget ? mWidget->pageNumber () : 0;\n}\n\nbool\nApvlvFrame::loadFile (const std::string &file, bool check,\n                      bool show_directory)\n{\n  if (check && file == mFilestr)\n    {\n      return false;\n    }\n\n  saveLastPosition (mFilestr);\n\n  mFile = FileFactory::loadFile (file);\n\n  if (mFile)\n    {\n      emit indexGenerited (mFile->getIndex ());\n\n      mFilestr = file;\n\n      if (mFile->sum () <= 1)\n        {\n          qDebug () << \"sum () = \" << mFile->sum ();\n        }\n\n      setWidget (mFile->getDisplayType ());\n\n      loadLastPosition (file);\n\n      setActive (true);\n\n      mSearchStr = \"\";\n      mSearchResults = nullptr;\n\n      if (ApvlvParams::instance ()->getIntOrDefault (\"autoreload\") > 0)\n        {\n          mWatcher = make_unique<QFileSystemWatcher> ();\n          QObject::connect (mWatcher.get (), SIGNAL (fileChanged ()), this,\n                            SLOT (changed_cb ()));\n\n          auto systempath = filesystem::path (file);\n          if (filesystem::is_symlink (systempath))\n            {\n              auto realname = filesystem::read_symlink (systempath).string ();\n              if (filesystem::is_regular_file (realname))\n                {\n                  mWatcher->addPath (QString::fromLocal8Bit (realname));\n                }\n            }\n          else\n            {\n              mWatcher->addPath (QString::fromLocal8Bit (file));\n            }\n        }\n    }\n\n  if (show_directory && mFile != nullptr)\n    {\n      toggleDirectory (true);\n    }\n  else\n    {\n      toggleDirectory (false);\n    }\n\n  return mFile != nullptr;\n}\n\nvoid\nApvlvFrame::markposition (const char s)\n{\n  ApvlvDocPosition adp = { mWidget->pageNumber (), mWidget->scrollRate () };\n  mPositions[s] = adp;\n}\n\nvoid\nApvlvFrame::jump (const char s)\n{\n  auto it = mPositions.find (s);\n  if (it != mPositions.end ())\n    {\n      ApvlvDocPosition adp = it->second;\n      markposition ('\\'');\n      showPage (adp.pagenum, adp.scrollrate);\n    }\n}\n\nvoid\nApvlvFrame::showPage (int pn, double s)\n{\n  auto rp = mFile->pageNumberWrap (pn);\n  if (rp < 0)\n    return;\n\n  mWidget->setAnchor (\"\");\n\n  if (!mZoominit)\n    {\n      mZoominit = true;\n      setZoomString (nullptr);\n    }\n\n  refresh (rp, s);\n}\n\nvoid\nApvlvFrame::showPage (int pn, const std::string &anchor)\n{\n  auto rp = mFile->pageNumberWrap (pn);\n  if (rp < 0)\n    return;\n  if (!mZoominit)\n    {\n      mZoominit = true;\n      setZoomString (nullptr);\n    }\n\n  mWidget->showPage (rp, anchor);\n  updateStatus ();\n}\n\nvoid\nApvlvFrame::nextPage (int times)\n{\n  showPage (mWidget->pageNumber () + times, 0.0f);\n}\n\nvoid\nApvlvFrame::previousPage (int times)\n{\n  showPage (mWidget->pageNumber () - times, 0.0f);\n}\n\nvoid\nApvlvFrame::refresh (int pn, double s)\n{\n  if (mFile == nullptr)\n    return;\n\n  mWidget->showPage (pn, s);\n  updateStatus ();\n}\n\nvoid\nApvlvFrame::halfNextPage (int times)\n{\n  double sr = mWidget->scrollRate ();\n  int rtimes = times / 2;\n\n  if (times % 2 != 0)\n    {\n      if (sr > 0.5)\n        {\n          sr = 0;\n          rtimes += 1;\n        }\n      else\n        {\n          sr = 1;\n        }\n    }\n\n  showPage (mWidget->pageNumber () + rtimes, sr);\n}\n\nvoid\nApvlvFrame::halfPreviousPage (int times)\n{\n  double sr = mWidget->scrollRate ();\n  int rtimes = times / 2;\n\n  if (times % 2 != 0)\n    {\n      if (sr < 0.5)\n        {\n          sr = 1;\n          rtimes += 1;\n        }\n      else\n        {\n          sr = 0;\n        }\n    }\n\n  showPage (mWidget->pageNumber () - rtimes, sr);\n}\n\nbool\nApvlvFrame::needSearch (const std::string &str, bool reverse)\n{\n  if (mFile == nullptr)\n    return false;\n\n  // search a different string\n  if (!str.empty () && str != mSearchStr)\n    {\n      qDebug () << \"different string.\";\n      mSearchStr = str;\n      return true;\n    }\n\n  else if (mSearchResults == nullptr)\n    {\n      qDebug () << \"no result.\";\n      return true;\n    }\n\n  // same string, but need to search next page\n  else if ((!reverse\n            && mWidget->searchSelect () == (int)mSearchResults->size () - 1)\n           || (reverse && mWidget->searchSelect () == 0))\n    {\n      qDebug () << \"same, but need next string: s: \" << reverse\n                << \", sel: \" << mWidget->searchSelect ()\n                << \", max: \" << mSearchResults->size ();\n      return true;\n    }\n\n  // same string, not need search, but has zoomed\n  else\n    {\n      qDebug () << \"same, not need next string. sel: \"\n                << mWidget->searchSelect ()\n                << \", max: \" << mSearchResults->size ();\n      if (!reverse)\n        {\n          setHighlightAndIndex (*mSearchResults,\n                                mWidget->searchSelect () + 1);\n        }\n      else\n        {\n          setHighlightAndIndex (*mSearchResults,\n                                mWidget->searchSelect () - 1);\n        }\n\n      return false;\n    }\n\n  return false;\n}\n\nbool\nApvlvFrame::search (const char *str, bool reverse)\n{\n  if (*str == '\\0' && mSearchStr.empty ())\n    {\n      return false;\n    }\n\n  if (*str)\n    {\n      mSearchCmd\n          = (reverse ? CommandModeType::BACKSEARCH : CommandModeType::SEARCH);\n    }\n\n  if (!needSearch (str, reverse))\n    {\n      return true;\n    }\n\n  mSearchResults = nullptr;\n  unsetHighlight ();\n\n  auto wrap = ApvlvParams::instance ()->getBoolOrDefault (\"wrapscan\");\n\n  auto i = mWidget->pageNumber ();\n  auto sum = mFile->sum ();\n  auto from = i;\n  bool search = false;\n  while (true)\n    {\n      if (*str != 0 || search)\n        {\n          mSearchResults\n              = mFile->pageSearch ((i + sum) % sum, mSearchStr.c_str ());\n          if (mSearchResults != nullptr && !mSearchResults->empty ())\n            {\n              if (i != mWidget->pageNumber ())\n                showPage (i, 0.0);\n              auto results = *mSearchResults;\n              auto sel = 0;\n              if (reverse)\n                sel = static_cast<int> (results.size () - 1);\n              setHighlightAndIndex (results, sel);\n              return true;\n            }\n        }\n\n      search = true;\n\n      if (!reverse && i < (wrap ? (from + sum) : (sum - 1)))\n        {\n          i++;\n        }\n      else if (reverse && i > (wrap ? (from - sum) : 0))\n        {\n          i--;\n        }\n      else\n        {\n          mView->errorMessage (string (\"can't find word: \"), mSearchStr);\n          return false;\n        }\n    }\n}\n\nbool\nApvlvFrame::totext (const char *file)\n{\n  if (mFile == nullptr)\n    return false;\n\n  auto pn = mWidget->pageNumber ();\n  string txt;\n  auto size = mFile->pageSizeF (pn, 0);\n  bool ret = mFile->pageText (pn, { 0, 0, size.width, size.height }, txt);\n  if (ret)\n    {\n      fstream fs{ filename (), ios::out };\n      if (fs.is_open ())\n        {\n          fs.write (txt.c_str (), txt.length ());\n          fs.close ();\n          return true;\n        }\n    }\n  return false;\n}\n\nbool\nApvlvFrame::rotate (int ct)\n{\n  // just hack\n  if (ct == 1)\n    ct = 90;\n\n  if (ct % 90 != 0)\n    {\n      mView->errorMessage (\"Not a 90 times value: \", ct);\n      return false;\n    }\n\n  auto rotate = mWidget->rotate ();\n  rotate += ct;\n  while (rotate < 0)\n    {\n      rotate += 360;\n    }\n  while (rotate > 360)\n    {\n      rotate -= 360;\n    }\n  mWidget->setRotate (rotate);\n  refresh (mWidget->pageNumber (), 0.0);\n  return true;\n}\n\nvoid\nApvlvFrame::gotoLink ([[maybe_unused]] int ct)\n{\n  // need impl\n}\n\nvoid\nApvlvFrame::returnLink ([[maybe_unused]] int ct)\n{\n  // need impl\n}\n\nvoid\nApvlvFrame::directoryShowPage (const FileIndex *index, bool force)\n{\n  if (index == nullptr)\n    return;\n\n  if (index->type == FileIndexType::FILE)\n    {\n      loadFile (index->path, true, true);\n      return;\n    }\n\n  auto file = mDirectory.currentFileFileIndex ();\n  if (file && file->path != mFilestr)\n    loadFile (file->path, true, true);\n\n  if (index->type == FileIndexType::PAGE)\n    {\n      if (index->page != mWidget->pageNumber ()\n          || index->anchor != mWidget->anchor ())\n        showPage (index->page, index->anchor);\n    }\n}\n\nvoid\nApvlvFrame::setWidget (DISPLAY_TYPE type)\n{\n  auto sizes = mPaned.sizes ();\n\n  if (type == DISPLAY_TYPE::IMAGE)\n    {\n      mWidget = make_unique<ImageWidget> ();\n      mWidget->setFile (mFile.get ());\n#ifdef APVLV_WITH_OCR\n      ocrParse ();\n#endif\n    }\n  else if (type == DISPLAY_TYPE::HTML)\n    {\n      mWidget = make_unique<WebViewWidget> ();\n      mWidget->setFile (mFile.get ());\n    }\n  else\n    {\n      mWidget.reset (mFile->getWidget ());\n    }\n  mTextLayout.addWidget (mWidget->widget (), 1);\n\n  mPaned.setSizes (sizes);\n}\n\nvoid\nApvlvFrame::unsetHighlight ()\n{\n  mWidget->setSearchStr (\"\");\n  mWidget->setSearchSelect (0);\n  mWidget->setSearchResults ({});\n}\n\nvoid\nApvlvFrame::setHighlightAndIndex (const WordListRectangle &poses, int sel)\n{\n  if (!poses[sel].word.empty ())\n    {\n      mWidget->setSearchStr (poses[sel].word);\n    }\n  mWidget->setSearchSelect (sel);\n  mWidget->setSearchResults (poses);\n\n  auto sr = mWidget->scrollRate ();\n  if (!poses[sel].rect_list.empty ())\n    {\n      auto rect = poses[sel].rect_list[0];\n      auto size\n          = mFile->pageSizeF (mWidget->pageNumber (), mWidget->rotate ());\n      if (size.height > 0)\n        {\n          sr = rect.p2y / size.height;\n        }\n    }\n\n  refresh (mWidget->pageNumber (), sr);\n}\n\nvoid\nApvlvFrame::updateStatus ()\n{\n  if (filename ())\n    {\n      vector<string> labels;\n\n      int pn = pageNumber () + 1;\n      int totpn = mFile->sum ();\n      auto zm = mWidget->zoomrate ();\n      auto sr = mWidget->scrollRate ();\n      string anchor = mWidget->anchor ();\n\n      auto systempath = filesystem::path (filename ());\n      auto bn = systempath.filename ();\n      labels.emplace_back (bn.string ());\n\n      auto ss = QString (\"%1/%2\").arg (pn).arg (totpn);\n      labels.emplace_back (ss.toStdString ());\n\n      ss = QString (\"%1%\").arg (static_cast<int> (zm * 100));\n      labels.emplace_back (ss.toStdString ());\n\n      ss = QString (\"%1%\").arg (static_cast<int> (sr * 100));\n      labels.emplace_back (ss.toStdString ());\n\n      mStatus.showMessages (labels);\n\n      mToolStatus.updateValue (pn, totpn, zm, sr);\n\n      mDirectory.setCurrentIndex (mFilestr, mWidget->pageNumber (),\n                                  mWidget->anchor ());\n    }\n}\n\nbool\nApvlvFrame::isStatusHidden ()\n{\n  return mStatus.isHidden ();\n}\n\nvoid\nApvlvFrame::statusShow ()\n{\n  mStatus.show ();\n}\n\nvoid\nApvlvFrame::statusHide ()\n{\n  mStatus.hide ();\n}\n\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/ApvlvFrame.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvFrame.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_FRAME_H_\n#define _APVLV_FRAME_H_\n\n#include <QAction>\n#include <QCheckBox>\n#include <QFileSystemWatcher>\n#include <QLabel>\n#include <QPrintDialog>\n#include <QPrinter>\n#include <QSplitter>\n#include <iostream>\n#include <map>\n\n#include \"ApvlvCmds.h\"\n#include \"ApvlvDirectory.h\"\n#include \"ApvlvFile.h\"\n#include \"ApvlvFileWidget.h\"\n#include \"ApvlvWidget.h\"\n\nnamespace apvlv\n{\n\nstruct ApvlvDocPosition\n{\n  int pagenum;\n  double scrollrate;\n};\n\nusing ApvlvDocPositionMap = std::map<char, ApvlvDocPosition>;\n\nstruct ApvlvWord\n{\n  CharRectangle pos;\n  std::string word;\n};\n\nstruct ApvlvLine\n{\n  CharRectangle pos;\n  std::vector<ApvlvWord> mWords;\n};\n\nclass ApvlvFrame;\nclass ApvlvStatus : public QFrame\n{\n  Q_OBJECT\npublic:\n  ApvlvStatus ();\n\n  ~ApvlvStatus () override = default;\n\n  void setActive (bool act);\n\n  void showMessages (const std::vector<std::string> &msgs);\n\nprivate:\n  QHBoxLayout mLayout;\n};\n\nclass ApvlvToolStatus : public QToolBar\n{\n  Q_OBJECT\npublic:\n  explicit ApvlvToolStatus (ApvlvFrame *frame);\n\n  void updateValue (int pn, int totpn, double zm, double sr);\n\nprivate:\n  ApvlvFrame *mFrame;\n\n  ApvlvLineEdit mPageValue;\n  QLabel mPageSum;\n  QLabel mScrollRate;\n\n  QComboBox mZoomType;\n  ApvlvLineEdit mZoomValue;\n\n#ifdef APVLV_WITH_OCR\n  QCheckBox mOcrParse;\n  QAction mOcrCopy;\n#endif\n\nprivate slots:\n  void gotoPage ();\n\n  friend class ApvlvFrame;\n};\n\nconst int DEFAULT_CONTENT_WIDTH = 300;\n\nclass FileWidget;\nclass ApvlvView;\nclass ApvlvFrame final : public QFrame\n{\n  Q_OBJECT\npublic:\n  explicit ApvlvFrame (ApvlvView *view);\n\n  ~ApvlvFrame () override;\n\n  bool reload ();\n\n  void inuse (bool use);\n\n  bool inuse ();\n\n  ApvlvFrame *clone ();\n\n  void setDirIndex (const std::string &path);\n\n  bool loadFile (const std::string &file, bool check, bool show_directory);\n\n  bool loadUri (const std::string &uri);\n\n  const char *filename ();\n\n  int pageNumber ();\n\n  void showPage (int pn, double s);\n  void showPage (int pn, const std::string &anchor);\n  void refresh (int pn, double s);\n\n  void setActive (bool act);\n\n  void updateStatus ();\n\n  bool isStatusHidden ();\n\n  void statusShow ();\n\n  void statusHide ();\n\n  bool print (int ct);\n\n  bool totext (const char *name);\n\n  bool rotate (int ct);\n\n  void markposition (char s);\n\n  void setZoomrate (double zm);\n\n  void setZoomString (const char *z);\n\n  void jump (char s);\n\n  void nextPage (int times);\n\n  void previousPage (int times);\n\n  void halfNextPage (int times);\n\n  void halfPreviousPage (int times);\n\n  bool search (const char *str, bool reverse);\n\n  void gotoLink (int ct);\n\n  void returnLink (int ct);\n\n  bool loadLastPosition (const std::string &filename);\n  bool saveLastPosition (const std::string &filename);\n\n  void directoryShowPage (const FileIndex *index, bool force);\n\n  int getSkip ();\n  void setSkip (int ct);\n\n  void toggleDirectory ();\n\n  void toggleDirectory (bool enabled);\n\n  bool toggledControlDirectory (bool is_right);\n\n  bool isShowDirectory ();\n\n  bool isControlledDirectory ();\n\n  void wheelEvent (QWheelEvent *event) override;\n\n  CmdReturn process (int has, int times, uint keyval);\n\n  ApvlvView *mView;\n\n  void\n  focusInEvent (QFocusEvent *event) override\n  {\n    emit focusIn ();\n  }\n\n  static ApvlvFrame *findByWidget (QWidget *widget);\n\nprivate:\n  std::unique_ptr<File> mFile;\n\n  FileIndex mDirIndex{};\n\n  bool mInuse;\n\n  std::unique_ptr<QFileSystemWatcher> mWatcher;\n\n  std::string mFilestr;\n\n  uint mProCmd;\n\n  char mSearchCmd{};\n  std::unique_ptr<WordListRectangle> mSearchResults;\n  std::string mSearchStr;\n\n  enum class ZoomMode\n  {\n    NORMAL,\n    FITWIDTH,\n    FITHEIGHT,\n    CUSTOM\n  };\n  ZoomMode mZoomMode;\n  static std::vector<const char *> ZoomLabel;\n\n  bool mZoominit{};\n\n  int mSkip{};\n\n  ApvlvDocPositionMap mPositions;\n\n  // the main menubar\n  QVBoxLayout mVbox;\n\n  // the main panel\n  QSplitter mPaned;\n  int mDirectoryWidth;\n\n  QHBoxLayout mHBoxLayout;\n\n  // directory panel\n  Directory mDirectory;\n\n  QFrame mTextFrame;\n  QVBoxLayout mTextLayout;\n  ApvlvToolStatus mToolStatus;\n  std::unique_ptr<FileWidget> mWidget;\n\n  // status bar\n  ApvlvStatus mStatus;\n\n  // if active\n  bool mActive{};\n\n  void setWidget (DISPLAY_TYPE type);\n  void unsetHighlight ();\n  void setHighlightAndIndex (const WordListRectangle &poses, int sel);\n  bool needSearch (const std::string &str, bool reverse);\n  CmdReturn subProcess (int ct, uint key);\n\nsignals:\n  void indexGenerited (const FileIndex &index);\n  void focusIn ();\n\nprivate slots:\n  void previousPage ();\n  void nextPage ();\n  void setZoomMode (int mode);\n  void zoomIn ();\n  void zoomOut ();\n#ifdef APVLV_WITH_OCR\n  void ocrParse ();\n  void ocrCopy ();\n#endif\n\n  friend class ApvlvStats;\n  friend class ApvlvToolStatus;\n};\n}\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/ApvlvImageWidget.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvImageWidget.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QBuffer>\n#include <QClipboard>\n#include <QInputDialog>\n#include <QMouseEvent>\n#include <QToolTip>\n#include <iostream>\n\n#include \"ApvlvImageWidget.h\"\n\n#include <QTimer>\n\nnamespace apvlv\n{\nusing namespace std;\n\n#ifdef APVLV_WITH_OCR\nTextContainer::TextContainer (QWidget *parent) : Editor (parent)\n{\n  setReadOnly (false);\n  setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff);\n  setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOff);\n}\n#endif\n\nImageContainer::ImageContainer (QWidget *parent) : QLabel (parent)\n{\n  mCopyAction.setText (tr (\"Copy\"));\n  QObject::connect (&mCopyAction, SIGNAL (triggered (bool)), this,\n                    SLOT (copy ()));\n  addAction (&mCopyAction);\n\n  mUnderlineAction.setText (tr (\"Underline\"));\n  QObject::connect (&mUnderlineAction, SIGNAL (triggered (bool)), this,\n                    SLOT (underline ()));\n  addAction (&mUnderlineAction);\n\n  mCommentAction.setText (tr (\"Comment\"));\n  QObject::connect (&mCommentAction, SIGNAL (triggered (bool)), this,\n                    SLOT (comment ()));\n  addAction (&mCommentAction);\n\n  setContextMenuPolicy (Qt::ContextMenuPolicy::ActionsContextMenu);\n\n  setMouseTracking (true);\n  mHoverTimer = new QTimer (this);\n  mHoverTimer->setSingleShot (true);\n  QObject::connect (mHoverTimer, SIGNAL (timeout ()), this,\n                    SLOT (handleHover ()));\n}\n\nvoid\nImageContainer::mousePressEvent (QMouseEvent *event)\n{\n  if (event->button () != Qt::MouseButton::LeftButton)\n    return;\n\n  redraw ();\n  mIsSelected = true;\n  mPressPosition = event->position ();\n}\n\nvoid\nImageContainer::mouseMoveEvent (QMouseEvent *event)\n{\n  QToolTip::hideText ();\n  mLastMousePos = event->pos ();\n  mHoverTimer->start (1000);\n  if (!mIsSelected)\n    return;\n\n  mMovePosition = event->position ();\n\n  auto range = selectionRange ();\n  auto rect_list = mImageWidget->file ()->pageHighlight (\n      mImageWidget->pageNumber (), range.first, range.second);\n  if (!rect_list)\n    return;\n\n  mImageWidget->setSelects (rect_list.value ());\n  redraw ();\n}\n\nvoid\nImageContainer::leaveEvent (QEvent *event)\n{\n  mHoverTimer->stop ();\n  QLabel::leaveEvent (event);\n}\n\nvoid\nImageContainer::mouseReleaseEvent (QMouseEvent *event)\n{\n  mImageWidget->setSelects ({});\n  mIsSelected = false;\n}\n\nbool\nImageContainer::renderImage (int pn, double zm, int rot)\n{\n  return mImageWidget->file ()->pageRenderToImage (pn, zm, rot, &mImage);\n}\n\nvoid\nImageContainer::redraw ()\n{\n  QImage img = mImage;\n  if (!mImageWidget->searchResults ().empty ())\n    {\n      imageSelectSearch (&img, mImageWidget->zoomrate (),\n                         mImageWidget->searchSelect (),\n                         mImageWidget->searchResults ());\n    }\n  else if (!mImageWidget->selects ().empty ())\n    {\n      imageSelect (&img, mImageWidget->zoomrate (), mImageWidget->selects ());\n    }\n  setPixmap (QPixmap::fromImage (img));\n  resize (img.size ());\n}\n\npair<ApvlvPoint, ApvlvPoint>\nImageContainer::selectionRange ()\n{\n  double left = mPressPosition.x () / mImageWidget->zoomrate ();\n  double top = mPressPosition.y () / mImageWidget->zoomrate ();\n  double right = mMovePosition.x () / mImageWidget->zoomrate ();\n  double bottom = mMovePosition.y () / mImageWidget->zoomrate ();\n  return { { left, top }, { right, bottom } };\n}\n\nvector<Rectangle>\nImageContainer::selectionArea ()\n{\n  auto range = selectionRange ();\n  auto rect_list = mImageWidget->file ()->pageHighlight (\n      mImageWidget->pageNumber (), range.first, range.second);\n  if (!rect_list)\n    return {};\n  return rect_list.value ();\n}\n\nstring\nImageContainer::selectionText ()\n{\n  auto range = selectionRange ();\n  string text;\n  mImageWidget->file ()->pageText (\n      mImageWidget->pageNumber (),\n      { range.first.x, range.first.y, range.second.x, range.second.y }, text);\n  return text;\n}\n\nvoid\nImageContainer::displayComment (QPoint pos)\n{\n  qDebug () << \"display comment at: \" << pos.x () << \":\" << pos.y ();\n  auto image_pos = pos;\n  image_pos /= mImageWidget->zoomrate ();\n\n  auto note = mImageWidget->file ()->getNote ();\n  auto comments = note->getCommentsInPage (mImageWidget->pageNumber ());\n  for (auto const &comment : comments)\n    {\n      QRectF rect{ comment.begin.x, comment.begin.y,\n                   comment.end.x - comment.begin.x,\n                   comment.end.y - comment.begin.y };\n      if (rect.contains (image_pos))\n        {\n          qDebug () << \"isVisible: \" << QToolTip::isVisible ();\n          QToolTip::showText (QCursor::pos (),\n                              QString::fromStdString (comment.commentText),\n                              this);\n          break;\n        }\n    }\n}\n\nvoid\nImageContainer::copy ()\n{\n  qDebug () << \"copy text\";\n  auto text = selectionText ();\n  auto clipboard = QGuiApplication::clipboard ();\n  clipboard->setText (QString::fromLocal8Bit (text));\n  mImageWidget->setSelects ({});\n  redraw ();\n}\n\nvoid\nImageContainer::underline ()\n{\n  qDebug () << \"underline text\";\n  auto page = mImageWidget->pageNumber ();\n  auto range = selectionRange ();\n  auto text = selectionText ();\n  if (!text.empty ())\n    {\n      auto note = mImageWidget->file ()->getNote ();\n      Comment comment;\n      comment.quoteText = text;\n      comment.begin.set (page, &range.first);\n      comment.end.set (page, &range.second);\n      note->addComment (comment);\n    }\n\n  mImageWidget->setSelects ({});\n  redraw ();\n}\n\nvoid\nImageContainer::comment ()\n{\n  qDebug () << \"comment text\";\n  do\n    {\n      auto text = selectionText ();\n      if (text.empty ())\n        break;\n\n      auto input_text = QInputDialog::getMultiLineText (this, tr (\"Input\"),\n                                                        tr (\"Comment\"));\n      auto commentText = input_text.trimmed ();\n      if (commentText.isEmpty ())\n        break;\n\n      auto page = mImageWidget->pageNumber ();\n      auto range = selectionRange ();\n      auto note = mImageWidget->file ()->getNote ();\n      Comment comment;\n      comment.quoteText = text,\n      comment.commentText = commentText.toStdString ();\n      comment.begin.set (page, &range.first);\n      comment.end.set (page, &range.second);\n      note->addComment (comment);\n    }\n  while (false);\n\n  mImageWidget->setSelects ({});\n  redraw ();\n}\n\nvoid\nImageContainer::handleHover ()\n{\n  auto pos = mapFromGlobal (QCursor::pos ());\n  if (rect ().contains (pos) && pos == mLastMousePos)\n    {\n      qDebug () << \"hovered\";\n      displayComment (pos);\n    }\n}\n\nApvlvImage::ApvlvImage ()\n{\n  setAlignment (Qt::AlignCenter);\n  setHorizontalScrollBarPolicy (Qt::ScrollBarPolicy::ScrollBarAsNeeded);\n  setVerticalScrollBarPolicy (Qt::ScrollBarPolicy::ScrollBarAsNeeded);\n\n  setWidget (&mImageContainer);\n}\n\nApvlvImage::~ApvlvImage ()\n{\n  qDebug () << \"ApvlvImage: \" << this << \" be freed\";\n}\n\n#ifdef APVLV_WITH_OCR\nvoid\nApvlvImage::ocrDisplay (bool is_ocr)\n{\n  if (is_ocr)\n    {\n      auto image = mImageContainer.pixmap ();\n      auto text = mOCR.getTextFromPixmap (image);\n      mTextContainer.setText (text.get ());\n      if (widget () != &mTextContainer)\n        {\n          takeWidget ();\n          setWidget (&mTextContainer);\n        }\n    }\n  else\n    {\n      if (widget () != &mImageContainer)\n        {\n          takeWidget ();\n          setWidget (&mImageContainer);\n        }\n    }\n}\n\nstd::unique_ptr<char>\nApvlvImage::ocrGetText ()\n{\n  auto image = mImageContainer.pixmap ();\n  auto text = mOCR.getTextFromPixmap (image);\n  return text;\n}\n#endif\n\nvoid\nImageWidget::showPage (int p, double s)\n{\n  if (p != mPageNumber)\n    {\n      if (!mImage.mImageContainer.renderImage (p, mZoomrate, mRotate))\n        return;\n    }\n  mImage.mImageContainer.redraw ();\n#ifdef APVLV_WITH_OCR\n  mImage.mTextContainer.resize (mImage.mImageContainer.size ());\n  if (mImage.widget () == &mImage.mTextContainer)\n    {\n      mImage.mTextContainer.setZoomrate (mZoomrate);\n      mImage.ocrDisplay (true);\n    }\n#endif\n  scrollTo (0.0, s);\n  mPageNumber = p;\n}\n\nvoid\nImageWidget::showPage (int p, const string &anchor)\n{\n  showPage (p, 0.0f);\n  mAnchor = anchor;\n}\n\nvoid\nImageWidget::setSearchResults (const WordListRectangle &wlr)\n{\n  mSearchResults = wlr;\n  mImage.mImageContainer.redraw ();\n}\n\nvoid\nImageWidget::setZoomrate (double zm)\n{\n  if (mPageNumber != INVALID_PAGENUM)\n    {\n      if (mImage.mImageContainer.renderImage (mPageNumber, zm, mRotate))\n        {\n          mImage.mImageContainer.redraw ();\n          mZoomrate = zm;\n        }\n#ifdef APVLV_WITH_OCR\n      mImage.mTextContainer.setZoomrate (zm);\n#endif\n    }\n  else\n    {\n      mZoomrate = zm;\n    }\n}\n\nvoid\nImageWidget::setRotate (int rotate)\n{\n  if (mPageNumber != INVALID_PAGENUM)\n    {\n      if (mImage.mImageContainer.renderImage (mPageNumber, mZoomrate, rotate))\n        {\n          mImage.mImageContainer.redraw ();\n          mRotate = rotate;\n        }\n    }\n  else\n    {\n      mRotate = rotate;\n    }\n}\n\nbool\nimageSelect (QImage *pix, double zm, const vector<Rectangle> &rect_list)\n{\n  for (auto const &rect : rect_list)\n    {\n      auto p1xz = static_cast<int> (rect.p1x * zm);\n      auto p2xz = static_cast<int> (rect.p2x * zm);\n      auto p1yz = static_cast<int> (rect.p1y * zm);\n      auto p2yz = static_cast<int> (rect.p2y * zm);\n\n      for (auto w = p1xz; w < p2xz; ++w)\n        {\n          for (auto h = p1yz; h < p2yz; ++h)\n            {\n              QColor c = pix->pixelColor (w, h);\n              c.setRgb (255 - c.red (), 255 - c.red (), 255 - c.red ());\n              pix->setPixelColor (w, h, c);\n            }\n        }\n    }\n\n  return true;\n}\n\nbool\nimageUnderline (QImage *pix, double zm, const vector<Rectangle> &rect_list)\n{\n  for (auto const &rect : rect_list)\n    {\n      auto p1xz = static_cast<int> (rect.p1x * zm);\n      auto p2xz = static_cast<int> (rect.p2x * zm);\n      auto p1yz = static_cast<int> (rect.p1y * zm);\n      auto p2yz = static_cast<int> (rect.p2y * zm);\n\n      for (auto w = p1xz; w < p2xz; ++w)\n        {\n          for (auto h = p1yz; h < p2yz; ++h)\n            {\n              QColor c = pix->pixelColor (w, h);\n              c.setRgb (255 - c.red (), 255 - c.red (), 255 - c.red ());\n              pix->setPixelColor (w, h, c);\n            }\n        }\n    }\n\n  return true;\n}\n\nbool\nimageSelectSearch (QImage *pix, double zm, int select,\n                   const WordListRectangle &wordlist)\n{\n  for (auto itr = wordlist.begin (); itr != wordlist.end (); ++itr)\n    {\n      auto rectangles = *itr;\n\n      for (auto const &pos : rectangles.rect_list)\n        {\n          auto p1xz = static_cast<int> (pos.p1x * zm);\n          auto p2xz = static_cast<int> (pos.p2x * zm);\n          auto p1yz = static_cast<int> (pos.p2y * zm);\n          auto p2yz = static_cast<int> (pos.p1y * zm);\n\n          if (pix->format () == QImage::Format_ARGB32)\n            {\n              imageArgb32ToRgb32 (*pix, p1xz, p1yz, p2xz, p2yz);\n            }\n\n          if (std::distance (wordlist.begin (), itr) == select)\n            {\n              for (int w = p1xz; w < p2xz; ++w)\n                {\n                  for (int h = p1yz; h < p2yz; ++h)\n                    {\n                      QColor c = pix->pixelColor (w, h);\n                      c.setRgb (255 - c.red (), 255 - c.red (),\n                                255 - c.red ());\n                      pix->setPixelColor (w, h, c);\n                    }\n                }\n            }\n          else\n            {\n              for (int w = p1xz; w < p2xz; ++w)\n                {\n                  for (int h = p1yz; h < p2yz; ++h)\n                    {\n                      QColor c = pix->pixelColor (w, h);\n                      c.setRgb (255 - c.red () / 2, 255 - c.red () / 2,\n                                255 - c.red () / 2);\n                      pix->setPixelColor (w, h, c);\n                    }\n                }\n            }\n        }\n    }\n\n  return true;\n}\n\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/ApvlvImageWidget.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvImageWidget.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_IMAGEWIDGET_H_\n#define _APVLV_IMAGEWIDGET_H_\n\n#include <QLabel>\n#include <QMainWindow>\n#include <QScrollArea>\n#include <QVBoxLayout>\n\n#include \"ApvlvFileWidget.h\"\n#include \"ApvlvUtil.h\"\n#ifdef APVLV_WITH_OCR\n#include \"ApvlvEditor.h\"\n#include \"ApvlvOCR.h\"\n#endif\n\nnamespace apvlv\n{\n\n#ifdef APVLV_WITH_OCR\nclass TextContainer : public Editor\n{\n  Q_OBJECT\npublic:\n  explicit TextContainer (QWidget *parent = nullptr);\n  ~TextContainer () override = default;\n};\n#endif\n\nclass ImageWidget;\nclass ImageContainer : public QLabel\n{\n  Q_OBJECT\npublic:\n  explicit ImageContainer (QWidget *parent = nullptr);\n\n  void mousePressEvent (QMouseEvent *event) override;\n  void mouseReleaseEvent (QMouseEvent *event) override;\n  void mouseMoveEvent (QMouseEvent *event) override;\n  void leaveEvent (QEvent *event) override;\n\n  virtual bool renderImage (int pn, double zm, int rot);\n  virtual void redraw ();\n\n  void\n  setImageWidget (ImageWidget *image_widget)\n  {\n    mImageWidget = image_widget;\n  }\n\nprivate:\n  bool mIsSelected{ false };\n\n  QPointF mPressPosition;\n  QPointF mMovePosition;\n\n  QTimer *mHoverTimer;\n  QPoint mLastMousePos;\n\n  ImageWidget *mImageWidget{ nullptr };\n\n  friend class ImageWidget;\n\n  QImage mImage;\n  QAction mCopyAction;\n  QAction mUnderlineAction;\n  QAction mCommentAction;\n\n  std::pair<ApvlvPoint, ApvlvPoint> selectionRange ();\n  std::vector<Rectangle> selectionArea ();\n  std::string selectionText ();\n  void displayComment (QPoint pos);\n\nprivate slots:\n  void copy ();\n  void underline ();\n  void comment ();\n  void handleHover ();\n};\n\nclass ApvlvImage : public QScrollArea\n{\n  Q_OBJECT\npublic:\n  ApvlvImage ();\n\n  ~ApvlvImage () override;\n\n#ifdef APVLV_WITH_OCR\n  void ocrDisplay (bool replace);\n  std::unique_ptr<char> ocrGetText ();\n#endif\n\nprivate:\n  ImageContainer mImageContainer;\n#ifdef APVLV_WITH_OCR\n  TextContainer mTextContainer;\n  OCR mOCR;\n#endif\n\n  friend class ImageWidget;\n};\n\nclass ImageWidget : public FileWidget\n{\npublic:\n  ImageWidget ()\n  {\n    mImage.mImageContainer.setImageWidget (this);\n    mHalScrollBar = mImage.horizontalScrollBar ();\n    mValScrollBar = mImage.verticalScrollBar ();\n  }\n\n  [[nodiscard]] QWidget *\n  widget () override\n  {\n    return &mImage;\n  }\n\n  void showPage (int pn, double s) override;\n  void showPage (int pn, const std::string &anchor) override;\n\n  void setSearchResults (const WordListRectangle &wlr) override;\n  void setZoomrate (double zm) override;\n  void setRotate (int rotate) override;\n\nprivate:\n  ApvlvImage mImage{};\n};\n\nbool imageSelectSearch (QImage *pix, double zm, int select,\n                        const WordListRectangle &poses);\n\nbool imageSelect (QImage *pix, double zm,\n                  const std::vector<Rectangle> &poses);\n\nbool imageUnderline (QImage *pix, double zm,\n                     const std::vector<Rectangle> &poses);\n\n}\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/ApvlvInfo.cc",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2008>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvInfo.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <algorithm>\n#include <cstdlib>\n#include <cstring>\n#include <fstream>\n#include <ranges>\n#include <sstream>\n\n#include \"ApvlvInfo.h\"\n#include \"ApvlvParams.h\"\n\nnamespace apvlv\n{\nusing namespace std;\n\nvoid\nApvlvInfo::loadFile (std::string_view file)\n{\n  mFileName = file;\n\n  ifstream is (mFileName, ios::in);\n  if (is.is_open ())\n    {\n      string line;\n\n      while (getline (is, line))\n        {\n          auto p = line.c_str ();\n\n          if (*p != '\\''              /* the ' */\n              || !isdigit (*(p + 1))) /* the digit */\n            {\n              continue;\n            }\n\n          addPosition (p);\n        }\n\n      is.close ();\n    }\n}\n\nbool\nApvlvInfo::update ()\n{\n  ofstream os (mFileName, ios::out);\n  if (!os.is_open ())\n    {\n      return false;\n    }\n\n  int i = 0;\n  for (const auto &infofile : mInfoFiles)\n    {\n      os << \"'\" << i++ << \"\\t\";\n      os << infofile.page << ':' << infofile.skip << \"\\t\";\n      os << infofile.rate << \"\\t\";\n      os << infofile.file << endl;\n    }\n\n  os.close ();\n  return true;\n}\n\nstd::optional<InfoFile *>\nApvlvInfo::lastFile ()\n{\n  if (mInfoFiles.empty ())\n    return nullopt;\n  else\n    return &*(mInfoFiles.rbegin ());\n}\n\noptional<InfoFile *>\nApvlvInfo::file (const string &filename)\n{\n  auto itr = std::ranges::find_if (std::views::reverse (mInfoFiles),\n                                   [filename] (auto const &infofile)\n                                     {\n                                       return infofile.file == filename;\n                                     });\n  if (itr != mInfoFiles.rend ())\n    {\n      return &(*itr);\n    }\n\n  return nullopt;\n}\n\nbool\nApvlvInfo::updateFile (int page, int skip, double rate,\n                       const string &filename)\n{\n  InfoFile infofile{ page, skip, rate, filename };\n  auto optinfofile = file (filename);\n  if (optinfofile)\n    {\n      *optinfofile.value () = infofile;\n    }\n  else\n    {\n      mInfoFiles.push_back (infofile);\n      if (mInfoFiles.size () > mMaxInfo)\n        mInfoFiles.pop_front ();\n    }\n\n  return update ();\n}\n\nApvlvInfo::ApvlvInfo ()\n{\n  mMaxInfo = ApvlvParams::instance ()->getIntOrDefault (\"max_info\",\n                                                        DEFAULT_MAX_INFO);\n}\n\nbool\nApvlvInfo::addPosition (const char *str)\n{\n  const char *p;\n  const char *s;\n\n  p = strchr (str + 2, '\\t'); /* Skip the ' and the digit */\n  if (p == nullptr)\n    {\n      return false;\n    }\n\n  while (*p != '\\0' && !isdigit (*p))\n    {\n      p++;\n    }\n  int page = int (strtol (p, nullptr, 10));\n  int skip;\n\n  s = strchr (p, ':');\n  for (; s && p < s; ++p)\n    {\n      if (!isdigit (*p))\n        {\n          break;\n        }\n    }\n  if (p == s)\n    {\n      ++p;\n      skip = int (strtol (p, nullptr, 10));\n    }\n  else\n    {\n      skip = 0;\n    }\n\n  p = strchr (p, '\\t');\n  if (p == nullptr)\n    {\n      return false;\n    }\n\n  while (*p != '\\0' && !isdigit (*p))\n    {\n      p++;\n    }\n  double rate = strtod (p, nullptr);\n\n  p = strchr (p, '\\t');\n  if (p == nullptr)\n    {\n      return false;\n    }\n\n  while (*p != '\\0' && isspace (*p))\n    {\n      p++;\n    }\n  if (*p == '\\0')\n    {\n      return false;\n    }\n\n  auto fp = InfoFile{ page, skip, rate, p };\n  mInfoFiles.emplace_back (fp);\n  return true;\n}\n};\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/ApvlvInfo.h",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2008>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvInfo.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_INFO_H_\n#define _APVLV_INFO_H_\n\n#include <deque>\n#include <optional>\n#include <string>\n\nnamespace apvlv\n{\n\nstruct InfoFile\n{\n  int page;\n  int skip;\n  double rate;\n  std::string file;\n};\n\nconst int DEFAULT_MAX_INFO = 100;\n\nclass ApvlvInfo final\n{\npublic:\n  ApvlvInfo (const ApvlvInfo &) = delete;\n  ApvlvInfo &operator= (const ApvlvInfo &) = delete;\n  void loadFile (std::string_view file);\n  bool update ();\n\n  std::optional<InfoFile *> lastFile ();\n  std::optional<InfoFile *> file (const std::string &filename);\n  bool updateFile (int page, int skip, double rate,\n                   const std::string &filename);\n\n  static ApvlvInfo *\n  instance ()\n  {\n    static ApvlvInfo inst;\n    return &inst;\n  }\n\nprivate:\n  ApvlvInfo ();\n  ~ApvlvInfo () = default;\n\n  std::string mFileName{};\n\n  std::deque<InfoFile> mInfoFiles{};\n  std::deque<InfoFile>::size_type mMaxInfo{ DEFAULT_MAX_INFO };\n\n  bool addPosition (const char *str);\n};\n};\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/ApvlvLab.cc",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvLab.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include \"ApvlvLab.h\"\n#include \"ApvlvUtil.h\"\n#include \"ApvlvWebViewWidget.h\"\n\nnamespace apvlv\n{\nusing namespace std;\n\nconst string stylesheet_content = \".block_c {\\n\"\n                                  \"  display: block;\\n\"\n                                  \"  font-size: 2.5em;\\n\"\n                                  \"  font-weight: normal;\\n\"\n                                  \"  line-height: 33.6pt;\\n\"\n                                  \"  text-align: center;\\n\"\n                                  \"  text-indent: 0;\\n\"\n                                  \"  margin: 17pt 0;\\n\"\n                                  \"  padding: 0;\\n\"\n                                  \"}\\n\"\n                                  \".block_ {\\n\"\n                                  \"  display: block;\\n\"\n                                  \"  font-size: 1.5em;\\n\"\n                                  \"  font-weight: normal;\\n\"\n                                  \"  line-height: 33.6pt;\\n\"\n                                  \"  text-align: justify;\\n\"\n                                  \"  text-indent: 0;\\n\"\n                                  \"  margin: 17pt 0;\\n\"\n                                  \"  padding: 0;\\n\"\n                                  \"}\\n\"\n                                  \".block_1 {\\n\"\n                                  \"  display: block;\\n\"\n                                  \"  line-height: 1.2;\\n\"\n                                  \"  text-align: justify;\\n\"\n                                  \"  margin: 0 0 7pt;\\n\"\n                                  \"  padding: 0;\\n\"\n                                  \"}\\n\";\nconst string title_template\n    = \"<?xml version='1.0' encoding='UTF-8'?>\\n\"\n      \"<html xmlns=\\\"http://www.w3.org/1999/xhtml\\\" \"\n      \"lang=\\\"en\\\" xml:lang=\\\"en\\\">\\n\"\n      \"  <head>\\n\"\n      \"    <title></title>\\n\"\n      \"    <link rel=\\\"stylesheet\\\" type=\\\"text/css\\\" \"\n      \"href=\\\"stylesheet.css\\\"/>\\n\"\n      \"    <meta http-equiv=\\\"Content-Type\\\" \"\n      \"content=\\\"text/html; charset=utf-8\\\"/>\\n\"\n      \"  </head>\\n\"\n      \"  <body>\\n\"\n      \"  <br />\\n\"\n      \"  <br />\\n\"\n      \"  <br />\\n\"\n      \"  <br />\\n\"\n      \"    %s\\n\"\n      \"  </body>\\n\"\n      \"</html>\\n\";\nconst string section_template\n    = \"<?xml version='1.0' encoding='UTF-8'?>\\n\"\n      \"<html xmlns=\\\"http://www.w3.org/1999/xhtml\\\" \"\n      \"lang=\\\"en\\\" xml:lang=\\\"en\\\">\\n\"\n      \"  <head>\\n\"\n      \"    <title></title>\\n\"\n      \"    <link rel=\\\"stylesheet\\\" \"\n      \"type=\\\"text/css\\\" href=\\\"stylesheet.css\\\"/>\\n\"\n      \"    <meta http-equiv=\\\"Content-Type\\\" \"\n      \"content=\\\"text/html; charset=utf-8\\\"/>\\n\"\n      \"  </head>\\n\"\n      \"  <body>\\n\"\n      \"    %s\\n\"\n      \"  </body>\\n\"\n      \"</html>\\n\";\nbool\nApvlvLab::load (const string &filename)\n{\n  return false;\n}\n\nApvlvLab::~ApvlvLab () = default;\n\nbool\nApvlvLab::pageRenderToWebView (int pn, double zm, int rot, WebView *webview)\n{\n  webview->setZoomFactor (zm);\n  QUrl url = QString (\"apvlv:///\") + QString::number (pn);\n  webview->load (url);\n  return true;\n}\n\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/ApvlvLab.h",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvLab.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_LAB_H_\n#define _APVLV_LAB_H_\n\n#include \"ApvlvFile.h\"\n#include <QWebEngineView>\n\nnamespace apvlv\n{\n\nclass ApvlvLab : public File\n{\npublic:\n  bool load (const std::string &filename) override;\n\n  ~ApvlvLab () override;\n\n  bool pageRenderToWebView (int pn, double zm, int rot,\n                            WebView *webview) override;\n\nprivate:\n};\n\n}\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/ApvlvLog.cc",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2024> Alf\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvLog.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QFileInfo>\n#include <QLoggingCategory>\n#include <QTime>\n#include <iostream>\n#include <string>\n\n#include \"ApvlvLog.h\"\n\nnamespace apvlv\n{\nvoid\nApvlvLog::setLogFile (const std::string &path)\n{\n  using FileFlag = QIODevice::OpenModeFlag;\n  if (!path.empty ())\n    {\n      mFile.setFileName (QString::fromLocal8Bit (path));\n      if (mFile.open (FileFlag::Text | FileFlag::WriteOnly | FileFlag::Append)\n          == false)\n        {\n          std::cerr << \"Open log file: \" << path\n                    << \"error: \" << mFile.errorString ().toStdString ()\n                    << std::endl;\n          return;\n        }\n\n      mTextStream.setDevice (&mFile);\n    }\n\n  QLoggingCategory::setFilterRules (\"qt.*=false\\n\"\n                                    \"default.debug=true\\n\"\n                                    \"default.*=true\");\n  qInstallMessageHandler (ApvlvLog::logMessage);\n}\n\nvoid\nApvlvLog::writeMessage (const QString &msg)\n{\n  std::lock_guard<std::mutex> lock (mMutex);\n#ifdef _DEBUG\n  std::cout << msg.toStdString () << std::endl;\n#endif\n\n  auto endstr = \"\\n\";\n#ifdef WIN32\n  endstr = \"\\r\\n\";\n#endif\n  if (mTextStream.device () && mTextStream.device ()->isOpen ())\n    {\n      mTextStream << msg << endstr;\n    }\n}\n\nApvlvLog::~ApvlvLog ()\n{\n  if (mFile.isOpen ())\n    {\n      mFile.close ();\n    }\n}\n\nApvlvLog *\nApvlvLog::instance ()\n{\n  static ApvlvLog log;\n  return &log;\n}\n\nvoid\nApvlvLog::logMessage (QtMsgType type, const QMessageLogContext &context,\n                      const QString &msg)\n{\n  auto now = QTime::currentTime ();\n  auto nowstr = now.toString (\"hh:mm:ss.zzz\");\n  QString log = nowstr + \" \";\n\n  if (context.file)\n    {\n      auto filename = QFileInfo (context.file).fileName ().toStdString ();\n      log += QString::asprintf (\"%s:%d \", filename.c_str (), context.line);\n      log += QString::asprintf (\"%s \", context.function);\n    }\n  log += msg;\n\n  ApvlvLog::instance ()->writeMessage (log);\n}\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/ApvlvLog.h",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2024> Alf\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvLog.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n#ifndef _APVLV_LOG_H_\n#define _APVLV_LOG_H_\n\n#include <QFile>\n#include <QTextStream>\n#include <QtMessageHandler>\n#include <memory>\n#include <mutex>\n\nnamespace apvlv\n{\n\nclass ApvlvLog final\n{\npublic:\n  ApvlvLog (const ApvlvLog &) = delete;\n  const ApvlvLog &operator= (const ApvlvLog &) = delete;\n  void setLogFile (const std::string &path);\n  ~ApvlvLog ();\n\n  static ApvlvLog *instance ();\n  static void logMessage (QtMsgType type, const QMessageLogContext &context,\n                          const QString &msg);\n\nprivate:\n  ApvlvLog () = default;\n  void writeMessage (const QString &log);\n\n  QFile mFile;\n  QTextStream mTextStream;\n  std::mutex mMutex;\n};\n\n};\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/ApvlvMarkdown.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvMarkdown.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <algorithm>\n#include <array>\n#include <fstream>\n\n#include \"ApvlvMarkdown.h\"\n\n#include <qassert.h>\n\nnamespace apvlv\n{\nusing namespace std;\n\nMarkdownNode::MarkdownNode (const MarkdownNode &other)\n{\n  node_type = other.node_type;\n  list_type = other.list_type;\n  heading_level = other.heading_level;\n  literal = other.literal;\n  title = other.title;\n  url = other.url;\n  for (auto &child : other.children)\n    {\n      auto nchild = new MarkdownNode (*child);\n      children.push_back (unique_ptr<MarkdownNode> (nchild));\n    }\n}\n\nMarkdownNode::MarkdownNode (MarkdownNode &&other) noexcept\n{\n  node_type = other.node_type;\n  list_type = other.list_type;\n  heading_level = other.heading_level;\n  literal.swap (other.literal);\n  title.swap (other.title);\n  url.swap (other.url);\n  children.swap (other.children);\n}\n\nMarkdownNode &\nMarkdownNode::operator= (const MarkdownNode &other)\n{\n  node_type = other.node_type;\n  list_type = other.list_type;\n  heading_level = other.heading_level;\n  literal = other.literal;\n  title = other.title;\n  url = other.url;\n  for (const auto &child : other.children)\n    {\n      auto nchild = new MarkdownNode (*child);\n      children.push_back (unique_ptr<MarkdownNode> (nchild));\n    }\n  return *this;\n}\n\nMarkdownNode &\nMarkdownNode::operator= (MarkdownNode &&other) noexcept\n{\n  node_type = other.node_type;\n  list_type = other.list_type;\n  heading_level = other.heading_level;\n  literal.swap (other.literal);\n  title.swap (other.title);\n  url.swap (other.url);\n  children.swap (other.children);\n  return *this;\n}\n\nint\nMarkdownNode::childrenCount ()\n{\n  return static_cast<int> (children.size ());\n}\n\nMarkdownNode *\nMarkdownNode::childAt (int index)\n{\n  return children[index].get ();\n}\n\nvoid\nMarkdownNode::appendChildPtr (std::unique_ptr<MarkdownNode> ptr)\n{\n  children.push_back (std::move (ptr));\n}\n\nvoid\nMarkdownNode::appendChild (MarkdownNode *n)\n{\n  children.push_back (unique_ptr<MarkdownNode> (n));\n}\n\nvoid\nMarkdownNode::removeChild (MarkdownNode *n)\n{\n  auto iter = std::find_if (children.begin (), children.end (),\n                            [n] (const auto &i)\n                              {\n                                return i.get () == n;\n                              });\n  if (iter != children.end ())\n    {\n      children.erase (iter);\n    }\n}\n\nstd::vector<std::string>\nMarkdownNode::getListTexts ()\n{\n  std::vector<std::string> texts;\n  for (const auto &i : children)\n    {\n      auto p = i->children.front ().get ();\n      auto t = p->children.front ().get ();\n      texts.push_back (t->literal);\n    }\n  return texts;\n}\n\nvoid\nMarkdownNode::setListTexts (cmark_list_type _type,\n                            const std::vector<std::string> &texts)\n{\n  children.clear ();\n  node_type = CMARK_NODE_LIST;\n  list_type = _type;\n  for (const auto &text : texts)\n    {\n      auto i = create (this, CMARK_NODE_ITEM);\n      auto p = create (i, CMARK_NODE_PARAGRAPH);\n      auto t = create (p, CMARK_NODE_TEXT);\n      t->literal = text;\n    }\n}\n\nvoid\nMarkdownNode::setNoListTexts (const std::vector<std::string> &texts)\n{\n  setListTexts (CMARK_NO_LIST, texts);\n}\n\nvoid\nMarkdownNode::setBulletListTexts (const std::vector<std::string> &texts)\n{\n  setListTexts (CMARK_BULLET_LIST, texts);\n}\n\nvoid\nMarkdownNode::setOrderedListTexts (const std::vector<std::string> &texts)\n{\n  setListTexts (CMARK_ORDERED_LIST, texts);\n}\n\nstd::pair<int, std::string>\nMarkdownNode::headText ()\n{\n  if (node_type != CMARK_NODE_HEADING || childrenCount () < 1)\n    {\n      throw std::invalid_argument (\"MarkdownNode::headText\");\n    }\n\n  auto n = childAt (0);\n  std::pair<int, std::string> res;\n  res.first = n->heading_level;\n  res.second = n->literal;\n  return res;\n}\n\nvoid\nMarkdownNode::appendHeadText (int _level, const std::string &_text)\n{\n  auto h = create (this, CMARK_NODE_HEADING);\n  h->heading_level = _level;\n  auto ht = create (h, CMARK_NODE_TEXT);\n  ht->literal = _text;\n}\n\nvoid\nMarkdownNode::appendHeadAndList (int _level, cmark_list_type _type,\n                                 const HeadAndList &headAndList)\n{\n  appendHeadText (_level, headAndList.first);\n  auto list = create (this, CMARK_NODE_LIST);\n  list->setListTexts (_type, headAndList.second);\n}\n\nvoid\nMarkdownNode::appendHeadAndNoList (int _level, const HeadAndList &headAndList)\n{\n  appendHeadAndList (_level, CMARK_NO_LIST, headAndList);\n}\n\nvoid\nMarkdownNode::appendHeadAndBulletList (int _level,\n                                       const HeadAndList &headAndList)\n{\n  appendHeadAndList (_level, CMARK_BULLET_LIST, headAndList);\n}\n\nvoid\nMarkdownNode::appendHeadAndOrderedList (int _level,\n                                        const HeadAndList &headAndList)\n{\n  appendHeadAndList (_level, CMARK_ORDERED_LIST, headAndList);\n}\n\nstd::unique_ptr<MarkdownNode>\nMarkdownNode::fromCmarkNode (cmark_node *node)\n{\n  auto mn = make_unique<MarkdownNode> (cmark_node_get_type (node));\n  switch (mn->node_type)\n    {\n    case CMARK_NODE_HEADING:\n      mn->heading_level = cmark_node_get_heading_level (node);\n      break;\n    case CMARK_NODE_LIST:\n      mn->list_type = cmark_node_get_list_type (node);\n      break;\n    case CMARK_NODE_IMAGE:\n    case CMARK_NODE_LINK:\n      mn->title = cmark_node_get_title (node);\n      mn->url = cmark_node_get_url (node);\n      break;\n    default:\n      auto l = cmark_node_get_literal (node);\n      if (l)\n        mn->literal = l;\n    }\n  for (auto n = cmark_node_first_child (node); n != nullptr;\n       n = cmark_node_next (n))\n    {\n      auto cmn = MarkdownNode::fromCmarkNode (n);\n      mn->children.emplace_back (std::move (cmn));\n    }\n  return mn;\n}\n\nMarkdownNode *\nMarkdownNode::create (MarkdownNode *parent, cmark_node_type _type,\n                      string_view literal)\n{\n  Q_ASSERT (parent != nullptr);\n  auto mn = make_unique<MarkdownNode> (_type);\n  mn->literal = literal;\n  auto node = mn.get ();\n  parent->appendChildPtr (std::move (mn));\n  return node;\n}\n\ncmark_node *\nMarkdownNode::toCmarkNode ()\n{\n  auto doc = cmark_node_new (node_type);\n  switch (node_type)\n    {\n    case CMARK_NODE_HEADING:\n      cmark_node_set_heading_level (doc, heading_level);\n      break;\n\n    case CMARK_NODE_LIST:\n      cmark_node_set_list_type (doc, list_type);\n      break;\n\n    case CMARK_NODE_IMAGE:\n    case CMARK_NODE_LINK:\n      cmark_node_set_title (doc, title.c_str ());\n      cmark_node_set_url (doc, url.c_str ());\n      break;\n\n    default:\n      break;\n    }\n  cmark_node_set_literal (doc, literal.c_str ());\n\n  for (auto const &child : children)\n    {\n      auto n = child.get ();\n      auto d = n->toCmarkNode ();\n      cmark_node_append_child (doc, d);\n    }\n\n  return doc;\n}\n\nMarkdown::Markdown ()\n    : mRoot{ make_unique<MarkdownNode> (CMARK_NODE_DOCUMENT) }\n{\n  mRoot->node_type = CMARK_NODE_DOCUMENT;\n}\n\nMarkdown::Markdown (const Markdown &other)\n{\n  if (this != &other)\n    {\n      mRoot = make_unique<MarkdownNode> (MarkdownNode (*other.mRoot));\n    }\n}\n\nMarkdown::Markdown (Markdown &&other) noexcept\n{\n  if (this != &other)\n    {\n      mRoot.swap (other.mRoot);\n    }\n}\n\nMarkdown &\nMarkdown::operator= (const Markdown &other)\n{\n  if (this != &other)\n    {\n      mRoot = make_unique<MarkdownNode> (MarkdownNode (*other.mRoot));\n    }\n\n  return *this;\n}\n\nMarkdown &\nMarkdown::operator= (Markdown &&other) noexcept\n{\n  if (this != &other)\n    {\n      mRoot.swap (other.mRoot);\n    }\n  return *this;\n}\n\nbool\nMarkdown::loadFromFile (const std::string &filename)\n{\n  ifstream ifs (filename, ifstream::binary);\n  if (!ifs.is_open ())\n    {\n      return false;\n    }\n\n  return loadFromStream (ifs);\n}\n\nbool\nMarkdown::loadFromStream (std::istream &is)\n{\n  auto parser = cmark_parser_new (CMARK_OPT_DEFAULT);\n  array<char, 16384> buffer{};\n  while (is.good ())\n    {\n      is.read (buffer.data (), buffer.size ());\n      auto got = is.gcount ();\n      if (got == 0)\n        {\n          break;\n        }\n      cmark_parser_feed (parser, buffer.data (), got);\n    }\n\n  auto doc = cmark_parser_finish (parser);\n  cmark_parser_free (parser);\n\n  if (doc)\n    {\n      mRoot = MarkdownNode::fromCmarkNode (doc);\n      cmark_node_free (doc);\n      return true;\n    }\n  else\n    {\n      return false;\n    }\n}\n\nbool\nMarkdown::saveToFile (const std::string &filename)\n{\n  ofstream ofs (filename, ofstream::binary);\n  if (!ofs.is_open ())\n    {\n      return false;\n    }\n\n  return saveToStream (ofs);\n}\n\nbool\nMarkdown::saveToStream (std::ostream &os)\n{\n  auto doc = mRoot->toCmarkNode ();\n  auto text = cmark_render_commonmark (doc, CMARK_OPT_DEFAULT, 78);\n  os << text;\n  free (text);\n  cmark_node_free (doc);\n  return false;\n}\n\nMarkdownNode *\nMarkdown::root () const\n{\n  return mRoot.get ();\n}\n\n}\n"
  },
  {
    "path": "src/ApvlvMarkdown.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvEditor.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_MARKDOWN_H_\n#define _APVLV_MARKDOWN_H_\n\n#include <cmark.h>\n#include <memory>\n#include <queue>\n#include <string>\n\nnamespace apvlv\n{\nstruct MarkdownNode\n{\n  cmark_node_type node_type{ CMARK_NODE_TEXT };\n  cmark_list_type list_type{ CMARK_NO_LIST };\n  int heading_level{ 1 };\n  std::string literal;\n  std::string title;\n  std::string url;\n  std::vector<std::unique_ptr<MarkdownNode>> children;\n\n  explicit MarkdownNode (cmark_node_type _type) : node_type (_type) {};\n  ~MarkdownNode () = default;\n  MarkdownNode (const MarkdownNode &other);\n  MarkdownNode (MarkdownNode &&other) noexcept;\n  MarkdownNode &operator= (const MarkdownNode &other);\n  MarkdownNode &operator= (MarkdownNode &&other) noexcept;\n\n  int childrenCount ();\n  MarkdownNode *childAt (int index);\n  void appendChildPtr (std::unique_ptr<MarkdownNode> ptr);\n  void appendChild (MarkdownNode *n);\n  void removeChild (MarkdownNode *n);\n\n  std::vector<std::string> getListTexts ();\n  void setListTexts (cmark_list_type _type,\n                     const std::vector<std::string> &texts);\n  void setNoListTexts (const std::vector<std::string> &texts);\n  void setBulletListTexts (const std::vector<std::string> &texts);\n  void setOrderedListTexts (const std::vector<std::string> &texts);\n\n  using HeadAndList = std::pair<std::string, std::vector<std::string>>;\n  std::pair<int, std::string> headText ();\n  void appendHeadText (int _level, const std::string &_text);\n  void appendHeadAndList (int _level, cmark_list_type _type,\n                          const HeadAndList &headAndList);\n  void appendHeadAndNoList (int _level, const HeadAndList &headAndList);\n  void appendHeadAndBulletList (int _level, const HeadAndList &headAndList);\n  void appendHeadAndOrderedList (int _level, const HeadAndList &headAndList);\n\n  static std::unique_ptr<MarkdownNode> fromCmarkNode (cmark_node *node);\n  static MarkdownNode *create (MarkdownNode *parent, cmark_node_type _type,\n                               std::string_view literal = \"\");\n  cmark_node *toCmarkNode ();\n};\n\nclass Markdown\n{\npublic:\n  Markdown ();\n  ~Markdown () = default;\n  Markdown (const Markdown &other);\n  Markdown (Markdown &&other) noexcept;\n  Markdown &operator= (const Markdown &other);\n  Markdown &operator= (Markdown &&other) noexcept;\n\n  bool loadFromFile (const std::string &filename);\n  bool loadFromStream (std::istream &is);\n  bool saveToFile (const std::string &filename);\n  bool saveToStream (std::ostream &os);\n  [[nodiscard]] MarkdownNode *root () const;\n  static std::unique_ptr<Markdown>\n  create ()\n  {\n    return std::make_unique<Markdown> ();\n  }\n\nprivate:\n  std::unique_ptr<MarkdownNode> mRoot;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "src/ApvlvNote.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvNote.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QDir>\n#include <cmark.h>\n#include <filesystem>\n#include <fstream>\n\n#include \"ApvlvFile.h\"\n#include \"ApvlvMarkdown.h\"\n#include \"ApvlvNote.h\"\n#include \"ApvlvUtil.h\"\n\nnamespace apvlv\n{\nusing namespace std;\n\nvoid\nLocation::set (int page1, const ApvlvPoint *point1, int offset1,\n               const std::string &path1, const std::string &anchor1)\n{\n  page = page1;\n  if (point1)\n    {\n      x = point1->x;\n      y = point1->y;\n    }\n  offset = offset1;\n  path = path1;\n  anchor = anchor1;\n}\n\nvoid\nLocation::fromMarkdownNode (MarkdownNode *node)\n{\n  stringstream ss{ node->literal };\n  ss >> page >> x >> y >> offset;\n  if (!ss.eof ())\n    {\n      auto str = ss.str ();\n      auto pos = str.find (\"[[\");\n      auto end = str.find (\"]]\");\n      path = str.substr (pos + 2, end - pos - 2);\n      anchor = str.substr (end + 2);\n    }\n}\n\nvoid\nLocation::toMarkdownNode (MarkdownNode *node) const\n{\n  stringstream ss;\n  ss << page << \" \" << x << \" \" << y << \" \" << offset;\n  if (!path.empty ())\n    {\n      ss << \" [[\" << path << \"]]#\" << anchor;\n    }\n  node->node_type = CMARK_NODE_TEXT;\n  node->literal = ss.str ();\n}\n\nvoid\nComment::fromMarkdownNode (MarkdownNode *node)\n{\n  auto qn = node->childAt (0);\n  auto qp = qn->childAt (0);\n  auto qt = qp->childAt (0);\n  quoteText = qt->literal;\n\n  auto cn = node->childAt (1);\n  commentText = cn->literal;\n\n  auto list = node->childAt (2);\n\n  auto i = list->childAt (0);\n  auto p = i->childAt (0);\n  auto t = p->childAt (0);\n  begin.fromMarkdownNode (t);\n\n  i = list->childAt (1);\n  p = i->childAt (0);\n  t = p->childAt (0);\n  end.fromMarkdownNode (t);\n\n  i = list->childAt (2);\n  p = i->childAt (0);\n  t = p->childAt (0);\n  struct tm tm{};\n  strptime (t->literal.c_str (), \"%a %b %d %H:%M:%S %Y\", &tm);\n  time = std::mktime (&tm);\n}\n\nvoid\nComment::toMarkdownNode (MarkdownNode *node) const\n{\n  auto qn = MarkdownNode::create (node, CMARK_NODE_BLOCK_QUOTE);\n  auto qp = MarkdownNode::create (qn, CMARK_NODE_PARAGRAPH);\n  MarkdownNode::create (qp, CMARK_NODE_TEXT, quoteText);\n\n  MarkdownNode::create (node, CMARK_NODE_CODE_BLOCK, commentText);\n\n  auto list = MarkdownNode::create (node, CMARK_NODE_LIST);\n  list->list_type = CMARK_NO_LIST;\n\n  auto i = MarkdownNode::create (list, CMARK_NODE_ITEM);\n  auto p = MarkdownNode::create (i, CMARK_NODE_PARAGRAPH);\n  auto t = MarkdownNode::create (p, CMARK_NODE_TEXT);\n  begin.toMarkdownNode (t);\n\n  i = MarkdownNode::create (list, CMARK_NODE_ITEM);\n  p = MarkdownNode::create (i, CMARK_NODE_PARAGRAPH);\n  t = MarkdownNode::create (p, CMARK_NODE_TEXT);\n  end.toMarkdownNode (t);\n\n  i = MarkdownNode::create (list, CMARK_NODE_ITEM);\n  p = MarkdownNode::create (i, CMARK_NODE_PARAGRAPH);\n  t = MarkdownNode::create (p, CMARK_NODE_TEXT);\n  t->literal = std::ctime (&time);\n}\n\nNote::~Note () = default;\n\nbool\nNote::loadStreamV1 (std::ifstream &is)\n{\n  auto doc = Markdown::create ();\n  doc->loadFromStream (is);\n  if (!doc)\n    {\n      return false;\n    }\n\n  auto node = doc->root ();\n  auto n = node->childAt (0);\n  auto list = node->childAt (1);\n  loadV1Version (list);\n\n  for (auto i = 2; i < node->childrenCount (); i++)\n    {\n      n = node->childAt (i);\n      if (n->node_type == CMARK_NODE_THEMATIC_BREAK)\n        {\n          continue;\n        }\n\n      if (i >= node->childrenCount () - 1)\n        break;\n\n      list = node->childAt (i + 1);\n      if (list == nullptr)\n        break;\n\n      // skip empty list of heading\n      if (list->node_type == CMARK_NODE_HEADING && list->heading_level == 1)\n        {\n          continue;\n        }\n\n      auto res = n->headText ();\n      i++;\n      if (res.second == \"Meta Data\")\n        {\n          loadV1MetaData (list);\n        }\n      else if (res.second == \"Comments\")\n        {\n          loadV1Comments (list);\n        }\n      else if (res.second == \"References\")\n        {\n          loadV1References (list);\n        }\n      else if (res.second == \"Links\")\n        {\n          loadV1Links (list);\n        }\n      else\n        {\n          qDebug () << \"Unknown head \\\"\" << res.second << \"\\\"\";\n        }\n    }\n\n  return true;\n}\n\nbool\nNote::loadStream (std::ifstream &is)\n{\n  string line;\n\n  getline (is, line);\n  if (!line.starts_with (\"---\"))\n    {\n      qWarning () << \"note header not found\";\n      return false;\n    }\n\n  string version;\n  is >> version >> version;\n  if (version == \"1\")\n    {\n      is.seekg (0, ios::beg);\n      return loadStreamV1 (is);\n    }\n\n  return false;\n}\n\nbool\nNote::load (std::string_view sv)\n{\n  string path = string (sv);\n  if (path.empty ())\n    path = mPath;\n\n  if (path.empty ())\n    return false;\n\n  mPath = path;\n\n  ifstream ifs{ path };\n  if (!ifs.is_open ())\n    return false;\n\n  auto ret = loadStream (ifs);\n  ifs.close ();\n  return ret;\n}\n\nvoid\nNote::loadV1Version (MarkdownNode *node)\n{\n  for (auto i = 0; i < node->childrenCount (); i++)\n    {\n      auto n = node->childAt (i);\n      if (n->node_type == CMARK_NODE_SOFTBREAK)\n        continue;\n\n      auto s = QString::fromLocal8Bit (n->literal);\n      auto vs = s.split (\":\");\n      if (vs.size () == 2)\n        {\n          if (vs[0].trimmed () == \"version\")\n            {\n              auto version = vs[1].trimmed ();\n              Q_ASSERT (version == \"1\");\n            }\n        }\n    }\n}\n\nvoid\nNote::loadV1MetaData (MarkdownNode *node)\n{\n  auto texts = node->getListTexts ();\n  for (const auto &text : texts)\n    {\n      auto tokens = QString::fromLocal8Bit (text).split (\":\");\n      if (tokens.size () == 2)\n        {\n          auto k = tokens[0].trimmed ();\n          auto v = tokens[1].trimmed ();\n          if (k == \"tag\")\n            {\n              auto ts = v.split (\",\");\n              for (auto const &t : ts)\n                {\n                  auto tag = t.trimmed ();\n                  if (!tag.isEmpty ())\n                    {\n                      qDebug () << \"got tag: \" << tag;\n                      mTagSet.insert (tag.toStdString ());\n                    }\n                }\n            }\n          else if (k == \"score\")\n            {\n              mScore = v.toFloat ();\n            }\n        }\n    }\n}\n\nvoid\nNote::loadV1Comments (MarkdownNode *node)\n{\n  for (auto i = 0; i < node->childrenCount (); i++)\n    {\n      auto ni = node->childAt (i);\n      auto comment = Comment{};\n      comment.fromMarkdownNode (ni);\n      addComment (comment);\n    }\n}\n\nvoid\nNote::loadV1References (MarkdownNode *node)\n{\n  auto texts = node->getListTexts ();\n  for (const auto &text : texts)\n    {\n      if (!text.empty ())\n        {\n          mReferences.insert (text);\n        }\n    }\n}\n\nvoid\nNote::loadV1Links (MarkdownNode *node)\n{\n  auto texts = node->getListTexts ();\n  for (const auto &text : texts)\n    {\n      if (!text.empty ())\n        {\n          mLinks.insert (text);\n        }\n    }\n}\n\nvoid\nNote::appendV1Version (MarkdownNode *doc)\n{\n  MarkdownNode::create (doc, CMARK_NODE_THEMATIC_BREAK);\n\n  auto g = MarkdownNode::create (doc, CMARK_NODE_PARAGRAPH);\n  MarkdownNode::create (g, CMARK_NODE_TEXT, \"version: 1\");\n  MarkdownNode::create (g, CMARK_NODE_LINEBREAK);\n  MarkdownNode::create (g, CMARK_NODE_TEXT, \"path: \" + mPath);\n\n  MarkdownNode::create (doc, CMARK_NODE_THEMATIC_BREAK);\n}\n\nvoid\nNote::appendV1MetaData (MarkdownNode *doc)\n{\n  MarkdownNode::HeadAndList headAndList;\n  headAndList.first = \"Meta Data\";\n  string tags;\n  for (const auto &cp : mTagSet)\n    {\n      tags += cp + \",\";\n    }\n  headAndList.second.push_back (\"score: \"\n                                + QString::number (mScore).toStdString ());\n  headAndList.second.push_back (\"tag: \" + tags);\n  doc->appendHeadAndBulletList (1, headAndList);\n}\n\nvoid\nNote::appendV1Comments (MarkdownNode *doc)\n{\n  doc->appendHeadText (1, \"Comments\");\n\n  auto list = MarkdownNode::create (doc, CMARK_NODE_LIST);\n  list->list_type = CMARK_ORDERED_LIST;\n  for (auto const &loc_comment : mCommentList)\n    {\n      auto comment = loc_comment.second;\n      auto n = MarkdownNode::create (list, CMARK_NODE_ITEM);\n      comment.toMarkdownNode (n);\n    }\n}\n\nvoid\nNote::appendV1References (MarkdownNode *doc)\n{\n  MarkdownNode::HeadAndList headAndList;\n  headAndList.first = \"References\";\n  for (auto const &r : mReferences)\n    {\n      headAndList.second.push_back (r);\n    }\n  doc->appendHeadAndBulletList (1, headAndList);\n}\n\nvoid\nNote::appendV1Links (MarkdownNode *doc)\n{\n  MarkdownNode::HeadAndList headAndList;\n  headAndList.first = \"Links\";\n  for (auto const &r : mLinks)\n    {\n      headAndList.second.push_back (r);\n    }\n  doc->appendHeadAndBulletList (1, headAndList);\n}\n\nbool\nNote::dumpStream (std::ostream &os)\n{\n  /* handmade version is ugly */\n#if 0\n  os << \"---\" << endl;\n  os << \"version: 1\" << endl;\n  os << \"path: \" << mFile->getFilename () << endl;\n\n  os << \"---\" << endl;\n  os << \"# Meta Data\" << endl;\n  os << \"- tag: \";\n  std::ranges::for_each (mTagSet,\n                         [&os] (const string &tag) { os << tag << \",\"; });\n  os << endl;\n  os << \"- score: \" << mScore << endl;\n  os << endl;\n\n  os << \"# Comments\" << endl;\n  auto index = 0;\n  std::ranges::for_each (mCommentList,\n                         [&os, &index] (const pair<Location, Comment> &pair1) {\n                           os << \" - \" << index++ << endl;\n                           os << pair1.second;\n                         });\n  os << endl;\n\n  os << \"# References\" << endl;\n  std::ranges::for_each (\n      mReferences, [&os] (const string &ref) { os << \"- \" << ref << endl; });\n  os << endl;\n\n  os << \"# Links\" << endl;\n  std::ranges::for_each (\n      mLinks, [&os] (const string &link) { os << \"- \" << link << endl; });\n  os << endl;\n\n#else\n  auto doc = Markdown::create ();\n  auto node = doc->root ();\n\n  appendV1Version (node);\n\n  appendV1MetaData (node);\n\n  appendV1Comments (node);\n\n  appendV1References (node);\n\n  appendV1Links (node);\n\n  doc->saveToStream (os);\n#endif\n\n  return true;\n}\n\nbool\nNote::dump (std::string_view sv)\n{\n  string path = string (sv);\n  if (path.empty ())\n    path = mPath;\n\n  if (path.empty ())\n    return false;\n\n  auto fspath = filesystem::path (path).parent_path ();\n  std::error_code code;\n  filesystem::create_directories (fspath, code);\n\n  ofstream ofs{ path };\n  if (!ofs.is_open ())\n    return false;\n\n  auto ret = dumpStream (ofs);\n  ofs.close ();\n  return ret;\n}\n\nstd::string\nNote::notePathOfFile (File *file)\n{\n  auto filename = file->getFilename ();\n  return notePathOfPath (filename);\n}\n\nstd::string\nNote::notePathOfPath (std::string_view sv)\n{\n  auto homedir = QDir::home ().filesystemAbsolutePath ().string ();\n  string filename = string (sv);\n  if (filename.find (homedir) == 0)\n    filename = filename.substr (homedir.size () + 1);\n  if (filename[0] == filesystem::path::preferred_separator)\n    filename = filename.substr (1);\n  if (filename[1] == ':')\n    filename[1] = '-';\n  auto path = NotesDir + filesystem::path::preferred_separator + filename;\n  return path + \".md\";\n}\n\n}\n"
  },
  {
    "path": "src/ApvlvNote.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvEditor.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_NOTE_H_\n#define _APVLV_NOTE_H_\n\n#include <map>\n#include <sstream>\n#include <string>\n#include <string_view>\n#include <unordered_set>\n#include <vector>\n\nnamespace apvlv\n{\nconstexpr float NoteScoreMin = 0.0f;\nconstexpr float NoteScoreMax = 10.0f;\n\nclass MarkdownNode;\nstruct ApvlvPoint;\nstruct Location\n{\n  int page{ 0 };\n  double x{ 0 };\n  double y{ 0 };\n  int offset{ -1 };\n  std::string path;\n  std::string anchor;\n\n  friend bool\n  operator< (const Location &a, const Location &b)\n  {\n    if (a.page != b.page)\n      return a.page < b.page;\n    if (a.y != b.y)\n      return a.y < b.y;\n    if (a.x != b.x)\n      return a.x < b.x;\n    if (a.offset != b.offset)\n      return a.offset < b.offset;\n    if (a.path != b.path)\n      return a.path < b.path;\n    return false;\n  }\n\n  void set (int page1, const ApvlvPoint *point1, int offset1 = 0,\n            const std::string &path1 = \"\", const std::string &anchor1 = \"\");\n\n  void fromMarkdownNode (MarkdownNode *node);\n  void toMarkdownNode (MarkdownNode *node) const;\n};\n\nclass File;\nclass Note;\nstruct Comment\n{\n  Comment ()\n      : time{ std::chrono::system_clock::to_time_t (\n            std::chrono::system_clock::now ()) }\n  {\n  }\n\n  std::string quoteText;\n  std::string commentText;\n  Location begin;\n  Location end;\n  time_t time;\n\n  void fromMarkdownNode (MarkdownNode *node);\n  void toMarkdownNode (MarkdownNode *node) const;\n};\n\nclass Note\n{\npublic:\n  Note () {}\n  ~Note ();\n\n  static std::string notePathOfFile (File *file);\n\n  static std::string notePathOfPath (std::string_view sv);\n\n  bool loadStreamV1 (std::ifstream &is);\n  bool loadStream (std::ifstream &is);\n  bool load (std::string_view path = \"\");\n\n  bool dumpStream (std::ostream &os);\n  bool dump (std::string_view path = \"\");\n\n  void\n  setScore (float score)\n  {\n    mScore = score;\n    dump ();\n  }\n\n  float\n  score ()\n  {\n    return mScore;\n  }\n\n  void\n  addTag (const std::string &tag)\n  {\n    mTagSet.insert (tag);\n    dump ();\n  }\n\n  void\n  removeTag (const std::string &tag)\n  {\n    mTagSet.erase (tag);\n    dump ();\n  }\n\n  const std::unordered_set<std::string> &\n  tag ()\n  {\n    return mTagSet;\n  }\n\n  std::string\n  tagString ()\n  {\n    if (mTagSet.empty ())\n      {\n        return \"\";\n      }\n\n    std::ostringstream oss;\n    auto itr = mTagSet.begin ();\n    oss << *itr;\n    ++itr;\n    while (itr != mTagSet.end ())\n      {\n        oss << \",\" << *itr;\n        ++itr;\n      }\n    return oss.str ();\n  }\n\n  void\n  setRemark (const std::string &remark)\n  {\n    mRemark = remark;\n    dump ();\n  }\n\n  const std::string &\n  remark ()\n  {\n    return mRemark;\n  }\n\n  void\n  addReference (const std::string &ref)\n  {\n    mReferences.insert (ref);\n    dump ();\n  }\n\n  void\n  removeReference (const std::string &ref)\n  {\n    mReferences.erase (ref);\n    dump ();\n  }\n\n  const std::unordered_set<std::string> &\n  references ()\n  {\n    return mReferences;\n  }\n\n  void\n  addLink (const std::string &link)\n  {\n    mLinks.insert (link);\n    dump ();\n  }\n\n  void\n  removeLink (const std::string &link)\n  {\n    mLinks.erase (link);\n    dump ();\n  }\n\n  const std::unordered_set<std::string> &\n  links ()\n  {\n    return mLinks;\n  }\n\n  void\n  addComment (const Comment &comment)\n  {\n    mCommentList.insert ({ comment.begin, comment });\n    dump ();\n  }\n\n  void\n  removeComment (const Comment &comment)\n  {\n    mCommentList.erase (comment.begin);\n    dump ();\n  }\n\n  std::vector<Comment>\n  getCommentsInPage (int page)\n  {\n    std::vector<Comment> comments;\n    for (const auto &pair1 : mCommentList)\n      {\n        if (pair1.first.page == page)\n          comments.push_back (pair1.second);\n      };\n    return comments;\n  }\n\n  std::vector<Comment>\n  getCommentsInPath (const std::string &path)\n  {\n    std::vector<Comment> comments;\n    for (const auto &pair1 : mCommentList)\n      {\n        if (pair1.first.path == path)\n          comments.push_back (pair1.second);\n      }\n    return comments;\n  }\n\nprivate:\n  void loadV1Version (MarkdownNode *node);\n  void loadV1MetaData (MarkdownNode *node);\n  void loadV1Comments (MarkdownNode *node);\n  void loadV1References (MarkdownNode *node);\n  void loadV1Links (MarkdownNode *node);\n  void appendV1Version (MarkdownNode *doc);\n  void appendV1MetaData (MarkdownNode *doc);\n  void appendV1Comments (MarkdownNode *doc);\n  void appendV1References (MarkdownNode *doc);\n  void appendV1Links (MarkdownNode *doc);\n\n  std::string mPath;\n\n  float mScore{ 0.0f };\n\n  std::unordered_set<std::string> mTagSet;\n\n  std::string mRemark;\n\n  std::unordered_set<std::string> mReferences;\n  std::unordered_set<std::string> mLinks;\n\n  std::map<Location, Comment> mCommentList;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "src/ApvlvNoteWidget.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvNoteWidget.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include \"ApvlvNoteWidget.h\"\n\n#include <QCompleter>\n#include <QInputDialog>\n#include <QLabel>\n#include <QPushButton>\n#include <QVBoxLayout>\n\nnamespace apvlv\n{\nusing namespace std;\n\nQString\nNoteDialog::getTag (const string &filename, const unordered_set<string> &tags,\n                    const QStringList &tagList)\n{\n  auto dia = make_unique<QDialog> (nullptr);\n  dia->setWindowTitle (QString::fromLocal8Bit (filename));\n  dia->setModal (true);\n  dia->setSizeGripEnabled (true);\n\n  auto layout = new QVBoxLayout (dia.get ());\n  dia->setLayout (layout);\n\n  auto hbox = new QHBoxLayout ();\n  auto label = new QLabel (dia.get ());\n  label->setText (tr (\"Input tag:\"));\n  hbox->addWidget (label);\n  auto entry = new QLineEdit (dia.get ());\n  hbox->addWidget (entry);\n  layout->addLayout (hbox);\n\n  hbox = new QHBoxLayout ();\n  auto ob = new QPushButton (tr (\"OK\"), dia.get ());\n  hbox->addWidget (ob);\n  QObject::connect (ob, SIGNAL (clicked (bool)), dia.get (),\n                    SLOT (accept ()));\n  auto oc = new QPushButton (tr (\"Cancel\"), dia.get ());\n  hbox->addWidget (oc);\n  QObject::connect (oc, SIGNAL (clicked (bool)), dia.get (),\n                    SLOT (reject ()));\n  layout->addLayout (hbox);\n\n  auto completer = new QCompleter (tagList, dia.get ());\n  completer->setFilterMode (Qt::MatchContains);\n  entry->setCompleter (completer);\n\n  auto res = dia->exec ();\n  if (res != QDialog::Accepted)\n    {\n      return {};\n    }\n\n  auto str = entry->text ();\n  return str;\n}\n\n}\n"
  },
  {
    "path": "src/ApvlvNoteWidget.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvEditor.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_NOTE_WIDGET_H_\n#define _APVLV_NOTE_WIDGET_H_\n\n#include <QDialog>\n#include <QFrame>\n#include <unordered_set>\n\n#include \"ApvlvEditor.h\"\n#include \"ApvlvNote.h\"\n\nnamespace apvlv\n{\nclass CommentEdit : public Editor\n{\n  Q_OBJECT\n\npublic:\nprivate:\n  Comment *mComment;\n};\n\nclass NoteEdit : public QFrame\n{\n  Q_OBJECT\n\npublic:\nprivate:\n  Note *mNote;\n};\n\nclass NoteDialog : QObject\n{\n  Q_OBJECT\n\npublic:\n  static QString getTag (const std::string &filename,\n                         const std::unordered_set<std::string> &tags,\n                         const QStringList &tagList);\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "src/ApvlvOCR.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvOCR.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include \"ApvlvOCR.h\"\n#include \"ApvlvParams.h\"\n\nnamespace apvlv\n{\n\nusing namespace std;\n\nOCR::OCR ()\n{\n  auto lang = ApvlvParams::instance ()->getGroupStringOrDefault (\n      \"ocr\", \"lang\", \"eng+chi_sim\");\n  mTessBaseAPI.Init (nullptr, lang.c_str ());\n}\n\nOCR::~OCR ()\n{\n  mTessBaseAPI.End ();\n}\n\nstd::unique_ptr<TextAreaVector>\nOCR::getTextArea (const QPixmap &pixmap)\n{\n  return nullptr;\n}\n\nstd::unique_ptr<char>\nOCR::getTextFromPixmap (const QPixmap &pixmap, QRect area)\n{\n  auto image = pixmap.toImage ();\n  image = image.convertToFormat (QImage::Format_RGB888);\n  mTessBaseAPI.SetImage (image.bits (), image.width (), image.height (), 3,\n                         static_cast<int> (image.bytesPerLine ()));\n  auto text = mTessBaseAPI.GetUTF8Text ();\n  if (area.isValid ())\n    {\n      mTessBaseAPI.SetRectangle (area.left (), area.top (), area.width (),\n                                 area.height ());\n    }\n  mTessBaseAPI.Clear ();\n  return unique_ptr<char> (text);\n}\n\n}\n"
  },
  {
    "path": "src/ApvlvOCR.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvOCR.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_OCR_H_\n#define _APVLV_OCR_H_\n\n#include <QPixmap>\n#include <QRect>\n#include <memory>\n#include <tesseract/capi.h>\n#include <vector>\n\nnamespace apvlv\n{\n\nusing TextAreaVector = std::vector<QRect>;\n\nclass OCR final\n{\npublic:\n  OCR ();\n  ~OCR ();\n\n  std::unique_ptr<TextAreaVector> getTextArea (const QPixmap &pixmap);\n  std::unique_ptr<char> getTextFromPixmap (const QPixmap &pixmap,\n                                           QRect area = QRect ());\n\nprivate:\n  TessBaseAPI mTessBaseAPI;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "src/ApvlvParams.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvParams.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <algorithm>\n#include <fstream>\n#include <iostream>\n#include <sstream>\n\n#include \"ApvlvCmds.h\"\n#include \"ApvlvParams.h\"\n#include \"ApvlvUtil.h\"\n\nnamespace apvlv\n{\nusing namespace std;\n\nApvlvParams::ApvlvParams ()\n{\n  push (\"inverted\", \"no\");\n  push (\"fullscreen\", \"no\");\n  push (\"zoom\", \"fitwidth\");\n  push (\"continuous\", \"yes\");\n  push (\"autoscrollpage\", \"yes\");\n  push (\"autoscrolldoc\", \"yes\");\n  push (\"noinfo\", \"no\");\n  push (\"width\", \"800\");\n  push (\"height\", \"600\");\n  push (\"fix_width\", \"0\");\n  push (\"fix_height\", \"0\");\n  push (\"background\", \"\");\n  push (\"warpscan\", \"yes\");\n  push (\"commandtimeout\", \"1000\");\n#ifdef WIN32\n  push (\"defaultdir\", \"C:\\\\\");\n#else\n  push (\"defaultdir\", \"/tmp\");\n#endif\n  push (\"guioptions\", \"mTsS\");\n  push (\"autoreload\", \"3\");\n  push (\"thread_count\", \"auto\");\n  push (\"lok_path\", \"/usr/lib64/libreoffice/program\");\n\n  push (\".pdf:engine\", \"MuPDF\");\n  push (\".epub:engine\", \"Web\");\n  push (\".fb2:engine\", \"Web\");\n  push (\".txt:engine\", \"MuPDF\");\n\n  push (\"ocr:lang\", \"eng+chi_sim\");\n}\n\nApvlvParams::~ApvlvParams () = default;\n\nbool\nApvlvParams::loadFile (const std::string &filename)\n{\n  string str;\n  fstream os (filename, ios::in);\n\n  if (!os.is_open ())\n    {\n      qWarning () << \"Open configure file \" << filename << \" error\";\n      return false;\n    }\n\n  while ((getline (os, str)))\n    {\n      string argu;\n      string data;\n      string crap;\n      stringstream is (str);\n\n      is >> crap;\n      if (crap[0] == '\\\"' || crap.empty ())\n        {\n          continue;\n        }\n\n      if (crap == \"set\")\n        {\n          is >> argu;\n          size_t off = argu.find ('=');\n          if (off == string::npos)\n            {\n              is >> crap >> data;\n              if (crap == \"=\")\n                {\n                  push (argu, data);\n                  continue;\n                }\n            }\n          else\n            {\n              argu[off] = ' ';\n              stringstream ass{ argu };\n              ass >> argu >> data;\n              push (argu, data);\n              continue;\n            }\n        }\n\n      // like \"map n next-page\"\n      else if (crap == \"map\")\n        {\n          is >> argu;\n\n          if (argu.empty ())\n            {\n              qWarning () << \"map command not complete\";\n              continue;\n            }\n\n          getline (is, data);\n\n          while (!data.empty () && isspace (data[0]))\n            data.erase (0, 1);\n\n          if (!argu.empty () && !data.empty ())\n            {\n              ApvlvCmds::buildCommandMap (argu, data);\n            }\n          else\n            {\n              qWarning () << \"Syntax error: map: \" << str;\n            }\n        }\n      else\n        {\n          qWarning () << \"Unknown rc command: \" << crap << \": \" << str;\n        }\n    }\n\n  return true;\n}\n\nbool\nApvlvParams::push (string_view ch, string_view str)\n{\n  mParamMap[string (ch)] = str;\n  return true;\n}\n\nstring\nApvlvParams::getGroupStringOrDefault (std::string_view entry,\n                                      std::string_view key,\n                                      const std::string &defs)\n{\n  auto itr = std::ranges::find_if (\n      mParamMap,\n      [entry, key] (const pair<string, string> &p) -> bool\n        {\n          if (p.first.find (':') == string::npos)\n            return false;\n          else\n            {\n              auto pos = p.first.find (':');\n              auto pentry = p.first.substr (0, pos);\n              auto pkey = p.first.substr (pos + 1);\n              return pentry == entry && pkey == key;\n            }\n        });\n  if (itr != mParamMap.cend ())\n    {\n      return itr->second;\n    }\n  return defs;\n}\n\nstring\nApvlvParams::getStringOrDefault (string_view key, const string &defs)\n{\n  auto itr\n      = std::ranges::find_if (mParamMap,\n                              [key] (const pair<string, string> &p) -> bool\n                                {\n                                  return p.first == key;\n                                });\n  if (itr != mParamMap.cend ())\n    {\n      return itr->second;\n    }\n  return defs;\n}\n\nint\nApvlvParams::getIntOrDefault (string_view key, int defi)\n{\n  auto values = getStringOrDefault (key, \"\");\n  if (values.empty ())\n    return defi;\n\n  return int (strtol (values.c_str (), nullptr, 10));\n}\n\nbool\nApvlvParams::getBoolOrDefault (string_view key, bool defb)\n{\n  auto values = getStringOrDefault (key, \"\");\n  if (values.empty ())\n    return defb;\n\n  if (values == \"true\" || values == \"yes\" || values == \"on\" || values == \"1\")\n    {\n      return true;\n    }\n  else\n    {\n      return false;\n    }\n}\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/ApvlvParams.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvParams.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_PARAMS_H_\n#define _APVLV_PARAMS_H_\n\n#include <map>\n#include <string>\n#include <string_view>\n\nnamespace apvlv\n{\n\nclass ApvlvParams final\n{\npublic:\n  ApvlvParams (const ApvlvParams &) = delete;\n  const ApvlvParams &operator= (const ApvlvParams &) = delete;\n  ApvlvParams (const ApvlvParams &&) = delete;\n  const ApvlvParams &&operator= (const ApvlvParams &&) = delete;\n\n  bool loadFile (const std::string &filename);\n\n  bool push (std::string_view ch, std::string_view str);\n\n  std::string getGroupStringOrDefault (std::string_view entry,\n                                       std::string_view key,\n                                       const std::string &defs = \"\");\n\n  std::string getStringOrDefault (std::string_view key,\n                                  const std::string &defs = \"\");\n\n  int getIntOrDefault (std::string_view key, int defi = 0);\n\n  bool getBoolOrDefault (std::string_view key, bool defb = false);\n\n  static ApvlvParams *\n  instance ()\n  {\n    static ApvlvParams inst;\n    return &inst;\n  }\n\nprivate:\n  ApvlvParams ();\n  ~ApvlvParams ();\n\n  std::map<std::string, std::string> mParamMap;\n};\n\n}\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/ApvlvQueue.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvQueue.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <condition_variable>\n#include <mutex>\n\n#include \"ApvlvQueue.h\"\n\nnamespace apvlv\n{\n\nusing namespace std;\n\nunique_ptr<Token>\nTokenDispatcher::getToken (bool isSpecial)\n{\n  std::unique_lock<std::mutex> lk (mMutex);\n  if ((mEnableSpecial && isSpecial) || mDispatchedCount < mCount)\n    {\n      mDispatchedCount++;\n      lk.unlock ();\n      return make_unique<Token> (this);\n    }\n\n  mCondition.wait (lk,\n                   [this]\n                     {\n                       return mDispatchedCount < mCount;\n                     });\n  mDispatchedCount++;\n  return make_unique<Token> (this);\n}\n\nvoid\nTokenDispatcher::returnToken (Token *token)\n{\n  std::unique_lock<std::mutex> lk (mMutex);\n  mDispatchedCount--;\n  mCondition.notify_all ();\n}\n}\n"
  },
  {
    "path": "src/ApvlvQueue.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvCmds.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_QUEUE_H_\n#define _APVLV_QUEUE_H_\n\n#include <condition_variable>\n#include <mutex>\n#include <queue>\n\nnamespace apvlv\n{\n\ntemplate <class T> class LockQueue\n{\npublic:\n  LockQueue () = default;\n  LockQueue (const LockQueue &) = delete;\n  LockQueue &operator= (const LockQueue &) = delete;\n  ~LockQueue () = default;\n\n  void\n  push (const T &node)\n  {\n    std::lock_guard<std::mutex> lock (mMutex);\n    mQueueInternal.push (node);\n  }\n\n  void\n  push (T &&node)\n  {\n    std::lock_guard<std::mutex> lock (mMutex);\n    mQueueInternal.push (std::move (node));\n  }\n\n  bool\n  pop (T &node)\n  {\n    std::lock_guard<std::mutex> lock (mMutex);\n    if (mQueueInternal.empty ())\n      return false;\n\n    node = std::move (mQueueInternal.front ());\n    mQueueInternal.pop ();\n    return true;\n  }\n\n  void\n  empty ()\n  {\n    std::lock_guard<std::mutex> lock (mMutex);\n    return mQueueInternal.empty ();\n  }\n\n  void\n  clear ()\n  {\n    std::lock_guard<std::mutex> lock (mMutex);\n    mQueueInternal = {};\n  }\n\nprivate:\n  std::queue<T> mQueueInternal;\n  std::mutex mMutex;\n};\n\nclass Token;\nclass TokenDispatcher final\n{\npublic:\n  TokenDispatcher (int count, bool enable)\n      : mCount (count), mEnableSpecial (enable)\n  {\n    mDispatchedCount = 0;\n  }\n  ~TokenDispatcher () = default;\n\n  std::unique_ptr<Token> getToken (bool isSpecial);\n\n  void returnToken (Token *token);\n\n  TokenDispatcher (const TokenDispatcher &) = delete;\n  TokenDispatcher &operator= (const TokenDispatcher &) = delete;\n  TokenDispatcher (TokenDispatcher &&) = delete;\n  TokenDispatcher &operator= (TokenDispatcher &&) = delete;\n\nprivate:\n  int mCount;\n  bool mEnableSpecial;\n  int mDispatchedCount;\n  std::mutex mMutex;\n  std::condition_variable mCondition;\n};\n\nclass Token final\n{\npublic:\n  explicit Token (TokenDispatcher *parent)\n      : mParent (parent), mIsReturned (false)\n  {\n  }\n  ~Token ()\n  {\n    if (!mIsReturned)\n      mParent->returnToken (this);\n  }\n\nprivate:\n  TokenDispatcher *mParent;\n  bool mIsReturned;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "src/ApvlvSearch.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE SearchDialog.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QDebug>\n#include <algorithm>\n#include <filesystem>\n#include <regex>\n#include <stack>\n\n#include \"ApvlvFile.h\"\n#include \"ApvlvParams.h\"\n#include \"ApvlvSearch.h\"\n\nnamespace apvlv\n{\nusing namespace std;\n\nSearcher::Searcher () : mRestart (false), mQuit (false)\n{\n  auto task = thread (&Searcher::dispatch, this);\n  mTasks.emplace_back (std::move (task));\n\n  auto thread_count = thread::hardware_concurrency () - 1;\n  auto thread_value\n      = ApvlvParams::instance ()->getStringOrDefault (\"thread_count\", \"auto\");\n  if (thread_value != \"auto\")\n    {\n      thread_count = ApvlvParams::instance ()->getIntOrDefault (\n          \"thread_count\", thread_count);\n    }\n\n  for (auto ind = 0u; ind < thread_count; ++ind)\n    {\n      task = thread (&Searcher::fileLoopFunc, this);\n      mTasks.emplace_back (std::move (task));\n    }\n}\n\nSearcher::~Searcher ()\n{\n  mRestart.store (true);\n  mQuit.store (true);\n  std::ranges::for_each (mTasks,\n                         [] (thread &task)\n                           {\n                             task.join ();\n                           });\n  qDebug (\"all search threads ended\");\n}\n\nvoid\nSearcher::submit (const SearchOptions &options)\n{\n  mOptions = options;\n  auto path = filesystem::path (options.mFromDir);\n  if (is_regular_file (path))\n    {\n      mFilenameQueue.push (absolute (path).string ());\n    }\n  mRestart.store (true);\n}\n\nunique_ptr<SearchFileMatch>\nSearcher::get ()\n{\n  unique_ptr<SearchFileMatch> ptr;\n  mResults.pop (ptr);\n  return ptr;\n}\n\nvoid\nSearcher::dispatch ()\n{\n  while (mQuit.load () == false)\n    {\n      if (mRestart.load () == true)\n        {\n          this_thread::sleep_for (2s);\n\n          mFilenameQueue.clear ();\n          mResults.clear ();\n\n          mRestart.store (false);\n\n          try\n            {\n              dirFunc ();\n            }\n          catch (const exception &ext)\n            {\n              qWarning () << \"search occurred error: \" << ext.what ();\n            }\n        }\n      else\n        {\n          this_thread::sleep_for (1s);\n        }\n    }\n}\n\nvoid\nSearcher::dirFunc ()\n{\n  qDebug () << \"searching \" << QString::fromLocal8Bit (mOptions.mText)\n            << \" from \" << QString::fromLocal8Bit (mOptions.mFromDir);\n  stack<string> dirs;\n  dirs.push (mOptions.mFromDir);\n  while (!dirs.empty ())\n    {\n      if (mRestart.load () == true || mQuit.load () == true)\n        {\n          return;\n        }\n\n      auto dir = dirs.top ();\n      dirs.pop ();\n      filesystem::directory_iterator itr (dir);\n      for (const auto &entry : itr)\n        {\n          if (mRestart.load () == true || mQuit.load () == true)\n            {\n              return;\n            }\n\n          if (entry.is_directory ())\n            {\n              if (entry.path ().filename () != \".\"\n                  && entry.path ().filename () != \"..\")\n                dirs.push (entry.path ().string ());\n            }\n          else if (entry.is_regular_file ())\n            {\n              auto ext = entry.path ().extension ();\n              if (ext.empty ())\n                continue;\n\n              auto titr\n                  = find (mOptions.mTypes.begin (), mOptions.mTypes.end (),\n                          entry.path ().extension ());\n              if (titr != mOptions.mTypes.end ())\n                {\n                  mFilenameQueue.push (entry.path ().string ());\n                }\n            }\n        }\n    }\n}\n\nvoid\nSearcher::fileLoopFunc ()\n{\n  while (mQuit.load () == false)\n    {\n      string name;\n      if (mFilenameQueue.pop (name))\n        {\n          fileFunc (name);\n        }\n      else\n        {\n          this_thread::sleep_for (1s);\n        }\n    }\n}\n\nvoid\nSearcher::fileFunc (const string &path)\n{\n  auto file = FileFactory::loadFile (path);\n  if (file)\n    {\n      qDebug () << \"searching for \" << QString::fromLocal8Bit (path);\n      auto result = file->grepFile (mOptions.mText, mOptions.mCaseSensitive,\n                                    mOptions.mRegex, mRestart);\n      if (result)\n        mResults.push (std::move (result));\n    }\n}\n\nvector<pair<size_t, size_t>>\ngrep (const string &source, const string &text, bool is_case, bool is_regex)\n{\n  vector<pair<size_t, size_t>> results;\n  if (is_regex == true)\n    {\n      regex regex_1{ text };\n      const sregex_token_iterator end;\n      sregex_token_iterator iter;\n      vector<string> regex_texts;\n      while ((iter = regex_token_iterator (source.begin (), source.end (),\n                                           regex_1))\n             != end)\n        {\n          regex_texts.push_back (iter->str ());\n        }\n      size_t pos = 0;\n      for (auto const &r_text : regex_texts)\n        {\n          pos = source.find (r_text, pos);\n          pair res{ pos, r_text.size () };\n          results.emplace_back (std::move (res));\n        }\n    }\n  else\n    {\n      auto p_source = &source;\n      auto p_text = &text;\n      if (is_case == false)\n        {\n          auto nsource = source;\n          auto ntext = text;\n          std::ranges::transform (nsource, nsource.begin (), ::tolower);\n          std::ranges::transform (ntext, ntext.begin (), ::tolower);\n          p_source = &nsource;\n          p_text = &ntext;\n        }\n\n      auto pos = p_source->find (*p_text);\n      while (pos != string::npos)\n        {\n          pair res{ pos, p_text->size () };\n          results.emplace_back (std::move (res));\n          pos = p_source->find (*p_text, pos + 1);\n        }\n    }\n\n  return results;\n}\n\n}\n"
  },
  {
    "path": "src/ApvlvSearch.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE SearchDialog.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_SEARCH_H_\n#define _APVLV_SEARCH_H_\n\n#include <atomic>\n#include <string>\n#include <thread>\n#include <vector>\n\n#include \"ApvlvQueue.h\"\n\nnamespace apvlv\n{\n\nstruct SearchMatch\n{\n  std::string match;\n  std::string line;\n  size_t pos;\n  size_t length;\n};\n\nusing SearchMatchList = std::vector<SearchMatch>;\n\nstruct SearchPageMatch\n{\n  int page;\n  SearchMatchList matches;\n};\n\nstruct SearchFileMatch\n{\n  std::string filename;\n  std::vector<SearchPageMatch> page_matches;\n};\n\nclass SearchOptions\n{\npublic:\n  friend bool\n  operator== (SearchOptions const &opt, SearchOptions const &other)\n  {\n    return opt.mText == other.mText\n           && opt.mCaseSensitive != other.mCaseSensitive\n           && opt.mRegex != other.mRegex && opt.mTypes != other.mTypes\n           && opt.mFromDir != other.mFromDir;\n  }\n\n  std::string mText;\n  bool mCaseSensitive;\n  bool mRegex;\n  std::string mFromDir;\n  std::vector<std::string> mTypes;\n};\n\nclass Searcher\n{\npublic:\n  Searcher ();\n  ~Searcher ();\n\n  void submit (const SearchOptions &options);\n  std::unique_ptr<SearchFileMatch> get ();\n\nprivate:\n  void dispatch ();\n  void dirFunc ();\n  void fileLoopFunc ();\n  void fileFunc (const std::string &path);\n\n  std::vector<std::thread> mTasks;\n\n  SearchOptions mOptions;\n  LockQueue<std::string> mFilenameQueue;\n  LockQueue<std::unique_ptr<SearchFileMatch>> mResults;\n  std::atomic<bool> mRestart;\n  std::atomic<bool> mQuit;\n};\n\nstd::vector<std::pair<size_t, size_t>> grep (const std::string &source,\n                                             const std::string &text,\n                                             bool is_case, bool is_regex);\n\n}\n\n#endif\n"
  },
  {
    "path": "src/ApvlvSearchDialog.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE SearchDialog.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QFileDialog>\n#include <algorithm>\n\n#include \"ApvlvFile.h\"\n#include \"ApvlvSearchDialog.h\"\n\nnamespace apvlv\n{\nusing namespace std;\n\nSearchDialog::SearchDialog (QWidget *parent)\n    : QDialog (parent), mPreviewIsFinished (true)\n{\n  setLayout (&mVBox);\n\n  mVBox.addLayout (&mHBox2);\n  mLabel.setText (tr (\"Find Directory: \"));\n  mHBox2.addWidget (&mLabel, 0);\n  mHBox2.addWidget (&mFromDir, 1);\n  QObject::connect (&mFromDir, SIGNAL (returnPressed ()), this,\n                    SLOT (search ()));\n  mFromDir.setText (QDir::homePath ());\n  mDirButton.setText (tr (\"...\"));\n  mDirButton.setFocusPolicy (Qt::NoFocus);\n  mHBox2.addWidget (&mDirButton, 0);\n  QObject::connect (&mDirButton, &QPushButton::clicked, this,\n                    [&] ()\n                      {\n                        auto dir = QFileDialog::getExistingDirectory ();\n                        if (!dir.isEmpty ())\n                          mFromDir.setText (dir);\n                      });\n\n  // search line\n  mVBox.addLayout (&mHBox);\n\n  mHBox.addWidget (&mSearchEdit, 1);\n  QObject::connect (&mSearchEdit, SIGNAL (returnPressed ()), this,\n                    SLOT (search ()));\n\n  mCaseSensitive.setText (tr (\"Case sensitive\"));\n  mHBox.addWidget (&mCaseSensitive);\n\n  mRegex.setText (tr (\"Regular expression\"));\n  mHBox.addWidget (&mRegex);\n\n  // file type line\n  mVBox.addLayout (&mHBox3);\n\n  auto exts = FileFactory::supportFileExts ();\n  std::ranges::for_each (exts,\n                         [&] (const auto &ext)\n                           {\n                             auto checkbox = new QCheckBox (\n                                 QString::fromLocal8Bit (ext));\n                             checkbox->setChecked (true);\n                             mHBox3.addWidget (checkbox);\n                             mTypes.emplace_back (checkbox);\n                           });\n\n  mVBox.addWidget (&mSplitter);\n\n  mSplitter.setOrientation (Qt::Vertical);\n\n  mSplitter.addWidget (&mResults);\n  mSplitter.addWidget (&mPreview);\n  mPreview.resize (400, 300);\n  QObject::connect (\n      &mResults,\n      SIGNAL (currentItemChanged (QListWidgetItem *, QListWidgetItem *)),\n      this, SLOT (previewItem (QListWidgetItem *)));\n  QObject::connect (&mResults, SIGNAL (itemActivated (QListWidgetItem *)),\n                    this, SLOT (activateItem (QListWidgetItem *)));\n  QObject::connect (&mPreview, SIGNAL (loadFinished (bool)), this,\n                    SLOT (loadFinish (bool)));\n\n  QObject::connect (&mGetTimer, SIGNAL (timeout ()), this,\n                    SLOT (getResults ()));\n  mGetTimer.start (100);\n}\n\nvoid\nSearchDialog::search ()\n{\n  SearchOptions options;\n  options.mText = mSearchEdit.text ().trimmed ().toStdString ();\n  options.mCaseSensitive = mCaseSensitive.isChecked ();\n  options.mRegex = mRegex.isChecked ();\n  options.mFromDir = mFromDir.text ().toStdString ();\n  for (const auto &type : mTypes)\n    {\n      auto ext = type->text ().replace (\"&\", \"\");\n      if (type->isChecked ())\n        options.mTypes.emplace_back (ext.toStdString ());\n    }\n  if (mOptions == options)\n    return;\n\n  mSearcher.submit (options);\n  mResults.clear ();\n  mOptions = options;\n}\n\nvoid\nSearchDialog::getResults ()\n{\n  unique_ptr<SearchFileMatch> result;\n  while ((result = mSearcher.get ()) != nullptr)\n    {\n      displayResult (std::move (result));\n    }\n}\n\nvoid\nSearchDialog::previewItem (QListWidgetItem *item)\n{\n  if (item == nullptr)\n    return;\n\n  if (mPreviewIsFinished == false)\n    return;\n\n  auto words = item->data (Qt::UserRole).toStringList ();\n  auto path = words[0].toStdString ();\n  auto pn = words[1].toInt ();\n  if (mPreviewFile && mPreviewFile->getFilename () != path)\n    mPreviewFile = nullptr;\n\n  if (mPreviewFile == nullptr)\n    mPreviewFile = FileFactory::loadFile (path);\n\n  if (mPreviewFile)\n    {\n      mPreview.setFile (mPreviewFile.get ());\n      mPreviewIsFinished = false;\n      mPreviewFile->pageRenderToWebView (pn, 1.0, 0, &mPreview);\n    }\n}\n\nvoid\nSearchDialog::activateItem (QListWidgetItem *item)\n{\n  auto words = item->data (Qt::UserRole).toStringList ();\n  auto path = words[0];\n  auto pn = words[1].toInt ();\n  emit loadFile (path.toStdString (), pn);\n  // accept ();\n}\n\nvoid\nSearchDialog::displayResult (unique_ptr<SearchFileMatch> result)\n{\n  auto line = QString::fromLocal8Bit (result->filename);\n  for (const auto &page : result->page_matches)\n    {\n      auto pos = line + ':' + QString::number (page.page + 1);\n      for_each (page.matches.begin (), page.matches.end (),\n                [&] (const auto &match)\n                  {\n                    auto matchitem = new QListWidgetItem (\n                        { QString::fromLocal8Bit (match.line) });\n                    matchitem->setToolTip (pos);\n                    QStringList data{ line, QString::number (page.page) };\n                    matchitem->setData (Qt::UserRole, data);\n                    mResults.addItem (matchitem);\n                  });\n    }\n}\n\nvoid\nSearchDialog::loadFinish ([[maybe_unused]] bool ret)\n{\n  mPreviewIsFinished = true;\n}\n\n}\n"
  },
  {
    "path": "src/ApvlvSearchDialog.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE SearchDialog.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_SEARCH_DIALOG_H_\n#define _APVLV_SEARCH_DIALOG_H_\n\n#include <QCheckBox>\n#include <QDialog>\n#include <QLabel>\n#include <QLineEdit>\n#include <QListWidget>\n#include <QPushButton>\n#include <QSplitter>\n#include <QTimer>\n#include <QVBoxLayout>\n#include <string>\n#include <vector>\n\n#include \"ApvlvWebViewWidget.h\"\n\nnamespace apvlv\n{\n\nclass File;\nclass SearchDialog : public QDialog\n{\n  Q_OBJECT\npublic:\n  explicit SearchDialog (QWidget *parent = nullptr);\n  ~SearchDialog () override = default;\n\nsignals:\n  void loadFile (const std::string &path, int pn);\n\nprivate slots:\n  void search ();\n  void getResults ();\n  void previewItem (QListWidgetItem *item);\n  void activateItem (QListWidgetItem *item);\n  void loadFinish (bool ret);\n\nprivate:\n  void displayResult (std::unique_ptr<SearchFileMatch> result);\n\n  QVBoxLayout mVBox;\n  QHBoxLayout mHBox2;\n  QHBoxLayout mHBox;\n  QHBoxLayout mHBox3;\n  QSplitter mSplitter;\n\n  QLabel mLabel;\n\n  QPushButton mDirButton;\n\n  SearchOptions mOptions;\n\n  Searcher mSearcher;\n\n  QTimer mGetTimer;\n\n  QLineEdit mSearchEdit;\n  QCheckBox mCaseSensitive;\n  QCheckBox mRegex;\n  std::vector<QCheckBox *> mTypes;\n  QLineEdit mFromDir;\n  QListWidget mResults;\n  WebView mPreview;\n\n  std::unique_ptr<File> mPreviewFile;\n  bool mPreviewIsFinished;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "src/ApvlvUtil.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvUtil.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QCoreApplication>\n#include <QDir>\n#include <QProcessEnvironment>\n#include <QRegularExpression>\n#include <filesystem>\n#include <memory>\n#include <optional>\n#include <string>\n\n#include \"ApvlvUtil.h\"\n\nnamespace apvlv\n{\n\nusing namespace std;\n\nstring ScriptDir;\nstring HelpPdf;\nstring IniExam;\nstring IconDir;\nstring IconFile;\nstring IconPage;\nstring Translations;\n\nstring IniFile;\nstring SessionFile;\nstring LogFile;\nstring NotesDir;\nstring UserScriptDir;\n\nstatic void\ngetXdgOrHomeIni (const QString &appdir)\n{\n  auto sysenv = QProcessEnvironment::systemEnvironment ();\n  auto xdgdir = sysenv.value (\"XDG_CONFIG_DIR\").toStdString ();\n  auto homedir = sysenv.value (\"HOME\").toStdString ();\n\n  if (homedir.empty ())\n    {\n      homedir = QDir::homePath ().toStdString ();\n    }\n\n  IniFile = appdir.toStdString () + \"/.apvlvrc\";\n\n  if (!xdgdir.empty ())\n    {\n      IniFile = xdgdir + \"/apvlv/apvlvrc\";\n    }\n  else if (!homedir.empty ())\n    {\n      IniFile = homedir + \"/.config/apvlv/apvlvrc\";\n      if (!filesystem::is_regular_file (IniFile))\n        {\n          IniFile = homedir + \"/.apvlvrc\";\n        }\n    }\n}\n\nstatic void\ngetXdgOrCachePath (const QString &appdir)\n{\n  auto sysenv = QProcessEnvironment::systemEnvironment ();\n  auto xdgdir = sysenv.value (\"XDG_CACHE_HOME\").toStdString ();\n  auto homedir = sysenv.value (\"HOME\").toStdString ();\n  if (homedir.empty ())\n    {\n      homedir = QDir::homePath ().toStdString ();\n    }\n\n  SessionFile = appdir.toStdString () + \"/apvlvinfo\";\n  if (!xdgdir.empty ())\n    {\n      SessionFile = xdgdir + \"/apvlvinfo\";\n    }\n  else if (!homedir.empty ())\n    {\n      SessionFile = homedir + \"/.cache/apvlvinfo\";\n    }\n}\n\nvoid\ngetRuntimePaths ()\n{\n  auto dirpath = QDir (QCoreApplication::applicationDirPath ());\n  dirpath.cdUp ();\n  auto prefix = dirpath.path ().toStdString ();\n\n  ScriptDir = prefix + \"/share/scripts\";\n\n  auto apvlvdir = prefix + \"/share/doc/apvlv\";\n\n  HelpPdf = apvlvdir + \"/Startup.pdf\";\n  IconDir = apvlvdir + \"/icons/dir.png\";\n  IconFile = apvlvdir + \"/icons/pdf.png\";\n  IconPage = apvlvdir + \"/icons/reg.png\";\n  Translations = apvlvdir + \"/translations\";\n\n#ifndef WIN32\n  IniExam = string (SYSCONFDIR) + \"/apvlvrc.example\";\n#else\n  IniExam = apvlvdir + \"/apvlvrc.example\";\n#endif\n\n  getXdgOrHomeIni (dirpath.path ());\n  getXdgOrCachePath (dirpath.path ());\n\n  auto homedir = QDir::homePath ().toStdString ();\n  NotesDir = homedir + \"/\" + \"ApvlvNotes\";\n}\n\noptional<unique_ptr<QXmlStreamReader>>\nxmlContentGetElement (const char *content, size_t length,\n                      const vector<string> &names)\n{\n  auto bytes = QByteArray{ content, (qsizetype)length };\n  auto xml = make_unique<QXmlStreamReader> (bytes);\n  std::ptrdiff_t state = 0;\n  while (!xml->atEnd ())\n    {\n      if (xml->isStartElement ())\n        {\n          auto name = xml->name ().toString ().toStdString ();\n          auto iter = std::ranges::find (names, name);\n          if (iter == names.end ())\n            {\n              xml->readNextStartElement ();\n              continue;\n            }\n\n          auto pos = distance (names.begin (), iter);\n          if (state == pos)\n            {\n              state++;\n            }\n\n          if (state == static_cast<ptrdiff_t> (names.size ()))\n            {\n              return xml;\n            }\n        }\n\n      xml->readNextStartElement ();\n    }\n\n  return nullopt;\n}\n\nstring\nxmlStreamGetAttributeValue (QXmlStreamReader *xml, const string &key)\n{\n  auto attrs = xml->attributes ();\n  for (auto &attr : attrs)\n    {\n      if (attr.name ().toString ().toStdString () == key)\n        return attr.value ().toString ().toStdString ();\n    }\n\n  return \"\";\n}\n\nstring\nxmlContentGetAttributeValue (const char *content, size_t length,\n                             const vector<string> &names, const string &key)\n{\n  auto optxml = xmlContentGetElement (content, length, names);\n  if (!optxml)\n    return \"\";\n\n  auto xml = optxml->get ();\n  return xmlStreamGetAttributeValue (xml, key);\n}\n\nstring\nfilenameExtension (const string &filename)\n{\n  auto pointp = filename.rfind ('.');\n  if (pointp == string::npos)\n    return \"\";\n\n  string ext = filename.substr (pointp);\n  std::ranges::transform (ext, ext.begin (), ::tolower);\n  return ext;\n}\n\nvoid\nimageArgb32ToRgb32 (QImage &image, int left, int top, int right, int bottom)\n{\n  for (auto x = left; x < right; ++x)\n    {\n      for (auto y = top; y < bottom; ++y)\n        {\n          auto c = image.pixelColor (x, y);\n          double ra = double (c.alpha ()) / 255.0;\n          auto nr = static_cast<int> (c.red () * ra + 255 * (1.0 - ra));\n          auto ng = static_cast<int> (c.green () * ra + 255 * (1.0 - ra));\n          auto nb = static_cast<int> (c.blue () * ra + 255 * (1.0 - ra));\n          auto pc = QColor::fromRgb (nr, ng, nb, 255);\n          image.setPixelColor (x, y, pc);\n        }\n    }\n}\n\nstring\ntemplateBuild (string_view temp, string_view token, string_view real)\n{\n  auto pos = temp.find (token);\n  if (pos == string::npos)\n    return string (temp);\n\n  auto first = temp.substr (0, pos);\n  auto second = temp.substr (pos + token.length ());\n  auto outstr = string (first);\n  outstr += string (real);\n  outstr += string (second);\n  return outstr;\n}\n\nqint64\nparseFormattedDataSize (const QString &sizeStr)\n{\n  QString cleanStr = sizeStr.simplified ().toUpper ();\n\n  QRegularExpression re (\"^(\\\\d+(?:\\\\.\\\\d+)?)(\\\\s*)(B?|KB?|MB?|GB?|TB?)$\");\n  QRegularExpressionMatch match = re.match (cleanStr);\n\n  if (match.hasMatch ())\n    {\n      double number = match.captured (1).toDouble ();\n      QString unit = match.captured (3);\n      if (unit.isEmpty ())\n        return static_cast<qint64> (number);\n\n      switch (unit[0].unicode ())\n        {\n        case 'T':\n          return static_cast<qint64> (number * 1024 * 1024 * 1024 * 1024);\n        case 'G':\n          return static_cast<qint64> (number * 1024 * 1024 * 1024);\n        case 'M':\n          return static_cast<qint64> (number * 1024 * 1024);\n        case 'K':\n          return static_cast<qint64> (number * 1024);\n        default:\n          return static_cast<qint64> (number);\n        }\n    }\n\n  return -1;\n}\n\nqint64\nfilesystemTimeToMSeconds (std::filesystem::file_time_type ftt)\n{\n  auto epoch = ftt.time_since_epoch () + chrono::seconds{ 6437664000 };\n  auto milliseconds = chrono::duration_cast<chrono::seconds> (epoch);\n  return static_cast<qint64> (milliseconds.count ());\n}\n\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/ApvlvUtil.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvUtil.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_UTIL_H_\n#define _APVLV_UTIL_H_\n\n#include <QImage>\n#include <QXmlStreamReader>\n#include <filesystem>\n#include <string>\n#include <vector>\n\nnamespace apvlv\n{\n\n// Global files\nextern std::string ScriptDir;\nextern std::string HelpPdf;\nextern std::string IniExam;\nextern std::string IconDir;\nextern std::string IconFile;\nextern std::string IconPage;\nextern std::string Translations;\n\nextern std::string IniFile;\nextern std::string SessionFile;\nextern std::string LogFile;\nextern std::string NotesDir;\nextern std::string UserScriptDir;\n\nvoid getRuntimePaths ();\n\n#ifdef WIN32\nconst char PATH_SEP_C = '\\\\';\nconst char *const PATH_SEP_S = \"\\\\\";\n#else\nconst char PATH_SEP_C = '/';\nconst char *const PATH_SEP_S = \"/\";\n#endif\n\nstd::optional<std::unique_ptr<QXmlStreamReader>>\nxmlContentGetElement (const char *content, size_t length,\n                      const std::vector<std::string> &names);\n\nstd::string xmlStreamGetAttributeValue (QXmlStreamReader *xml,\n                                        const std::string &key);\n\nstd::string\nxmlContentGetAttributeValue (const char *content, size_t length,\n                             const std::vector<std::string> &names,\n                             const std::string &key);\n\nstd::string filenameExtension (const std::string &filename);\n\nvoid imageArgb32ToRgb32 (QImage &image, int left, int top, int right,\n                         int bottom);\n\nstd::string templateBuild (std::string_view temp, std::string_view token,\n                           std::string_view real);\n\nqint64 parseFormattedDataSize (const QString &sizeStr);\n\nqint64 filesystemTimeToMSeconds (std::filesystem::file_time_type ftt);\n\n}\n#endif\n\n// Local Variables:\n// mode: c++\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/ApvlvView.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvView.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QApplication>\n#include <QFileDialog>\n#include <QInputDialog>\n#include <QLineEdit>\n#include <filesystem>\n#include <sstream>\n\n#include \"ApvlvDired.h\"\n#include \"ApvlvInfo.h\"\n#include \"ApvlvParams.h\"\n#include \"ApvlvSearchDialog.h\"\n#include \"ApvlvView.h\"\n\nnamespace apvlv\n{\nusing namespace std;\nusing namespace CommandModeType;\n\nstatic bool\nisAltEscape (QKeyEvent *event)\n{\n  if (event->key () == Qt::Key_BracketLeft &&\n      /* ...so we can ignore mod keys like numlock and capslock. */\n      (event->modifiers () & Qt::ControlModifier))\n    {\n      return true;\n    }\n\n  return false;\n}\n\nvoid\nApvlvCommandBar::keyPressEvent (QKeyEvent *evt)\n{\n  if (evt->key () == Qt::Key_Escape || isAltEscape (evt)\n      || evt->key () == Qt::Key_Tab || evt->key () == Qt::Key_Up\n      || evt->key () == Qt::Key_Down)\n    {\n      emit keyPressed (evt);\n      return;\n    }\n\n  QLineEdit::keyPressEvent (evt);\n}\n\nbool\nApvlvCommandBar::eventFilter (QObject *obj, QEvent *event)\n{\n  if (event->type () == QEvent::KeyPress\n      && dynamic_cast<QKeyEvent *> (event)->key () == Qt::Key_Tab)\n    {\n      qDebug () << \"Ate key press tab\";\n      emit keyPressed (dynamic_cast<QKeyEvent *> (event));\n      return true;\n    }\n  else\n    {\n      return QObject::eventFilter (obj, event);\n    }\n}\n\nApvlvView::ApvlvView (ApvlvView *parent) : mCmds (this), mSearchDialog (this)\n{\n  mParent = parent;\n  if (mParent)\n    {\n      mParent->appendChild (this);\n    }\n\n  auto guiopt\n      = ApvlvParams::instance ()->getStringOrDefault (\"guioptions\", \"mTS\");\n\n  mCmdType = CmdStatusType::CMD_NONE;\n\n  mProCmd = 0;\n  mProCmdCnt = 0;\n\n  mCurrHistroy = -1;\n\n  mHasFull = false;\n\n  keyLastEnd = true;\n\n  processInLast = false;\n\n  int w = ApvlvParams::instance ()->getIntOrDefault (\"width\");\n  int h = ApvlvParams::instance ()->getIntOrDefault (\"height\");\n\n  if (ApvlvParams::instance ()->getBoolOrDefault (\"fullscreen\"))\n    {\n      fullScreen ();\n    }\n  else\n    {\n      resize (w > 1 ? w : 800, h > 1 ? h : 600);\n    }\n  show ();\n\n  setCentralWidget (&mCentral);\n\n  setupMenuBar (guiopt);\n  setMenuBar (&mMenuBar);\n  setupToolBar ();\n  addToolBar (Qt::TopToolBarArea, &mToolBar);\n\n  bool isMenuBarShow = guiopt.find ('m') != string::npos;\n  if (!isMenuBarShow)\n    {\n      mMenuBar.hide ();\n    }\n  bool isToolBarShow = guiopt.find ('T') != string::npos;\n  if (!isToolBarShow)\n    {\n      mToolBar.hide ();\n    }\n\n  mCentral.setLayout (&mVBoxLayout);\n\n  mTabContainer.setTabBarAutoHide (true);\n  mVBoxLayout.addWidget (&mTabContainer, 1);\n\n  QObject::connect (&mTabContainer, SIGNAL (currentChanged (int)), this,\n                    SLOT (tabSwitched (int)));\n\n  mVBoxLayout.addWidget (&mCommandBar, 0);\n  QObject::connect (&mCommandBar, SIGNAL (textEdited (const QString &)), this,\n                    SLOT (commandbarEdited (const QString &)));\n  QObject::connect (&mCommandBar, SIGNAL (returnPressed ()), this,\n                    SLOT (commandbarReturn ()));\n  QObject::connect (&mCommandBar, SIGNAL (keyPressed (QKeyEvent *)), this,\n                    SLOT (commandbarKeyPressed (QKeyEvent *)));\n\n  QObject::connect (&mSearchDialog,\n                    SIGNAL (loadFile (const std::string &, int)), this,\n                    SLOT (loadFileOnPage (const std::string &, int)));\n\n  cmdHide ();\n}\n\nApvlvView::~ApvlvView ()\n{\n  if (mParent)\n    {\n      mParent->eraseChild (this);\n    }\n\n  auto itr = mChildren.begin ();\n  while (itr != mChildren.end ())\n    {\n      delete *itr;\n      itr = mChildren.begin ();\n    }\n\n  mCmdHistroy.clear ();\n}\n\nApvlvWindow *\nApvlvView::currentWindow ()\n{\n  auto index = mTabContainer.currentIndex ();\n  if (index < 0)\n    return nullptr;\n\n  auto root_win = dynamic_cast<ApvlvWindow *> (mTabContainer.widget (index));\n  Q_ASSERT (root_win);\n\n  auto widget = QApplication::focusWidget ();\n  if (widget)\n    {\n      auto win = root_win->findWindowByWidget (widget);\n      if (win)\n        {\n          root_win->setActiveWindow (win);\n          return win;\n        }\n    }\n\n  auto win = root_win->getActiveWindow ();\n  if (win)\n    return win;\n\n  return root_win->firstFrameWindow ();\n}\n\nvoid\nApvlvView::delCurrentWindow ()\n{\n  if (auto win = currentWindow (); win)\n    win->perish ();\n  updateTabName ();\n}\n\nvoid\nApvlvView::open ()\n{\n  QString dirname;\n  auto fp = ApvlvInfo::instance ()->lastFile ();\n  if (fp)\n    {\n      dirname = QString::fromLocal8Bit (\n          filesystem::path (fp.value ()->file).parent_path ().string ());\n    }\n  else\n    {\n      dirname = QDir::homePath ();\n    }\n\n  // qDebug (\"lastfile: [%s], dirname: [%s]\", fp ? fp->file.c_str () : \"\",\n  // dirname);\n  auto const mimes = FileFactory::supportMimeTypes ();\n  QString filters;\n  for (const auto &m : mimes)\n    {\n      filters += m.first.c_str ();\n      filters += \"(\";\n      filters += ('*' + m.second[0]);\n      for (decltype (m.second.size ()) index = 1; index < m.second.size ();\n           ++index)\n        filters += (\" *\" + m.second[index]);\n      filters += \");;\";\n    }\n\n  QString selected;\n  auto filename = QFileDialog::getOpenFileName (this, \"open file\", dirname,\n                                                filters, &selected);\n  if (!filename.isEmpty ())\n    {\n      loadFile (string (filename.toLocal8Bit ().constData ()));\n    }\n}\n\nvoid\nApvlvView::openDir ()\n{\n  QString dirname;\n  auto fp = ApvlvInfo::instance ()->lastFile ();\n  if (fp)\n    {\n      dirname = QString::fromLocal8Bit (\n          filesystem::path (fp.value ()->file).parent_path ().string ());\n    }\n  else\n    {\n      dirname = QDir::homePath ();\n    }\n\n  auto filename = QFileDialog::getExistingDirectory (\n      this, \"open dir\", dirname, QFileDialog::ShowDirsOnly);\n  if (!filename.isEmpty ())\n    {\n      loadDir (filename.toStdString ());\n    }\n}\n\nvoid\nApvlvView::openRrl ()\n{\n  auto res = QInputDialog::getText (this, \"url\", tr (\"input url: \"));\n  if (!res.isEmpty ())\n    {\n      currentFrame ()->loadUri (res.toStdString ());\n    }\n}\n\nbool\nApvlvView::loadDir (const std::string &path)\n{\n  currentFrame ()->setDirIndex (path);\n  return true;\n}\n\nvoid\nApvlvView::quit (bool only_tab)\n{\n  if (mTabContainer.count () <= 1 || only_tab == false)\n    {\n      closeEvent (nullptr);\n      return;\n    }\n\n  auto index = mTabContainer.currentIndex ();\n  Q_ASSERT (index != -1);\n  mTabContainer.removeTab (index);\n}\n\nvoid\nApvlvView::search ()\n{\n  promptCommand ('/');\n}\n\nvoid\nApvlvView::backSearch ()\n{\n  promptCommand ('?');\n}\n\nvoid\nApvlvView::advancedSearch ()\n{\n  mSearchDialog.show ();\n}\n\nvoid\nApvlvView::dired ()\n{\n  auto diag = DiredDialog (this);\n  QObject::connect (&diag, SIGNAL (loadFile (const string &, int)), this,\n                    SLOT (loadFileOnPage (const string &, int)));\n  diag.exec ();\n}\n\nbool\nApvlvView::newTab (const std::string &filename)\n{\n  auto docname = filename;\n  if (filesystem::is_directory (filename))\n    {\n      docname = HelpPdf;\n    }\n\n  auto optndoc = hasLoaded (docname);\n  if (!optndoc)\n    {\n      auto ndoc = new ApvlvFrame (this);\n      if (!ndoc->loadFile (docname, true, true))\n        {\n          delete ndoc;\n          ndoc = nullptr;\n        }\n\n      if (ndoc)\n        {\n          regLoaded (ndoc);\n          optndoc = make_optional<ApvlvFrame *> (ndoc);\n        }\n    }\n\n  if (optndoc)\n    {\n      newTab (optndoc.value ());\n      if (filesystem::is_directory (filename))\n        {\n          loadDir (filename);\n        }\n      return true;\n    }\n  else\n    {\n      return false;\n    }\n}\n\nbool\nApvlvView::newTab (ApvlvFrame *core)\n{\n  auto win = new ApvlvWindow ();\n  win->setFrame (core);\n\n  auto basename\n      = core->filename ()\n            ? filesystem::path (core->filename ()).filename ().string ()\n            : \"NONE\";\n  auto pos = mTabContainer.currentIndex () + 1;\n  mTabContainer.insertTab (pos, win, QString::fromLocal8Bit (basename));\n  mTabContainer.setCurrentIndex (pos);\n\n  return true;\n}\n\nbool\nApvlvView::loadFile (const std::string &filename)\n{\n  auto abpath = filesystem::absolute (filename).string ();\n\n  ApvlvWindow *win = currentWindow ();\n  ApvlvFrame *ndoc = nullptr;\n\n  auto optndoc = hasLoaded (abpath);\n  if (!optndoc)\n    {\n      ndoc = new ApvlvFrame (this);\n      if (!ndoc->loadFile (filename, true, true))\n        {\n          delete ndoc;\n          ndoc = nullptr;\n        }\n      else\n        {\n          regLoaded (ndoc);\n          optndoc = make_optional<ApvlvFrame *> (ndoc);\n        }\n    }\n\n  if (optndoc)\n    {\n      win->setFrame (optndoc.value ());\n      updateTabName ();\n    }\n\n  return ndoc != nullptr;\n}\n\nvoid\nApvlvView::loadFileOnPage (const string &filename, int pn)\n{\n  auto cdoc = currentFrame ();\n  if (cdoc)\n    {\n      if (loadFile (filename))\n        {\n          cdoc->showPage (pn, 0.0);\n        }\n    }\n}\n\noptional<ApvlvFrame *>\nApvlvView::hasLoaded (string_view abpath)\n{\n  for (auto &core : mDocs)\n    {\n      if (!core->inuse () && abpath == core->filename ())\n        {\n          return make_optional<ApvlvFrame *> (core.get ());\n        }\n    }\n\n  return nullopt;\n}\n\nvoid\nApvlvView::regLoaded (ApvlvFrame *doc)\n{\n  auto cache_count\n      = ApvlvParams::instance ()->getIntOrDefault (\"cache_count\", 10);\n  if (mDocs.size () >= static_cast<size_t> (cache_count))\n    {\n      auto found_itr = mDocs.end ();\n      for (auto itr = mDocs.begin (); itr != mDocs.end (); ++itr)\n        {\n          if ((*itr)->inuse () == false)\n            {\n              found_itr = itr;\n              break;\n            }\n        }\n\n      if (found_itr != mDocs.end ())\n        {\n          mDocs.erase (found_itr);\n        }\n    }\n\n  mDocs.push_back (unique_ptr<ApvlvFrame> (doc));\n}\n\nvoid\nApvlvView::setupMenuBar (const string &guiopt)\n{\n  // File -> open, openDir\n  auto mfile = new QMenu (tr (\"File\"));\n  mMenuBar.addMenu (mfile);\n\n  auto action = mfile->addAction (tr (\"Open\"));\n  QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (open ()));\n  action = mfile->addAction (tr (\"OpenDir\"));\n  QObject::connect (action, SIGNAL (triggered (bool)), this,\n                    SLOT (openDir ()));\n  action = mfile->addAction (tr (\"OpenUrl\"));\n  QObject::connect (action, SIGNAL (triggered (bool)), this,\n                    SLOT (openRrl ()));\n  action = mfile->addAction (tr (\"New Tab\"));\n  QObject::connect (action, SIGNAL (triggered (bool)), this,\n                    SLOT (newTab ()));\n  action = mfile->addAction (tr (\"Close Tab\"));\n  QObject::connect (action, SIGNAL (triggered (bool)), this,\n                    SLOT (closeTab ()));\n  action = mfile->addAction (tr (\"Quit\"));\n  QObject::connect (action, SIGNAL (triggered (bool)), this,\n                    SLOT (quit (bool)));\n\n  // Edit ->\n  auto medit = new QMenu (tr (\"Edit\"));\n  mMenuBar.addMenu (medit);\n  action = medit->addAction (tr (\"Search\"));\n  QObject::connect (action, SIGNAL (triggered (bool)), this,\n                    SLOT (search ()));\n  action = medit->addAction (tr (\"Back Search\"));\n  QObject::connect (action, SIGNAL (triggered (bool)), this,\n                    SLOT (backSearch ()));\n\n  // View ->\n  auto mview = new QMenu (tr (\"View\"));\n  mMenuBar.addMenu (mview);\n\n  action = mview->addAction (tr (\"ToolBar\"));\n  action->setCheckable (true);\n  QObject::connect (action, SIGNAL (triggered (bool)), this,\n                    SLOT (toggleToolBar ()));\n  bool isToolBarShow = guiopt.find ('T') != string::npos;\n  action->setChecked (isToolBarShow);\n\n  action = mview->addAction (tr (\"StatusBar\"));\n  action->setCheckable (true);\n  QObject::connect (action, SIGNAL (triggered (bool)), this,\n                    SLOT (toggleStatus ()));\n  bool isStatusShow = guiopt.find ('S') != string::npos;\n  action->setChecked (isStatusShow);\n\n  action = mview->addAction (tr (\"Horizontal Split\"));\n  QObject::connect (action, SIGNAL (triggered (bool)), this,\n                    SLOT (horizontalSplit ()));\n  action = mview->addAction (tr (\"Vertical Split\"));\n  QObject::connect (action, SIGNAL (triggered (bool)), this,\n                    SLOT (verticalSplit ()));\n  action = mview->addAction (tr (\"Close Split\"));\n  QObject::connect (action, SIGNAL (triggered (bool)), this,\n                    SLOT (unBirth ()));\n\n  // Navigate ->\n  auto mnavigate = new QMenu (tr (\"Navigate\"));\n  mMenuBar.addMenu (mnavigate);\n  action = mnavigate->addAction (tr (\"Previous Page\"));\n  QObject::connect (action, SIGNAL (triggered (bool)), this,\n                    SLOT (previousPage ()));\n  action = mnavigate->addAction (tr (\"Next Page\"));\n  QObject::connect (action, SIGNAL (triggered (bool)), this,\n                    SLOT (nextPage ()));\n\n  // Tools\n  auto mtools = new QMenu (tr (\"Tools\"));\n  mMenuBar.addMenu (mtools);\n  action = mtools->addAction (tr (\"Advanced Search\"));\n  QObject::connect (action, SIGNAL (triggered (bool)), this,\n                    SLOT (advancedSearch ()));\n  action = mtools->addAction (tr (\"Dired\"));\n  QObject::connect (action, SIGNAL (trigged (bool)), this, SLOT (dired ()));\n\n  // Help -> about\n  auto mhelp = new QMenu (tr (\"Help\"));\n  mMenuBar.addMenu (mhelp);\n}\n\nvoid\nApvlvView::setupToolBar ()\n{\n  auto action = mToolBar.addAction (tr (\"Open\"));\n  action->setIcon (\n      QApplication::style ()->standardIcon (QStyle::SP_DialogOpenButton));\n  QObject::connect (action, SIGNAL (triggered (bool)), this, SLOT (open ()));\n\n  action = mToolBar.addAction (tr (\"OpenDir\"));\n  action->setIcon (\n      QApplication::style ()->standardIcon (QStyle::SP_DirOpenIcon));\n  QObject::connect (action, SIGNAL (triggered (bool)), this,\n                    SLOT (openDir ()));\n\n  action = mToolBar.addAction (tr (\"Toggle Directory\"));\n  QObject::connect (action, SIGNAL (triggered (bool)), this,\n                    SLOT (toggleDirectory ()));\n\n  action = mToolBar.addAction (tr (\"Quit\"));\n  QObject::connect (action, SIGNAL (triggered (bool)), this,\n                    SLOT (quit (bool)));\n}\n\nvoid\nApvlvView::promptCommand (char ch)\n{\n  QString s{ ch };\n  mCommandBar.setText (s);\n  cmdShow (CmdStatusType::CMD_CMD);\n}\n\nvoid\nApvlvView::promptCommand (const char *str)\n{\n  mCommandBar.setText (str);\n  cmdShow (CmdStatusType::CMD_CMD);\n}\n\nchar *\nApvlvView::input (const char *str, int width, int height,\n                  const string &content)\n{\n  // need impl\n  return nullptr;\n}\n\nvoid\nApvlvView::cmdShow (CmdStatusType cmdtype)\n{\n  mCmdType = cmdtype;\n\n  mCommandBar.show ();\n  mCommandBar.setCursorPosition (1);\n  mCommandBar.setFocus ();\n}\n\nvoid\nApvlvView::cmdHide ()\n{\n  mCmdType = CmdStatusType::CMD_NONE;\n  mCmdTime = chrono::steady_clock::now ();\n\n  mCommandBar.hide ();\n  mTabContainer.setFocus ();\n}\n\nvoid\nApvlvView::cmdAuto (const char *ps)\n{\n  stringstream ss (ps);\n  string cmd;\n  string np;\n  string argu;\n\n  ss >> cmd >> np >> argu;\n\n  if (np.empty ())\n    {\n      return;\n    }\n\n  if (np[np.length () - 1] == '\\\\')\n    {\n      np.replace (np.length () - 1, 1, 1, ' ');\n      np += argu;\n      ss >> argu;\n    }\n\n  if (cmd.empty () || np.empty ())\n    {\n      return;\n    }\n\n  auto comp = ApvlvCompletion{};\n  if (cmd == \"o\" || cmd == \"open\" || cmd == \"TOtext\")\n    {\n      comp.addPath (np);\n    }\n  else if (cmd == \"doc\")\n    {\n      vector<string> items;\n      for (auto &doc : mDocs)\n        {\n          items.emplace_back (doc->filename ());\n        }\n      comp.addItems (items);\n    }\n\n  qDebug () << \"find match: \" << np;\n  auto comtext = comp.complete (np);\n  if (!comtext.empty ())\n    {\n      qDebug () << \"get a match: \" << comtext;\n      QString s = QString::fromLocal8Bit (comtext);\n      s.replace (\" \", \"\\\\ \");\n      QString linetext = QString::asprintf (\":%s %s\", cmd.c_str (),\n                                            s.toStdString ().c_str ());\n      mCommandBar.setText (linetext);\n    }\n  else\n    {\n      qDebug () << \"no get match\";\n    }\n}\n\nvoid\nApvlvView::fullScreen ()\n{\n  if (mHasFull == false)\n    {\n      ApvlvFrame *core = currentFrame ();\n      if (core)\n        core->toggleDirectory (false);\n\n      showFullScreen ();\n      mHasFull = true;\n    }\n  else\n    {\n      showNormal ();\n      mHasFull = false;\n    }\n}\n\nvoid\nApvlvView::nextPage ()\n{\n  currentFrame ()->nextPage (1);\n}\n\nvoid\nApvlvView::previousPage ()\n{\n  currentFrame ()->previousPage (1);\n}\n\nvoid\nApvlvView::toggleDirectory ()\n{\n  currentFrame ()->toggleDirectory ();\n}\n\nvoid\nApvlvView::toggleToolBar ()\n{\n  if (mToolBar.isHidden ())\n    {\n      mToolBar.show ();\n    }\n  else\n    {\n      mToolBar.hide ();\n    }\n}\n\nvoid\nApvlvView::toggleStatus ()\n{\n  for (auto &doc : mDocs)\n    {\n      if (doc->isStatusHidden ())\n        {\n          doc->statusShow ();\n        }\n      else\n        {\n          doc->statusHide ();\n        }\n    }\n}\n\nvoid\nApvlvView::newTab ()\n{\n  auto doc = currentFrame ()->clone ();\n  newTab (doc);\n}\n\nvoid\nApvlvView::closeTab ()\n{\n  quit (true);\n}\n\nvoid\nApvlvView::horizontalSplit ()\n{\n  currentWindow ()->birth (ApvlvWindow::WindowType::SP, nullptr);\n}\n\nvoid\nApvlvView::verticalSplit ()\n{\n  currentWindow ()->birth (ApvlvWindow::WindowType::VSP, nullptr);\n}\n\nvoid\nApvlvView::unBirth ()\n{\n  currentWindow ()->perish ();\n}\n\nApvlvFrame *\nApvlvView::currentFrame ()\n{\n  if (auto widget = QApplication::focusWidget (); widget)\n    {\n      if (auto doc = ApvlvFrame::findByWidget (widget); doc)\n        return doc;\n    }\n\n  ApvlvWindow *curwin = currentWindow ();\n  Q_ASSERT (curwin);\n  return curwin->getFrame ();\n}\n\nCmdReturn\nApvlvView::subProcess (int times, uint keyval)\n{\n  uint procmd = mProCmd;\n  mProCmd = 0;\n  mProCmdCnt = 0;\n  switch (procmd)\n    {\n    case 'Z':\n      if (keyval == 'Z')\n        quit (true);\n\n    case ctrlValue ('w'):\n      if (keyval == 'q' || keyval == ctrlValue ('Q'))\n        {\n          if (currentWindow ()->isRoot ())\n            {\n              quit (true);\n            }\n          else\n            {\n              delCurrentWindow ();\n            }\n        }\n      else\n        {\n          CmdReturn rv = currentWindow ()->process (times, keyval);\n          updateTabName ();\n          return rv;\n        }\n      break;\n\n    case 'g':\n      if (keyval == 't')\n        {\n          if (times == 0)\n            switchTab (mTabContainer.currentIndex () + 1);\n          else\n            switchTab (times - 1);\n        }\n      else if (keyval == 'T')\n        {\n          if (times == 0)\n            switchTab (mTabContainer.currentIndex () - 1);\n          else\n            switchTab (times - 1);\n        }\n      else if (keyval == 'g')\n        {\n          if (times == 0)\n            times = 1;\n          currentFrame ()->showPage (times - 1, 0.0);\n        }\n      break;\n\n    default:\n      return CmdReturn::NO_MATCH;\n    }\n\n  return CmdReturn::MATCH;\n}\n\nCmdReturn\nApvlvView::process (int has, int ct, uint key)\n{\n  if (mProCmd != 0)\n    {\n      auto ret = subProcess (mProCmdCnt, key);\n      if (ret == CmdReturn::MATCH)\n        {\n          saveKey (has, ct, key, true);\n        }\n      return ret;\n    }\n\n  switch (key)\n    {\n    case 'Z':\n      mProCmd = 'Z';\n      return CmdReturn::NEED_MORE;\n\n    case ctrlValue ('w'):\n      mProCmd = ctrlValue ('w');\n      mProCmdCnt = has ? ct : 1;\n      saveKey (has, ct, key, false);\n      return CmdReturn::NEED_MORE;\n    case 'q':\n      quit (true);\n      break;\n    case 'f':\n      fullScreen ();\n      break;\n    case '.':\n      processLastKey ();\n      break;\n    case 'g':\n      mProCmd = 'g';\n      mProCmdCnt = has ? ct : 0;\n      saveKey (has, ct, key, false);\n      return CmdReturn::NEED_MORE;\n    default:\n      CmdReturn ret = currentFrame ()->process (has, ct, key);\n      if (ret == CmdReturn::NEED_MORE)\n        {\n          saveKey (has, ct, key, false);\n        }\n      else if (ret == CmdReturn::MATCH)\n        {\n          saveKey (has, ct, key, true);\n        }\n    }\n\n  return CmdReturn::MATCH;\n}\n\nbool\nApvlvView::run (const char *str)\n{\n  bool ret;\n\n  switch (*str)\n    {\n    case SEARCH:\n      currentFrame ()->markposition ('\\'');\n      ret = currentFrame ()->search (str + 1, false);\n      break;\n\n    case BACKSEARCH:\n      currentFrame ()->markposition ('\\'');\n      ret = currentFrame ()->search (str + 1, true);\n      break;\n\n    case COMMANDMODE:\n      ret = runCommand (str + 1);\n      break;\n\n    case FIND:\n      ret = currentFrame ()->search (str + 1, false);\n      break;\n\n    default:\n      ret = false;\n      break;\n    }\n\n  return ret;\n}\n\nvoid\nApvlvView::setTitle (const std::string &title)\n{\n  setWindowTitle (QString::fromLocal8Bit (title));\n}\n\nbool\nApvlvView::runCommand (const char *str)\n{\n  bool ret = true;\n\n  if (*str == '!')\n    {\n      system (str + 1);\n    }\n  else\n    {\n      stringstream ss (str);\n      string cmd;\n      string subcmd;\n      string argu;\n      ss >> cmd >> subcmd >> argu;\n\n      if (!subcmd.empty () && subcmd[subcmd.length () - 1] == '\\\\')\n        {\n          subcmd.replace (subcmd.length () - 1, 1, 1, ' ');\n          subcmd += argu;\n          ss >> argu;\n        }\n\n      if (cmd == \"set\")\n        {\n          if (subcmd == \"skip\")\n            {\n              currentFrame ()->setSkip (\n                  int (strtol (argu.c_str (), nullptr, 10)));\n            }\n          else\n            {\n              ApvlvParams::instance ()->push (subcmd, argu);\n            }\n        }\n      else if (cmd == \"map\" && !subcmd.empty ())\n        {\n          ApvlvCmds::buildCommandMap (subcmd, argu);\n        }\n      else if ((cmd == \"o\" || cmd == \"open\" || cmd == \"doc\")\n               && !subcmd.empty ())\n        {\n          char *home;\n\n          if (subcmd[0] == '~')\n            {\n              home = getenv (\"HOME\");\n\n              if (home)\n                {\n                  subcmd.replace (0, 1, home);\n                }\n            }\n\n          if (filesystem::is_directory (subcmd))\n            {\n              ret = loadDir (subcmd);\n            }\n          else if (filesystem::is_regular_file (subcmd))\n            {\n              ret = loadFile (subcmd);\n            }\n          else\n            {\n              errorMessage (string (\"no file: \"), subcmd);\n              ret = false;\n            }\n        }\n      else if (cmd == \"TOtext\" && !subcmd.empty ())\n        {\n          currentFrame ()->totext (subcmd.c_str ());\n        }\n      else if ((cmd == \"pr\" || cmd == \"print\"))\n        {\n          currentFrame ()->print (\n              subcmd.empty () ? -1\n                              : int (strtol (subcmd.c_str (), nullptr, 10)));\n        }\n      else if (cmd == \"sp\")\n        {\n          if (currentWindow ()->birth (ApvlvWindow::WindowType::SP, nullptr))\n            {\n              windowAdded ();\n            }\n        }\n      else if (cmd == \"vsp\")\n        {\n          if (currentWindow ()->birth (ApvlvWindow::WindowType::VSP, nullptr))\n            {\n              windowAdded ();\n            }\n        }\n      else if ((cmd == \"zoom\" || cmd == \"z\") && !subcmd.empty ())\n        {\n          currentFrame ()->setZoomString (subcmd.c_str ());\n        }\n      else if (cmd == \"forwardpage\" || cmd == \"fp\")\n        {\n          if (subcmd.empty ())\n            currentFrame ()->nextPage (1);\n          else\n            currentFrame ()->nextPage (\n                int (strtol (subcmd.c_str (), nullptr, 10)));\n        }\n      else if (cmd == \"prewardpage\" || cmd == \"bp\")\n        {\n          if (subcmd.empty ())\n            currentFrame ()->previousPage (1);\n          else\n            currentFrame ()->previousPage (\n                int (strtol (subcmd.c_str (), nullptr, 10)));\n        }\n      else if (cmd == \"directory\")\n        {\n          currentFrame ()->toggleDirectory ();\n        }\n      else if (cmd == \"goto\" || cmd == \"g\")\n        {\n          currentFrame ()->markposition ('\\'');\n          auto p = strtol (subcmd.c_str (), nullptr, 10);\n          p += currentFrame ()->getSkip ();\n          currentFrame ()->showPage (int (p - 1), 0.0);\n        }\n      else if (cmd == \"help\" || cmd == \"h\")\n        {\n          loadFile (HelpPdf);\n        }\n      else if (cmd == \"q\" || cmd == \"quit\")\n        {\n          if (currentWindow ()->isRoot ())\n            {\n              quit (true);\n            }\n          else\n            {\n              delCurrentWindow ();\n            }\n        }\n      else if (cmd == \"qall\")\n        {\n          quit (false);\n        }\n      else if (cmd == \"tabnew\")\n        {\n          newTab (HelpPdf);\n        }\n      else if (cmd == \"tabn\" || cmd == \"tabnext\")\n        {\n          switchTab (mTabContainer.currentIndex () + 1);\n        }\n      else if (cmd == \"tabp\" || cmd == \"tabprevious\")\n        {\n          switchTab (mTabContainer.currentIndex () - 1);\n        }\n      else if (cmd == \"toc\")\n        {\n          loadFile (currentFrame ()->filename ());\n        }\n      else\n        {\n          bool isn = true;\n          for (char i : cmd)\n            {\n              if (i < '0' || i > '9')\n                {\n                  isn = false;\n                  break;\n                }\n            }\n          if (isn && currentFrame ())\n            {\n              auto p = strtol (cmd.c_str (), nullptr, 10);\n              p += currentFrame ()->getSkip ();\n              if (p != currentFrame ()->pageNumber ())\n                {\n                  currentFrame ()->showPage (int (p - 1), 0.0);\n                }\n            }\n          else\n            {\n              errorMessage (string (\"no command: \"), cmd);\n              ret = false;\n            }\n        }\n    }\n\n  return ret;\n}\n\nvoid\nApvlvView::keyPressEvent (QKeyEvent *evt)\n{\n  if (mCmdType != CmdStatusType::CMD_NONE)\n    {\n      return;\n    }\n\n  auto now = chrono::steady_clock::now ();\n  auto millis\n      = chrono::duration_cast<chrono::milliseconds> (now - mCmdTime).count ();\n  if (millis < 1000L)\n    return;\n\n  mCmds.append (evt);\n}\n\nvoid\nApvlvView::commandbarEdited ([[maybe_unused]] const QString &str)\n{\n  // need impl\n}\n\nvoid\nApvlvView::commandbarReturn ()\n{\n  if (mCmdType == CmdStatusType::CMD_CMD)\n    {\n      auto str = mCommandBar.text ();\n      if (!str.isEmpty ())\n        {\n          if (run (str.toStdString ().c_str ()))\n            {\n              mCmdHistroy.emplace_back (str.toStdString ());\n              mCurrHistroy = mCmdHistroy.size () - 1;\n              cmdHide ();\n            }\n        }\n      else\n        {\n          cmdHide ();\n        }\n    }\n  else if (mCmdType == CmdStatusType::CMD_MESSAGE)\n    {\n      cmdHide ();\n    }\n}\n\nvoid\nApvlvView::commandbarKeyPressed (QKeyEvent *gek)\n{\n  if (gek->key () == Qt::Key_Tab)\n    {\n      auto str = mCommandBar.text ();\n      if (!str.isEmpty ())\n        {\n          cmdAuto (str.toStdString ().c_str () + 1);\n        }\n    }\n  else if (gek->key () == Qt::Key_Backspace)\n    {\n      auto str = mCommandBar.text ();\n      if (str.length () == 1)\n        {\n          cmdHide ();\n          mCurrHistroy = mCmdHistroy.size () - 1;\n        }\n    }\n  else if (gek->key () == Qt::Key_Escape || isAltEscape (gek))\n    {\n      cmdHide ();\n      mCurrHistroy = mCmdHistroy.size () - 1;\n    }\n  else if (gek->key () == Qt::Key_Up)\n    {\n      if (!mCmdHistroy.empty ())\n        {\n          mCommandBar.setText (QString::fromLocal8Bit (\n              mCurrHistroy > 0 ? mCmdHistroy[mCurrHistroy--]\n                               : mCmdHistroy[0]));\n        }\n    }\n  else if (gek->key () == Qt::Key_Down)\n    {\n      if (!mCmdHistroy.empty ())\n        {\n          mCommandBar.setText (QString::fromLocal8Bit (\n              (size_t)mCurrHistroy < mCmdHistroy.size () - 1\n                  ? mCmdHistroy[++mCurrHistroy]\n                  : mCmdHistroy[mCmdHistroy.size () - 1]));\n        }\n    }\n}\n\nvoid\nApvlvView::closeEvent (QCloseEvent *evt)\n{\n  if (mParent == nullptr)\n    {\n      QGuiApplication::exit ();\n    }\n}\n\nvoid\nApvlvView::tabSwitched (int pnum)\n{\n  qDebug () << \"tabwidget switch to: \" << pnum;\n  if (pnum == -1)\n    return;\n\n  auto adoc = currentFrame ();\n  if (adoc && adoc->filename ())\n    {\n      auto filename\n          = filesystem::path (currentFrame ()->filename ()).filename ();\n      setTitle (filename.string ());\n    }\n}\n\nvoid\nApvlvView::switchTab (int tabPos)\n{\n  int ntabs = mTabContainer.count ();\n  while (tabPos < 0)\n    tabPos += ntabs;\n\n  tabPos = tabPos % ntabs;\n  mTabContainer.setCurrentIndex (tabPos);\n}\n\nvoid\nApvlvView::windowAdded ()\n{\n  auto index = mTabContainer.currentIndex ();\n  Q_ASSERT (index != -1);\n  updateTabName ();\n}\n\nvoid\nApvlvView::updateTabName ()\n{\n  auto win = currentWindow ();\n  if (win == nullptr)\n    return;\n\n  auto frame = win->getFrame ();\n  if (frame == nullptr)\n    return;\n\n  const char *filename = frame->filename ();\n  string gfilename;\n\n  if (filename == nullptr)\n    gfilename = \"None\";\n  else\n    gfilename = filesystem::path (filename).filename ().string ();\n\n  setTitle (gfilename);\n\n  auto index = mTabContainer.currentIndex ();\n  Q_ASSERT (index != -1);\n  auto tagname = QString::fromLocal8Bit (gfilename);\n  // need impl tagname = QString(\"[%1] %2\").arg (mTabList[index]).arg\n  // (gfilename.c_str());\n  mTabContainer.setTabText (index, tagname);\n}\n\nvoid\nApvlvView::appendChild (ApvlvView *view)\n{\n  mChildren.push_back (view);\n}\n\nvoid\nApvlvView::eraseChild (ApvlvView *view)\n{\n  auto itr = mChildren.begin ();\n  while (*itr != view && itr != mChildren.end ())\n    itr++;\n\n  if (*itr == view)\n    {\n      mChildren.erase (itr);\n    }\n}\n\nvoid\nApvlvView::saveKey (int has, int ct, uint key, bool end)\n{\n  if (processInLast)\n    return;\n\n  if (keyLastEnd)\n    {\n      keySquence.clear ();\n    }\n\n  struct keyNode key_node = { has, ct, key, end };\n  keySquence.push_back (key_node);\n  keyLastEnd = end;\n}\n\nvoid\nApvlvView::processLastKey ()\n{\n  processInLast = true;\n  for (auto node : keySquence)\n    {\n      process (node.Has, node.Ct, node.Key);\n    }\n  processInLast = false;\n}\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/ApvlvView.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvView.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_VIEW_H_\n#define _APVLV_VIEW_H_\n\n#include <QLineEdit>\n#include <QMainWindow>\n#include <QMenuBar>\n#include <QTabWidget>\n#include <QVBoxLayout>\n#include <iosfwd>\n#include <iostream>\n#include <sstream>\n#include <string_view>\n\n#include \"ApvlvCmds.h\"\n#include \"ApvlvCompletion.h\"\n#include \"ApvlvFrame.h\"\n#include \"ApvlvSearchDialog.h\"\n#include \"ApvlvWindow.h\"\n\nnamespace apvlv\n{\n\nnamespace CommandModeType\n{\nconst char SEARCH = '/';\nconst char BACKSEARCH = '?';\nconst char COMMANDMODE = ':';\nconst char FIND = 'F';\n}\n\nclass ApvlvFrame;\nclass ApvlvWindow;\n\nclass ApvlvCommandBar : public QLineEdit\n{\n  Q_OBJECT\npublic:\n  ApvlvCommandBar ()\n  {\n    installEventFilter (this);\n  };\n\nprotected:\n  void keyPressEvent (QKeyEvent *evt) override;\n  bool eventFilter (QObject *obj, QEvent *event) override;\n\nsignals:\n  void keyPressed (QKeyEvent *evt);\n};\n\nclass ApvlvView final : public QMainWindow\n{\n  Q_OBJECT\npublic:\n  explicit ApvlvView (ApvlvView *view = nullptr);\n\n  ~ApvlvView () override;\n\n  ApvlvWindow *currentWindow ();\n\n  void delCurrentWindow ();\n\n  bool newTab (const std::string &filename);\n\n  bool newTab (ApvlvFrame *core);\n\n  void promptCommand (char ch);\n\n  void promptCommand (const char *str);\n\n  template <typename... T>\n  void\n  errorMessage (T... args)\n  {\n    std::stringstream msg;\n    msg << \"ERROR: \";\n    msg << (... + args);\n    mCommandBar.setText (QString::fromLocal8Bit (msg.str ()));\n    cmdShow (CmdStatusType::CMD_MESSAGE);\n  }\n\n  static char *input (const char *str, int width = 400, int height = 150,\n                      const std::string &content = \"\");\n\n  bool run (const char *str);\n\n  bool loadFile (const std::string &filename);\n\n  bool loadDir (const std::string &path);\n\n  std::optional<ApvlvFrame *> hasLoaded (std::string_view abpath);\n\n  void regLoaded (ApvlvFrame *doc);\n\n  CmdReturn process (int hastimes, int times, uint keyval);\n\n  CmdReturn subProcess (int times, uint keyval);\n\n  void cmdShow (CmdStatusType cmdtype);\n\n  void cmdHide ();\n\n  void cmdAuto (const char *str);\n\n  void setTitle (const std::string &title);\n\n  ApvlvFrame *currentFrame ();\n\n  void appendChild (ApvlvView *view);\n\n  void eraseChild (ApvlvView *view);\n\npublic slots:\n  void loadFileOnPage (const std::string &filename, int pn);\n\n  void open ();\n\n  void openDir ();\n\n  void openRrl ();\n\n  void quit (bool only_tab = true);\n\n  void search ();\n\n  void backSearch ();\n\n  void advancedSearch ();\n\n  void dired ();\n\n  void fullScreen ();\n\n  void nextPage ();\n\n  void previousPage ();\n\n  void toggleDirectory ();\n\n  void toggleToolBar ();\n\n  void toggleStatus ();\n\n  void newTab ();\n\n  void closeTab ();\n\n  void horizontalSplit ();\n\n  void verticalSplit ();\n\n  void unBirth ();\n\nprivate:\n  void setupMenuBar (const std::string &guiopt);\n\n  void setupToolBar ();\n\n  bool runCommand (const char *cmd);\n\n  void switchTab (int tabPos);\n\n  // Update the tab's context and update tab label.\n  void windowAdded ();\n\n  void updateTabName ();\n\n  CmdStatusType mCmdType;\n  std::chrono::time_point<std::chrono::steady_clock> mCmdTime;\n\n  uint mProCmd;\n  int mProCmdCnt;\n\n  QFrame mCentral;\n  QVBoxLayout mVBoxLayout;\n\n  QTabWidget mTabContainer;\n  ApvlvCommandBar mCommandBar;\n\n  QMenuBar mMenuBar;\n  QToolBar mToolBar;\n\n  bool mHasFull;\n\n  struct keyNode\n  {\n    ;\n    int Has;\n    int Ct;\n    uint Key;\n    bool End;\n  };\n  bool keyLastEnd;\n  bool processInLast;\n  std::vector<keyNode> keySquence;\n\n  void saveKey (int has, int ct, uint key, bool end);\n\n  void processLastKey ();\n\n  void closeEvent (QCloseEvent *evt) override;\n  void keyPressEvent (QKeyEvent *evt) override;\n\n  ApvlvCmds mCmds;\n\n  SearchDialog mSearchDialog;\n\n  std::vector<std::unique_ptr<ApvlvFrame>> mDocs;\n\n  std::vector<std::string> mCmdHistroy;\n  size_t mCurrHistroy;\n\n  ApvlvView *mParent;\n  std::vector<ApvlvView *> mChildren;\n\nprivate slots:\n  void commandbarEdited (const QString &str);\n  void commandbarReturn ();\n  void commandbarKeyPressed (QKeyEvent *gek);\n  void tabSwitched (int ind);\n};\n\n}\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/ApvlvWeb.cc",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvHtm.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QUrl>\n\n#include \"ApvlvWeb.h\"\n#include \"ApvlvWebViewWidget.h\"\n\nnamespace apvlv\n{\nusing namespace std;\n\nbool\nApvlvWEB::load (const string &filename)\n{\n  mUrl = filename.c_str ();\n  return true;\n}\n\nbool\nApvlvWEB::pageRenderToWebView (int pn, double zm, int rot, WebView *webview)\n{\n  webview->setZoomFactor (zm);\n  // do not load started page in browsing\n  if (!webview->url ().isValid ())\n    {\n      webview->load (mUrl);\n    }\n  return true;\n}\n\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/ApvlvWeb.h",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvWeb.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_WEB_H_\n#define _APVLV_WEB_H_\n\n#include <QUrl>\n\n#include \"ApvlvFile.h\"\n#include \"ApvlvWebViewWidget.h\"\n\nnamespace apvlv\n{\nclass ApvlvWEB : public File\n{\npublic:\n  ApvlvWEB () = default;\n\n  [[nodiscard]] DISPLAY_TYPE\n  getDisplayType () const override\n  {\n    return DISPLAY_TYPE::CUSTOM;\n  }\n\n  FileWidget *\n  getWidget () override\n  {\n    auto wid = new WebViewWidget ();\n    wid->setFile (this);\n    wid->setInternalScroll (true);\n    return wid;\n  }\n\n  bool load (const std::string &filename) override;\n\n  bool pageRenderToWebView (int pn, double zm, int rot,\n                            WebView *webview) override;\n\nprotected:\n  QUrl mUrl;\n};\n\n}\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/ApvlvWebViewWidget.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvWebViewWidget.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <filesystem>\n#include <iostream>\n#include <sstream>\n\n#include <QClipboard>\n#include <QFile>\n#include <QInputDialog>\n#include <QWebEngineScriptCollection>\n#include <qevent.h>\n\n#include \"ApvlvUtil.h\"\n#include \"ApvlvWebViewWidget.h\"\n\nnamespace apvlv\n{\nusing namespace std;\n\nvoid\nApvlvSchemeHandler::requestStarted (QWebEngineUrlRequestJob *job)\n{\n  auto url = job->requestUrl ();\n  auto path = url.path ().toStdString ();\n  auto key = path.substr (1);\n  auto mime = mFile->pathMimeType (key);\n  auto roptcont = mFile->pathContent (key);\n  if (!roptcont)\n    {\n      job->fail (QWebEngineUrlRequestJob::UrlNotFound);\n      return;\n    }\n\n  mArray = std::move (roptcont.value ());\n  mBuffer.setData (mArray);\n  job->reply (QByteArray (mime.c_str ()), &mBuffer);\n\n  emit webpageUpdated (key);\n}\n\nWebView::WebView ()\n{\n  mProfile.setHttpCacheType (QWebEngineProfile::NoCache);\n  mProfile.installUrlSchemeHandler (\"apvlv\", &mSchemeHandler);\n  mPage = make_unique<QWebEnginePage> (&mProfile);\n  setPage (mPage.get ());\n\n  mCopyAction.setText (tr (\"Copy\"));\n  QObject::connect (&mCopyAction, SIGNAL (triggered (bool)), this,\n                    SLOT (copy ()));\n  mMenu.addAction (&mCopyAction);\n\n  mUnderlineAction.setText (tr (\"Underline\"));\n  QObject::connect (&mUnderlineAction, SIGNAL (triggered (bool)), this,\n                    SLOT (underline ()));\n  mMenu.addAction (&mUnderlineAction);\n\n  mCommentAction.setText (tr (\"Comment\"));\n  QObject::connect (&mCommentAction, SIGNAL (triggered (bool)), this,\n                    SLOT (comment ()));\n  mMenu.addAction (&mCommentAction);\n}\n\nWebViewWidget::WebViewWidget ()\n{\n  QObject::connect (&mWebView, SIGNAL (loadFinished (bool)), this,\n                    SLOT (webviewLoadFinished (bool)));\n  QObject::connect (&mWebView.mSchemeHandler,\n                    SIGNAL (webpageUpdated (const string &)), this,\n                    SLOT (webviewUpdate (const string &)));\n  loadJavaScriptFromDir (ScriptDir);\n}\n\nbool\nWebViewWidget::loadJavaScriptFromDir (const std::string &dir)\n{\n  std::filesystem::path script_dir{ dir };\n  if (!is_directory (script_dir))\n    return false;\n\n  QList<QWebEngineScript> script_list;\n  for (auto &entry : std::filesystem::directory_iterator (\n           script_dir,\n           std::filesystem::directory_options::follow_directory_symlink))\n    {\n      auto const &path = entry.path ();\n      if (entry.is_regular_file () && path.extension () == \".js\")\n        {\n          QFile file (path);\n          if (!file.open (QIODevice::ReadOnly | QIODevice::Text))\n            {\n              qWarning () << \"open \" << path.c_str () << \"to read error\";\n              continue;\n            }\n\n          auto contents = file.readAll ();\n\n          auto script = QWebEngineScript{};\n          script.setName (QString::fromStdString (path.filename ()));\n          script.setSourceCode (contents);\n          script.setInjectionPoint (QWebEngineScript::DocumentCreation);\n          script.setWorldId (QWebEngineScript::MainWorld);\n          script.setRunsOnSubFrames (true);\n          script_list.push_back (script);\n        }\n    }\n\n  auto page = mWebView.page ();\n  page->scripts ().insert (script_list);\n  return true;\n}\n\nvoid\nWebViewWidget::showPage (int p, double s)\n{\n  mFile->pageRenderToWebView (p, mZoomrate, 0, &mWebView);\n  mPageNumber = p;\n}\n\nvoid\nWebViewWidget::showPage (int p, const string &anchor)\n{\n  mFile->pageRenderToWebView (p, mZoomrate, 0, &mWebView);\n  mPageNumber = p;\n  mAnchor = anchor;\n}\n\nvoid\nWebViewWidget::scroll (int times, int h, int v)\n{\n  if (!mFile)\n    return;\n\n  auto scripts\n      = QString (\"scrollByTimes(%1, %2, %3);\").arg (times).arg (h).arg (v);\n  auto page = mWebView.page ();\n  page->runJavaScript (scripts);\n}\n\nvoid\nWebViewWidget::scrollTo (double xrate, double yrate)\n{\n  if (!mFile)\n    return;\n\n  auto scripts\n      = QString (\"scrollToPosition(%1, %2);\").arg (xrate).arg (yrate);\n  auto page = mWebView.page ();\n  page->runJavaScript (scripts);\n}\n\nvoid\nWebViewWidget::scrollUp (int times)\n{\n  scroll (times, 0, -50);\n\n  if (mWebView.isScrolledToTop ())\n    {\n      if (mIsInternalScroll)\n        {\n          auto scripts = QString (\"dispatchKeydownEvent(%1);\").arg (37);\n          auto page = mWebView.page ();\n          page->runJavaScript (scripts);\n        }\n      else\n        {\n          auto p = mFile->pageNumberWrap (mPageNumber - 1);\n          if (p >= 0)\n            {\n              mIsScrollUp = true;\n              showPage (p, 0.0);\n            }\n        }\n    }\n}\n\nvoid\nWebViewWidget::scrollDown (int times)\n{\n  scroll (times, 0, 50);\n\n  if (mWebView.isScrolledToBottom ())\n    {\n      if (mIsInternalScroll)\n        {\n          auto scripts = QString (\"dispatchKeydownEvent(%1);\").arg (39);\n          auto page = mWebView.page ();\n          page->runJavaScript (scripts);\n        }\n      else\n        {\n          auto p = mFile->pageNumberWrap (mPageNumber + 1);\n          if (p >= 0)\n            showPage (p, 0.0);\n        }\n    }\n}\n\nvoid\nWebViewWidget::scrollLeft (int times)\n{\n  scroll (times, -50, 0);\n}\n\nvoid\nWebViewWidget::scrollRight (int times)\n{\n  scroll (times, 50, 0);\n}\n\nvoid\nWebViewWidget::setSearchStr (const string &str)\n{\n  auto qstr = QString::fromLocal8Bit (str);\n  QWebEnginePage::FindFlags flags{};\n  mWebView.findText (qstr, flags);\n}\n\nvoid\nWebViewWidget::setSearchSelect (int select)\n{\n  auto text = mWebView.selectedText ();\n  QWebEnginePage::FindFlags flags{};\n  mWebView.findText (text, flags);\n  mSearchSelect = select;\n}\n\nvoid\nWebViewWidget::setZoomrate (double zm)\n{\n  mWebView.setZoomFactor (zm);\n  mZoomrate = zm;\n}\n\nbool\nWebView::isScrolledToTop ()\n{\n  auto page = this->page ();\n  auto p = page->scrollPosition ();\n  return p.y () < 0.5;\n}\n\nbool\nWebView::isScrolledToBottom ()\n{\n  auto page = this->page ();\n  auto p = page->scrollPosition ();\n  auto cs = page->contentsSize ();\n  return p.y () + QWebEngineView::height () + 0.5 > cs.height ();\n}\n\nvoid\nWebView::contextMenuEvent (QContextMenuEvent *event)\n{\n  qDebug () << \"WebView::customeMenuRequest\";\n  mMenu.popup (mapToGlobal (event->pos ()));\n}\n\nstd::pair<int, int>\nWebView::getSelectionPosition () const\n{\n  int begin = -1;\n  int end = -1;\n  QEventLoop loop;\n  mPage->runJavaScript (\n      \"getSelectionOffset(0);\",\n      [&loop, &begin, &end] (const QVariant &result)\n        {\n          if (result.isValid ()\n              && result.typeId () == QMetaType::QVariantList)\n            {\n              auto offsets = result.toList ();\n              begin = offsets[0].toInt ();\n              end = offsets[1].toInt ();\n              qDebug () << \"Begin offset:\" << offsets[0].toInt ();\n              qDebug () << \"End offset:\" << offsets[1].toInt ();\n              loop.quit ();\n            }\n        });\n  loop.exec ();\n  return std::make_pair (begin, end);\n}\n\nvoid\nWebView::underLinePosition (int begin, int end, const std::string &tooltip)\n{\n  qDebug () << \"underLinePosition\" << begin << \" -> \" << end;\n  QString src = QString (\"underlineByOffset(%1, %2, '%3');\")\n    .arg (begin)\n    .arg (end)\n    .arg (tooltip.c_str());\n  mPage->runJavaScript (src);\n}\n\nvoid\nWebView::copy () const\n{\n  qDebug () << \"copy text\";\n  auto text = mPage->selectedText ();\n  auto clipboard = QGuiApplication::clipboard ();\n  clipboard->setText (text);\n}\n\nvoid\nWebView::underline ()\n{\n  qDebug () << \"underline text\";\n  auto text = mPage->selectedText ();\n  if (text.isEmpty ())\n    {\n      return;\n    }\n\n  auto offset = getSelectionPosition ();\n  if (offset.first < 0 || offset.second < 0)\n    {\n      return;\n    }\n\n  auto path = mPage->url ().path ().toStdString ();\n  auto file = mSchemeHandler.file ();\n  auto note = file->getNote ();\n  Comment comment;\n  comment.quoteText = text.toStdString ();\n  comment.begin.set (0, nullptr, offset.first, path);\n  comment.end.set (0, nullptr, offset.second, path);\n  note->addComment (comment);\n}\n\nvoid\nWebView::comment ()\n{\n  qDebug () << \"comment text\";\n  do\n    {\n      auto text = mPage->selectedText ();\n      if (text.isEmpty ())\n        break;\n\n      auto input_text\n          = QInputDialog::getText (this, tr (\"Input\"), tr (\"Comment\"));\n      auto commentText = input_text.trimmed ();\n      if (commentText.isEmpty ())\n        break;\n\n      commentText = commentText.toHtmlEscaped ();\n\n      auto offset = getSelectionPosition ();\n      if (offset.first < 0 || offset.second < 0)\n        break;\n\n      auto path = mPage->url ().path ().toStdString ();\n      auto file = mSchemeHandler.file ();\n      auto note = file->getNote ();\n      Comment comment;\n      comment.quoteText = text.toStdString ();\n      comment.commentText = commentText.toStdString ();\n      comment.begin.set (0, nullptr, offset.first, path);\n      comment.end.set (0, nullptr, offset.second, path);\n      note->addComment (comment);\n    }\n  while (false);\n}\n\nvoid\nWebViewWidget::setComment ()\n{\n  auto note = mFile->getNote ();\n  if (!note)\n    return;\n  auto path = mWebView.page ()->url ().path ().toStdString ();\n  auto comments = note->getCommentsInPath (path);\n  for (auto &comment : comments)\n    {\n      auto begin_offset = comment.begin.offset;\n      auto end_offset = comment.end.offset;\n      mWebView.underLinePosition (begin_offset, end_offset,\n                                  comment.commentText);\n    }\n}\n\nvoid\nWebViewWidget::webviewLoadFinished (bool suc)\n{\n  if (suc)\n    {\n      if (!mAnchor.empty ())\n        {\n          auto page = mWebView.page ();\n          auto scripts\n              = QString (\"scrollToAnchor('%1');\").arg (mAnchor.c_str ());\n          page->runJavaScript (scripts);\n        }\n      else if (mIsScrollUp)\n        {\n          scrollTo (0.0, 1.0);\n          mIsScrollUp = false;\n        }\n\n      if (!mIsScrollUp)\n        {\n          setComment ();\n        }\n    }\n}\n\nvoid\nWebViewWidget::webviewFindTextFinished (\n    const QWebEngineFindTextResult &result)\n{\n  mSearchResult = result;\n}\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/ApvlvWebViewWidget.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvWebViewWidget.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_WEBVIEW_WIDGET_H_\n#define _APVLV_WEBVIEW_WIDGET_H_\n\n#include <memory>\n#include <string>\n\n#include <QBuffer>\n#include <QByteArray>\n#include <QMenu>\n#include <QWebEngineFindTextResult>\n#include <QWebEngineProfile>\n#include <QWebEngineUrlRequestJob>\n#include <QWebEngineUrlSchemeHandler>\n#include <QWebEngineView>\n\n#include \"ApvlvFileWidget.h\"\n\nnamespace apvlv\n{\nclass File;\nclass ApvlvSchemeHandler : public QWebEngineUrlSchemeHandler\n{\n  Q_OBJECT\npublic:\n  void\n  setFile (File *file)\n  {\n    mFile = file;\n  }\n  File *\n  file () const\n  {\n    return mFile;\n  }\n  void requestStarted (QWebEngineUrlRequestJob *job) override;\n\nprivate:\n  File *mFile;\n  QByteArray mArray;\n  QBuffer mBuffer;\n\nsignals:\n  void webpageUpdated (const std::string &key);\n};\n\nclass WebView : public QWebEngineView\n{\n  Q_OBJECT\npublic:\n  WebView ();\n  void\n  setFile (File *file)\n  {\n    mSchemeHandler.setFile (file);\n  }\n\nprotected:\n  void contextMenuEvent (QContextMenuEvent *event) override;\n\nprivate:\n  QWebEngineProfile mProfile;\n  std::unique_ptr<QWebEnginePage> mPage;\n  ApvlvSchemeHandler mSchemeHandler;\n  QMenu mMenu;\n\n  bool isScrolledToTop ();\n  bool isScrolledToBottom ();\n\n  [[nodiscard]] std::pair<int, int> getSelectionPosition () const;\n  void underLinePosition (int begin, int end, const std::string &);\n\n  QAction mCopyAction;\n  QAction mUnderlineAction;\n  QAction mCommentAction;\n\n  friend class WebViewWidget;\n\nprivate slots:\n  void copy () const;\n  void underline ();\n  void comment ();\n};\n\nclass WebViewWidget : public FileWidget\n{\n  Q_OBJECT\npublic:\n  WebViewWidget ();\n\n  [[nodiscard]] QWidget *\n  widget () override\n  {\n    return &mWebView;\n  }\n\n  void\n  setFile (File *file) override\n  {\n    mFile = file;\n    mWebView.setFile (mFile);\n  }\n\n  void showPage (int pn, double s) override;\n  void showPage (int pn, const std::string &anchor) override;\n\n  void scroll (int times, int w, int h) override;\n  void scrollTo (double x, double y) override;\n\n  void scrollUp (int times) override;\n  void scrollDown (int times) override;\n  void scrollLeft (int times) override;\n  void scrollRight (int times) override;\n\n  void setSearchStr (const std::string &str) override;\n  void setSearchSelect (int select) override;\n\n  void setZoomrate (double zm) override;\n\n  void\n  setInternalScroll (bool internal)\n  {\n    mIsInternalScroll = internal;\n  }\n  bool\n  internalScroll ()\n  {\n    return mIsInternalScroll;\n  }\n\nprivate:\n  WebView mWebView{};\n  bool mIsInternalScroll{ false };\n  bool mIsScrollUp{ false };\n  QWebEngineFindTextResult mSearchResult;\n\n  bool loadJavaScriptFromDir (const std::string &dir);\n\n  void setComment ();\n\nsignals:\n  void webpageUpdated (const std::string &msg);\n\nprivate slots:\n  void\n  webviewUpdate (const std::string &msg)\n  {\n    emit webpageUpdated (msg);\n  };\n\n  void webviewLoadFinished (bool suc);\n  void webviewFindTextFinished (const QWebEngineFindTextResult &result);\n};\n\n}\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/ApvlvWidget.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvWidget.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QKeyEvent>\n\n#include \"ApvlvWidget.h\"\n\nnamespace apvlv\n{\nvoid\nApvlvLineEdit::keyPressEvent (QKeyEvent *event)\n{\n  if (event->key () == Qt::Key_Escape)\n    {\n      clearFocus ();\n      event->ignore ();\n    }\n  else\n    {\n      QLineEdit::keyPressEvent (event);\n    }\n}\n\n}"
  },
  {
    "path": "src/ApvlvWidget.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvWidget.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_WIDGET_H_\n#define _APVLV_WIDGET_H_\n\n#include <QLineEdit>\n\nnamespace apvlv\n{\n\nclass ApvlvLineEdit : public QLineEdit\n{\nprotected:\n  void keyPressEvent (QKeyEvent *event) override;\n};\n\n}\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/ApvlvWindow.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvWindow.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QSplitter>\n#include <QStackedWidget>\n#include <stack>\n\n#include \"ApvlvParams.h\"\n#include \"ApvlvView.h\"\n#include \"ApvlvWindow.h\"\n\nnamespace apvlv\n{\nApvlvWindow::ApvlvWindow ()\n{\n  qDebug () << \"win: \" << this << \", init\";\n  setLayout (&mLayout);\n  mLayout.addWidget (&mPaned);\n}\n\nApvlvWindow::~ApvlvWindow ()\n{\n  qDebug () << \"win: \" << this << \", released\";\n  if (mType == WindowType::FRAME)\n    {\n      auto frame = stealFrame ();\n      if (frame)\n        frame->inuse (false);\n    }\n}\n\nCmdReturn\nApvlvWindow::process (int ct, uint key)\n{\n  ApvlvWindow *nwin;\n  qDebug () << \"input [\" << key << \"]\";\n\n  switch (key)\n    {\n    case ctrlValue ('w'):\n    case 'k':\n    case 'j':\n    case 'h':\n    case 'l':\n      nwin = getNeighbor (ct, key);\n      if (nwin != nullptr && nwin != this)\n        {\n          nwin->setActive (true);\n        }\n      break;\n\n    case '-':\n      smaller (ct);\n      break;\n\n    case '+':\n      bigger (ct);\n      break;\n\n    default:\n      break;\n    }\n  return CmdReturn::MATCH;\n}\n\nApvlvWindow *\nApvlvWindow::findWindowByWidget (QWidget *widget)\n{\n  auto doc = ApvlvFrame::findByWidget (widget);\n  if (doc == nullptr)\n    return nullptr;\n\n  std::stack<ApvlvWindow *> winstack;\n  winstack.push (this);\n  while (!winstack.empty ())\n    {\n      auto win = winstack.top ();\n      winstack.pop ();\n      if (win->mType == ApvlvWindow::WindowType::FRAME)\n        {\n          if (win->getFrame () == doc)\n            return win;\n        }\n      else\n        {\n          winstack.push (win->firstWindow ());\n          winstack.push (win->secondWindow ());\n        }\n    }\n\n  return nullptr;\n}\n\nApvlvWindow *\nApvlvWindow::getNeighbor (int count, uint key)\n{\n  ApvlvWindow *last_win = nullptr;\n  ApvlvWindow *win = this;\n\n  for (int c = 0; c < count; ++c)\n    {\n      switch (key)\n        {\n        case ctrlValue ('w'):\n          win = win->getNext ();\n          break;\n        case 'k':\n          win = win->getTop ();\n          break;\n        case 'j':\n          win = win->getBottom ();\n          break;\n        case 'h':\n          win = win->getLeft ();\n          break;\n        case 'l':\n          win = win->getRight ();\n          break;\n        default:\n          break;\n        }\n\n      if (win != nullptr)\n        last_win = win;\n      else\n        break;\n    }\n\n  return last_win;\n}\n\nApvlvWindow *\nApvlvWindow::getLeft ()\n{\n  if (mType == WindowType::FRAME\n      && getFrame ()->toggledControlDirectory (false))\n    {\n      return this;\n    }\n\n  ApvlvWindow *fwin = this;\n\n  while (fwin->parentWindow ())\n    {\n      fwin = fwin->parentWindow ();\n      if (fwin->mType == WindowType::SP && fwin->firstWindow () != this)\n        {\n          if (fwin->secondWindow () == this)\n            return fwin->firstWindow ();\n\n          while (fwin->mType != WindowType::FRAME)\n            {\n              fwin = fwin->secondWindow ();\n            }\n          return fwin;\n        }\n    }\n\n  return nullptr;\n}\n\nApvlvWindow *\nApvlvWindow::getRight ()\n{\n  if (mType == WindowType::FRAME\n      && getFrame ()->toggledControlDirectory (true))\n    {\n      return this;\n    }\n\n  ApvlvWindow *fwin = this;\n\n  while (fwin->parentWindow ())\n    {\n      fwin = fwin->parentWindow ();\n      if (fwin->mType == WindowType::SP && fwin->secondWindow () != this)\n        {\n          if (fwin->firstWindow () == this)\n            return fwin->secondWindow ();\n\n          while (fwin->mType != WindowType::FRAME)\n            {\n              fwin = fwin->firstWindow ();\n            }\n          return fwin;\n        }\n    }\n\n  return nullptr;\n}\n\nApvlvWindow *\nApvlvWindow::getTop ()\n{\n  ApvlvWindow *fwin = this;\n\n  while (fwin->parentWindow ())\n    {\n      fwin = fwin->parentWindow ();\n      if (fwin->mType == WindowType::VSP && (fwin->firstWindow () != this))\n        {\n          if (fwin->secondWindow () == this)\n            return fwin->firstWindow ();\n\n          while (fwin->mType != WindowType::FRAME)\n            {\n              fwin = fwin->secondWindow ();\n            }\n          return fwin;\n        }\n    }\n\n  return nullptr;\n}\n\nApvlvWindow *\nApvlvWindow::getBottom ()\n{\n  ApvlvWindow *fwin = this;\n\n  while (fwin->parentWindow ())\n    {\n      fwin = fwin->parentWindow ();\n      if (fwin->mType == WindowType::VSP && fwin->secondWindow () != this)\n        {\n          if (fwin->firstWindow () == this)\n            return fwin->secondWindow ();\n\n          while (fwin->mType != WindowType::FRAME)\n            {\n              fwin = fwin->firstWindow ();\n            }\n          return fwin;\n        }\n    }\n\n  return nullptr;\n}\n\nvoid\nApvlvWindow::splitWidget (WindowType type, QWidget *one, QWidget *other)\n{\n  setFrameStyle (QFrame::NoFrame);\n  setLineWidth (0);\n\n  mPaned.setOrientation (type == WindowType::SP ? Qt::Horizontal\n                                                : Qt::Vertical);\n  mPaned.setHandleWidth (10);\n  mPaned.setStretchFactor (0, 1);\n  mPaned.setStretchFactor (1, 1);\n\n  mPaned.addWidget (one);\n  mPaned.addWidget (other);\n\n  mType = type;\n}\n\nvoid\nApvlvWindow::perishWidget ()\n{\n  auto par = parentWindow ();\n  auto win1 = par->firstWindow ();\n  auto win2 = par->secondWindow ();\n  auto rewin = (this == win1 ? win2 : win1);\n\n  auto par_par = par->parent ();\n  if (par_par->inherits (\"QStackedWidget\"))\n    {\n      win1->setParent (nullptr);\n      win2->setParent (nullptr);\n\n      if (rewin->mType == WindowType::FRAME)\n        {\n          auto frame = rewin->stealFrame ();\n          par->setFrame (frame);\n        }\n      else\n        {\n          par->splitWidget (rewin->mType, rewin->firstWindow (),\n                            rewin->secondWindow ());\n        }\n\n      qDebug () << win1 << \" will be deleted \";\n      win1->deleteLater ();\n      qDebug () << win2 << \" will be deleted \";\n      win2->deleteLater ();\n    }\n  else\n    {\n      auto par_splitter = dynamic_cast<QSplitter *> (par_par);\n      auto par0 = dynamic_cast<ApvlvWindow *> (par_splitter->widget ((0)));\n      auto par1 = dynamic_cast<ApvlvWindow *> (par_splitter->widget ((1)));\n      rewin->setParent (nullptr);\n      par0->setParent (nullptr);\n      par1->setParent (nullptr);\n      if (par0 == par)\n        {\n          qDebug () << rewin << \" and \" << par1 << \" will be inserted \";\n          par_splitter->addWidget (rewin);\n          par_splitter->addWidget (par1);\n          qDebug () << par0 << \" will be deleted \";\n          par0->deleteLater ();\n        }\n      else\n        {\n          qDebug () << par0 << \" and \" << rewin << \" will be inserted \";\n          par_splitter->addWidget (par0);\n          par_splitter->addWidget (rewin);\n          qDebug () << par1 << \" will be deleted \";\n          par1->deleteLater ();\n        }\n    }\n}\n\nvoid\nApvlvWindow::setAsRootActive ()\n{\n  auto root_win = rootWindow ();\n  if (root_win)\n    root_win->setActiveWindow (this);\n}\n\nApvlvWindow *\nApvlvWindow::getNext ()\n{\n  auto *n = getRight ();\n  if (n)\n    return n;\n\n  n = getBottom ();\n  if (n)\n    return n;\n\n  n = getLeft ();\n  if (n)\n    return n;\n\n  n = getTop ();\n  return n;\n}\n\n// birth a new FRAME window, and the new window beyond the input doc\n// this made a FRAME window to SP|VSP\nbool\nApvlvWindow::birth (WindowType type, ApvlvFrame *doc)\n{\n  Q_ASSERT (mType == WindowType::FRAME);\n\n  auto frame = dynamic_cast<ApvlvFrame *> (mPaned.widget (0));\n  frame->setParent (nullptr);\n\n  if (doc == nullptr)\n    {\n      doc = frame->clone ();\n      if (doc == nullptr)\n        {\n          frame->mView->errorMessage (\"can't split\");\n          return false;\n        }\n\n      frame->mView->regLoaded (doc);\n    }\n\n  auto win1 = new ApvlvWindow ();\n  win1->setFrame (frame);\n\n  auto win2 = new ApvlvWindow ();\n  win2->setFrame (doc);\n\n  qDebug () << \"win: \" << this << \" birth two: \" << win1 << \" and \" << win2;\n  splitWidget (type, win1, win2);\n\n  if (auto root = rootWindow (); root && root->mActive == this)\n    {\n      root->mActive = win1;\n    }\n\n  return true;\n}\n\nvoid\nApvlvWindow::perish ()\n{\n  qDebug () << \"win: \" << this << \" try to perish \";\n  if (mType != WindowType::FRAME)\n    return;\n\n  if (auto root = rootWindow (); root && root->mActive == this)\n    {\n      root->mActive = nullptr;\n    }\n\n  perishWidget ();\n}\n\nvoid\nApvlvWindow::setActive (bool act)\n{\n  Q_ASSERT (mType == WindowType::FRAME);\n  QTimer::singleShot (50, getFrame (), SLOT (setFocus ()));\n}\n\nvoid\nApvlvWindow::setFrame (ApvlvFrame *doc)\n{\n  qDebug () << \"win: \" << this << \", set core: \" << doc;\n  setFrameStyle (QFrame::Raised | QFrame::Box);\n  setLineWidth (1);\n\n  if (mType == WindowType::FRAME)\n    {\n      auto frame = stealFrame ();\n      if (frame)\n        {\n          frame->inuse (false);\n        }\n    }\n\n  mPaned.setStretchFactor (0, 1);\n  mPaned.addWidget (doc);\n  doc->inuse (true);\n  mType = WindowType::FRAME;\n\n  QObject::connect (doc, SIGNAL (focusIn ()), this,\n                    SLOT (setAsRootActive ()));\n}\n\nApvlvFrame *\nApvlvWindow::stealFrame ()\n{\n  Q_ASSERT (mType == WindowType::FRAME);\n  auto frame = dynamic_cast<ApvlvFrame *> (mPaned.widget (0));\n  if (frame)\n    {\n      frame->setParent (nullptr);\n    }\n  return frame;\n}\n\nApvlvFrame *\nApvlvWindow::getFrame ()\n{\n  Q_ASSERT (mType == WindowType::FRAME);\n  auto frame = dynamic_cast<ApvlvFrame *> (mPaned.widget (0));\n  return frame;\n}\n\nApvlvWindow *\nApvlvWindow::firstWindow ()\n{\n  Q_ASSERT (mType != WindowType::FRAME);\n  auto win = dynamic_cast<ApvlvWindow *> (mPaned.widget (0));\n  return win;\n}\n\nApvlvWindow *\nApvlvWindow::secondWindow ()\n{\n  Q_ASSERT (mType != WindowType::FRAME);\n  auto win = dynamic_cast<ApvlvWindow *> (mPaned.widget (1));\n  return win;\n}\n\nApvlvWindow *\nApvlvWindow::parentWindow ()\n{\n  auto win = parent ();\n  Q_ASSERT (win);\n  if (win->inherits (\"QSplitter\"))\n    win = win->parent ();\n  return dynamic_cast<ApvlvWindow *> (win);\n}\n\nApvlvWindow *\nApvlvWindow::rootWindow ()\n{\n  auto win = this;\n  while (win && !win->isRoot ())\n    win = win->parentWindow ();\n  return win;\n}\n\nApvlvWindow *\nApvlvWindow::firstFrameWindow ()\n{\n  auto win = this;\n  while (win->mType != WindowType::FRAME)\n    {\n      win = win->firstWindow ();\n    }\n\n  return win;\n}\n\nbool\nApvlvWindow::isRoot ()\n{\n  auto par = parentWindow ();\n  if (par == nullptr)\n    return true;\n  else\n    return false;\n}\n\nvoid\nApvlvWindow::smaller (int times)\n{\n  if (isRoot ())\n    return;\n\n  auto pwin = parentWindow ();\n  auto sizes = pwin->mPaned.sizes ();\n  int len = 20 * times;\n  if (pwin->firstWindow () == this)\n    {\n      sizes[0] -= len;\n      sizes[1] += len;\n    }\n  else\n    {\n      sizes[0] += len;\n      sizes[1] -= len;\n    }\n  pwin->mPaned.setSizes (sizes);\n}\n\nvoid\nApvlvWindow::bigger (int times)\n{\n  if (isRoot ())\n    return;\n\n  smaller (0 - times);\n}\n\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/ApvlvWindow.h",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvWindow.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_WINDOW_H_\n#define _APVLV_WINDOW_H_\n\n#include \"ApvlvFrame.h\"\n\nnamespace apvlv\n{\nclass ApvlvFrame;\nclass ApvlvWindowContext;\n\nclass ApvlvWindow final : public QFrame\n{\n  Q_OBJECT\npublic:\n  ApvlvWindow ();\n  ~ApvlvWindow () override;\n\n  /* WE operate the AW_DOC window\n   * Any SP, VSP are a virtual window, just for contain the AW_DOC window\n   * AW_NONE is an empty window, need free\n   * So, ANY user interface function can only get the AW_DOC window\n   * */\n  enum class WindowType\n  {\n    FRAME,\n    SP,\n    VSP,\n  };\n  WindowType mType{ WindowType::FRAME };\n\n  bool birth (WindowType type, ApvlvFrame *doc);\n\n  void perish ();\n\n  void setActive (bool act);\n\n  void setFrame (ApvlvFrame *doc);\n  ApvlvFrame *stealFrame ();\n  ApvlvFrame *getFrame ();\n\n  ApvlvWindow *firstWindow ();\n  ApvlvWindow *secondWindow ();\n  ApvlvWindow *parentWindow ();\n  ApvlvWindow *rootWindow ();\n  ApvlvWindow *firstFrameWindow ();\n\n  void\n  setActiveWindow (ApvlvWindow *win)\n  {\n    mActive = win;\n  }\n  ApvlvWindow *\n  getActiveWindow ()\n  {\n    return mActive;\n  }\n\n  bool isRoot ();\n\n  void smaller (int times = 1);\n  void bigger (int times = 1);\n\n  ApvlvWindow *getNeighbor (int count, uint key);\n\n  ApvlvWindow *getNext ();\n\n  CmdReturn process (int times, uint keyval);\n\n  ApvlvWindow *findWindowByWidget (QWidget *widget);\n\nprivate:\n  QVBoxLayout mLayout;\n  QSplitter mPaned;\n\n  ApvlvWindow *mActive{ nullptr };\n\n  ApvlvWindow *getLeft ();\n  ApvlvWindow *getRight ();\n  ApvlvWindow *getTop ();\n  ApvlvWindow *getBottom ();\n\n  void splitWidget (WindowType type, QWidget *one, QWidget *other);\n\nprivate slots:\n  void perishWidget ();\n  void setAsRootActive ();\n};\n\n}\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/CMakeLists.txt",
    "content": "# Simplified CMakeLists.txt for apvlv src directory\n\n# Include source and engine configurations\ninclude(${CMAKE_CURRENT_SOURCE_DIR}/Sources.cmake)\ninclude(${CMAKE_CURRENT_SOURCE_DIR}/Engines.cmake)\ninclude(${CMAKE_CURRENT_SOURCE_DIR}/WindowsEngines.cmake)\n\n# Setup include directories\ninclude_directories(\n    ${CMAKE_CURRENT_SOURCE_DIR}\n    ${QUAZIP_INCLUDE_DIRS}\n    ${Qt_INCLUDE_DIRS}\n    ${CMARK_INCLUDE_DIRS}\n)\n\n# Setup link directories\nlink_directories(${QUAZIP_LIBRARY_DIRS})\n\n# Initialize required libraries\nset(APVLV_REQ_LIBRARIES\n    ${QUAZIP_LIBRARIES}\n    ${Qt_LIBRARIES}\n    ${CMARK_LIBRARIES}\n)\n\n# Add engine-specific files and libraries\nlist(APPEND HEADERS ${APVLV_ENGINE_HEADERS})\nlist(APPEND SOURCES ${APVLV_ENGINE_SOURCES})\nlist(APPEND APVLV_REQ_LIBRARIES ${APVLV_ENGINE_LIBRARIES})\n\n# Message about engines\nmessage(STATUS \"Building with QtPdf engine\")\nmessage(STATUS \"Building with Web engine for EPUB/FB2\")\n\n# Format target for clang-format\nfind_program(CLANG_FORMAT clang-format)\nif(CLANG_FORMAT)\n    # Get all source files\n    file(GLOB_RECURSE ALL_CXX_SOURCES\n        \"${CMAKE_CURRENT_SOURCE_DIR}/*.cc\"\n        \"${CMAKE_CURRENT_SOURCE_DIR}/*.h\"\n    )\n\n    # Create format target\n    add_custom_target(format\n        COMMAND ${CLANG_FORMAT}\n        -i\n        -style=file\n        ${ALL_CXX_SOURCES}\n        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}\n        COMMENT \"Formatting source code with clang-format\"\n    )\n\n    message(STATUS \"Added 'make format' target for code formatting\")\nelse()\n    message(WARNING \"clang-format not found, 'make format' target not available\")\nendif()\n\n# Create executable\nadd_executable(apvlv ${HEADERS} ${SOURCES})\nset_property(TARGET apvlv PROPERTY AUTOMOC ON)\ntarget_link_libraries(apvlv ${APVLV_REQ_LIBRARIES})\n\n# Test executable\nadd_executable(testNote ApvlvNote.cc ApvlvMarkdown.cc testNote.cc)\nset_property(TARGET testNote PROPERTY AUTOMOC ON)\ntarget_link_libraries(testNote ${APVLV_REQ_LIBRARIES})\n\n# Post-build operations\nif(WIN32)\n    # Windows specific post-build\n    add_custom_command(TARGET apvlv POST_BUILD\n        COMMAND ${CMAKE_COMMAND} -E copy_directory\n        ${CMAKE_SOURCE_DIR}/share ${CMAKE_CURRENT_BINARY_DIR}/share\n    )\nelse()\n    # Unix specific post-build\n    add_custom_command(TARGET apvlv POST_BUILD\n        COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/share\n        COMMAND ${CMAKE_COMMAND} -E copy_directory\n        ${CMAKE_SOURCE_DIR}/share ${CMAKE_BINARY_DIR}/share\n    )\nendif()\n\n# Translation setup\nfind_program(LUPDATE lupdate HINTS \"${_qt_bin_dir}\")\nfind_program(LRELEASE lrelease HINTS \"${_qt_bin_dir}\")\nif(LUPDATE STREQUAL \"LUPDATE-NOTFOUND\")\n    find_program(LUPDATE lupdate-qt6 HINTS \"${_qt_bin_dir}\")\n    find_program(LRELEASE lrelease-qt6 HINTS \"${_qt_bin_dir}\")\nendif()\n\nfile(GLOB cppfiles *.cc)\nadd_custom_target(apvlv_lupdate\n    COMMAND ${LUPDATE} -locations none -target-language zh_CN\n    ${cppfiles} -ts ${CMAKE_SOURCE_DIR}/zh_CN.ts\n)\nadd_custom_target(apvlv_lrelease\n    COMMAND ${LRELEASE} ${CMAKE_SOURCE_DIR}/zh_CN.ts\n    -qm ${CMAKE_SOURCE_DIR}/share/doc/apvlv/translations/zh_CN.qm\n)\nadd_dependencies(apvlv_lrelease apvlv_lupdate)\nadd_dependencies(apvlv apvlv_lrelease)\n\n# Installation\ninstall(TARGETS apvlv DESTINATION bin)\n"
  },
  {
    "path": "src/Engines.cmake",
    "content": "# Document engine configuration\n\n# Initialize engine-specific variables\nset(APVLV_ENGINE_HEADERS \"\")\nset(APVLV_ENGINE_SOURCES \"\")\nset(APVLV_ENGINE_LIBRARIES \"\")\n\n# MuPDF engine\nif(APVLV_WITH_MUPDF)\n  message(STATUS \"Enable MuPDF engine\")\n  list(APPEND APVLV_ENGINE_HEADERS file/ApvlvMuPdf.h)\n  list(APPEND APVLV_ENGINE_SOURCES file/ApvlvMuPdf.cc)\n\n  if(WIN32)\n    if(MUPDF_FOUND)\n      if(TARGET mupdf::mupdf)\n        list(APPEND APVLV_ENGINE_LIBRARIES mupdf::mupdf)\n      elseif(MUPDF_STATIC_LIBRARIES)\n        list(APPEND APVLV_ENGINE_LIBRARIES ${MUPDF_STATIC_LIBRARIES})\n      else()\n        list(APPEND APVLV_ENGINE_LIBRARIES mupdf)\n      endif()\n    else()\n      # Fallback to direct library name\n      list(APPEND APVLV_ENGINE_LIBRARIES mupdf)\n    endif()\n  else()\n    if(MUPDF_FOUND)\n      list(APPEND APVLV_ENGINE_LIBRARIES ${MUPDF_STATIC_LIBRARIES} -lharfbuzz)\n    else()\n      list(APPEND APVLV_ENGINE_LIBRARIES -lmupdf)\n    endif()\n  endif()\nendif()\n\n# Poppler engine\nif(APVLV_WITH_POPPLER)\n  if(WIN32)\n    # On Windows, Poppler is enabled by default\n    message(STATUS \"Enable Poppler engine\")\n    list(APPEND APVLV_ENGINE_HEADERS file/ApvlvPopplerPdf.h)\n    list(APPEND APVLV_ENGINE_SOURCES file/ApvlvPopplerPdf.cc)\n    if(POPPLER_FOUND)\n      if(TARGET Poppler::poppler)\n        list(APPEND APVLV_ENGINE_LIBRARIES Poppler::poppler)\n      elseif(POPPLER_LIBRARIES)\n        list(APPEND APVLV_ENGINE_LIBRARIES ${POPPLER_LIBRARIES})\n      else()\n        list(APPEND APVLV_ENGINE_LIBRARIES poppler)\n      endif()\n    else()\n      # Fallback to direct library name\n      list(APPEND APVLV_ENGINE_LIBRARIES poppler)\n    endif()\n  elseif()\n    message(STATUS \"Enable Poppler engine\")\n    include_directories(${POPPLER_INCLUDE_DIRS})\n    list(APPEND APVLV_ENGINE_HEADERS file/ApvlvPopplerPdf.h)\n    list(APPEND APVLV_ENGINE_SOURCES file/ApvlvPopplerPdf.cc)\n    list(APPEND APVLV_ENGINE_LIBRARIES ${POPPLER_LIBRARIES})\n  endif()\nendif()\n\n# DjVu support\nif(APVLV_WITH_DJVU)\n  message(STATUS \"Enable DjVu support\")\n  list(APPEND APVLV_ENGINE_HEADERS file/ApvlvDjvu.h)\n  list(APPEND APVLV_ENGINE_SOURCES file/ApvlvDjvu.cc)\n\n  if(WIN32)\n    if(TARGET djvulibre::djvulibre)\n      list(APPEND APVLV_ENGINE_LIBRARIES djvulibre::djvulibre)\n    elseif(DJVULIBRE_DIR)\n      include_directories(${DJVULIBRE_DIR}/include)\n      link_directories(${DJVULIBRE_DIR}/lib)\n      list(APPEND APVLV_ENGINE_LIBRARIES djvulibre)\n    else()\n      # Fallback to direct library name\n      list(APPEND APVLV_ENGINE_LIBRARIES djvulibre)\n    endif()\n  else()\n    list(APPEND APVLV_ENGINE_LIBRARIES -ldjvulibre)\n  endif()\nendif()\n\n# OCR support\nif(APVLV_WITH_OCR)\n  message(STATUS \"Enable OCR support\")\n  add_definitions(-DAPVLV_WITH_OCR)\n  list(APPEND APVLV_ENGINE_HEADERS ApvlvOCR.h)\n  list(APPEND APVLV_ENGINE_SOURCES ApvlvOCR.cc)\n\n  if(WIN32)\n    if(TESSERACT_FOUND)\n      if(TARGET tesseract::tesseract)\n        list(APPEND APVLV_ENGINE_LIBRARIES tesseract::tesseract)\n      elseif(TESSERACT_LIBRARIES)\n        list(APPEND APVLV_ENGINE_LIBRARIES ${TESSERACT_LIBRARIES})\n      else()\n        list(APPEND APVLV_ENGINE_LIBRARIES tesseract)\n      endif()\n    else()\n      # Fallback to direct library name\n      list(APPEND APVLV_ENGINE_LIBRARIES tesseract)\n    endif()\n  else()\n    if(TESSERACT_FOUND)\n      list(APPEND APVLV_ENGINE_LIBRARIES ${TESSERACT_LIBRARIES})\n    endif()\n  endif()\nendif()\n"
  },
  {
    "path": "src/Sources.cmake",
    "content": "# Source files configuration\n\n# Core headers\nset(HEADERS\n    ApvlvCmds.h\n    ApvlvFrame.h\n    ApvlvFile.h\n    ApvlvFileIndex.h\n    ApvlvFileWidget.h\n    ApvlvWidget.h\n    ApvlvWeb.h\n    ApvlvInfo.h\n    ApvlvParams.h\n    ApvlvUtil.h\n    ApvlvView.h\n    ApvlvWindow.h\n    ApvlvCompletion.h\n    ApvlvDirectory.h\n    ApvlvLab.h\n    ApvlvLog.h\n    ApvlvSearch.h\n    ApvlvSearchDialog.h\n    ApvlvDired.h\n    ApvlvQueue.h\n    ApvlvImageWidget.h\n    ApvlvWebViewWidget.h\n    ApvlvEditor.h\n    ApvlvNote.h\n    ApvlvNoteWidget.h\n    ApvlvMarkdown.h\n    file/ApvlvHtm.h\n    file/ApvlvImage.h\n    file/ApvlvQtPdf.h\n    file/ApvlvEpub.h\n    file/ApvlvFb2.h\n    file/ApvlvTxt.h\n)\n\n# Core sources\nset(SOURCES\n    ApvlvCmds.cc\n    ApvlvFrame.cc\n    ApvlvFile.cc\n    ApvlvFileIndex.cc\n    ApvlvFileWidget.cc\n    ApvlvWidget.cc\n    ApvlvWeb.cc\n    ApvlvInfo.cc\n    ApvlvParams.cc\n    ApvlvUtil.cc\n    ApvlvView.cc\n    ApvlvWindow.cc\n    ApvlvCompletion.cc\n    ApvlvDirectory.cc\n    ApvlvLab.cc\n    ApvlvLog.cc\n    ApvlvSearch.cc\n    ApvlvSearchDialog.cc\n    ApvlvDired.cc\n    ApvlvQueue.cc\n    ApvlvImageWidget.cc\n    ApvlvWebViewWidget.cc\n    ApvlvEditor.cc\n    ApvlvNote.cc\n    ApvlvNoteWidget.cc\n    ApvlvMarkdown.cc\n    file/ApvlvHtm.cc\n    file/ApvlvImage.cc\n    file/ApvlvQtPdf.cc\n    file/ApvlvEpub.cc\n    file/ApvlvFb2.cc\n    file/ApvlvTxt.cc\n    main.cc\n)\n"
  },
  {
    "path": "src/WindowsEngines.cmake",
    "content": "# Windows-specific document engines\n\nif(WIN32 AND APVLV_WITH_OFFICE)\n    message(STATUS \"Enable MSOffice as office file engine\")\n    find_package(Qt6AxContainer REQUIRED)\n    include_directories(${Qt6AxContainer_INCLUDE_DIRS})\n    list(APVLV_ENGINE_LIBRARIES ${Qt6AxContainer_LIBRARIES} Shlwapi.lib)\n    list(APPEND APVLV_ENGINE_HEADERS file/ApvlvAxOffice.h)\n    list(APPEND APVLV_ENGINE_SOURCES file/ApvlvAxOffice.cc)\nelseif(NOT WIN32 AND APVLV_WITH_OFFICE)\n    message(STATUS \"Enable libreOffice as office file engine\")\n    list(APPEND APVLV_ENGINE_LIBRARIES -llibreofficekitgtk)\n    add_definitions(-DLIBO_INTERNAL_ONLY=1)\n    list(APPEND APVLV_ENGINE_HEADERS file/ApvlvLibreOffice.h)\n    list(APPEND APVLV_ENGINE_SOURCES file/ApvlvLibreOffice.cc)\nendif()\n"
  },
  {
    "path": "src/config.h.in",
    "content": "#ifndef APVLV_CONFIG_H\n#define APVLV_CONFIG_H\n\n// Package information\n#define PACKAGE_NAME \"@PACKAGE_NAME@\"\n#define PACKAGE_VERSION \"@PACKAGE_VERSION@\"\n#define PACKAGE_BUGREPORT \"@PACKAGE_BUGREPORT@\"\n#define RELEASE \"@RELEASE@\"\n\n// System paths\n#define SYSCONFDIR \"@SYSCONFDIR@\"\n\n// Feature flags\n#cmakedefine APVLV_WITH_OCR\n#cmakedefine APVLV_WITH_MUPDF\n#cmakedefine APVLV_WITH_POPPLER\n#cmakedefine APVLV_WITH_DJVU\n#cmakedefine APVLV_WITH_OFFICE\n\n#endif // APVLV_CONFIG_H\n"
  },
  {
    "path": "src/file/ApvlvAxOffice.cc",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvOffice.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <Ole2.h>\n#include <OleCtl.h>\n#include <QPdfDocument>\n#include <Shlwapi.h>\n#include <Windows.h>\n#include <ocidl.h>\n\n#include \"ApvlvAxOffice.h\"\n#include \"ApvlvUtil.h\"\n#include \"ApvlvWebViewWidget.h\"\n\nnamespace apvlv\n{\nusing namespace std;\n\nFILE_TYPE_DEFINITION (\"MSOffice\", ApvlvOfficeWord, { \".doc\", \".docx\" });\nFILE_TYPE_DEFINITION (\"MSOffice\", ApvlvPowerPoint, { \".ppt\", \".pptx\" });\nFILE_TYPE_DEFINITION (\"MSOffice\", ApvlvExcel, { \".xls\", \".xlsx\" });\n\nbool\nApvlvOfficeWord::load (const string &filename)\n{\n  auto qname = QString::fromLocal8Bit (filename);\n  mApp = new QAxWidget (\"Word.Application\");\n  mApp->setProperty (\"Visible\", false);\n  mDocs = mApp->querySubObject (\"Documents\");\n  mDoc = mDocs->querySubObject (\n      \"OpenNoRepairDialog(const QString&, bool, bool, bool)\", qname, false,\n      true, false);\n  if (mDoc == nullptr)\n    {\n      mApp->dynamicCall (\"Quit()\");\n      delete mApp;\n      return false;\n    }\n  return true;\n}\n\nint\nApvlvOfficeWord::sum ()\n{\n  auto content = mDoc->querySubObject (\"Content\");\n  auto pages\n      = content->dynamicCall (\"Information(wdNumberOfPagesInDocument)\");\n  return pages.toInt ();\n}\n\nSizeF\nApvlvOfficeWord::pageSizeF (int pn, int rot)\n{\n  auto pnstr = QString (\"Pages(%1)\").arg (pn + 1);\n  auto win = mDoc->querySubObject (\"ActiveWindow\");\n  auto pane = win->querySubObject (\"ActivePane\");\n  auto page = pane->querySubObject (pnstr.toStdString ().c_str ());\n  auto width = page->property (\"Width\");\n  auto height = page->property (\"Height\");\n  return { width.toDouble (), height.toDouble () };\n}\n\nbool\nApvlvOfficeWord::pageText (int pn, const Rectangle &rect, string &text)\n{\n  auto content = mDoc->querySubObject (\"Content\");\n  return false;\n}\n\nbool\nApvlvOfficeWord::pageRenderToImage (int pn, double zm, int rot, QImage *pix)\n{\n  char szFormatName[1024];\n  const char *lpFormatName;\n  static UINT auPriorityList[] = { CF_TEXT, CF_BITMAP };\n\n  auto gf = GetPriorityClipboardFormat (auPriorityList, 2);\n\n  auto pnstr = QString (\"Pages(%1)\").arg (pn + 1);\n  auto win = mDoc->querySubObject (\"ActiveWindow\");\n  auto pane = win->querySubObject (\"ActivePane\");\n  auto page = pane->querySubObject (pnstr.toStdString ().c_str ());\n\n  auto selection = mApp->querySubObject (\"Selection\");\n  selection->dynamicCall (\"GoTo(int, int, int, const QVariant&)\", 1, 1, 1,\n                          page->property (\"Start\"));\n  selection->dynamicCall (\"MoveDown(int, int, int)\", 5, 1, 0);\n  selection->dynamicCall (\"EndKey(int, int)\", 6, 1);\n  selection->dynamicCall (\"CopyAsPicture()\");\n\n  Sleep (1000);\n  auto clip = QApplication::clipboard ();\n  auto mime = clip->mimeData ();\n  for (auto const &t : mime->formats ())\n    {\n      qDebug () << \"clipboard contains: \" << t;\n    }\n\n  // HWND hWnd = (HWND)mApp->winId ();\n  if (OpenClipboard (NULL) == FALSE)\n    {\n      qDebug (\"open clipboard error: %d\\n\", GetLastError ());\n      return false;\n    }\n\n  auto uFormat = EnumClipboardFormats (0);\n  while (uFormat)\n    {\n      if (GetClipboardFormatNameA (uFormat, szFormatName,\n                                   sizeof (szFormatName)))\n        lpFormatName = szFormatName;\n      else\n        lpFormatName = \"(unknown)\";\n      qDebug (\"get file: %s\\n\", lpFormatName);\n      uFormat = EnumClipboardFormats (uFormat);\n    }\n\n  if (IsClipboardFormatAvailable (CF_BITMAP) == FALSE)\n    {\n      qDebug (\"no picture: %d\\n\", GetLastError ());\n      CloseClipboard ();\n      return false;\n    }\n\n  HBITMAP hBitmap = static_cast<HBITMAP> (GetClipboardData (CF_BITMAP));\n  if (hBitmap == nullptr)\n    {\n      qDebug (\"no image: %d\\n\", GetLastError ());\n      CloseClipboard ();\n      return false;\n    }\n  IPicture *iPicture = nullptr;\n  auto hr = OleCreatePictureIndirect (reinterpret_cast<LPPICTDESC> (hBitmap),\n                                      IID_IPicture, TRUE, (void **)&iPicture);\n  if (FAILED (hr))\n    {\n      qDebug (\"failed create: %d\\n\", GetLastError ());\n      CloseClipboard ();\n      return false;\n    }\n  LPSTREAM stream = NULL;\n  hr = SHCreateStreamOnFileA (\"z:\\\\a.bmp\", STGM_CREATE | STGM_WRITE, &stream);\n  if (FAILED (hr))\n    {\n      iPicture->Release ();\n      CloseClipboard ();\n      return false;\n    }\n\n  hr = iPicture->SaveAsFile (stream, FALSE, NULL);\n  iPicture->Release ();\n  CloseClipboard ();\n  if (FAILED (hr))\n    {\n      qDebug (\"save failed\\n\");\n      return false;\n    }\n\n  return true;\n}\n\nbool\nApvlvOfficeWord::pageRenderToWebView (int pn, double zm, int rot,\n                                      WebView *webview)\n{\n  webview->setZoomFactor (zm);\n  QUrl url = QString (\"apvlv:///%1\").arg (pn);\n  webview->load (url);\n  return true;\n}\n\noptional<QByteArray>\nApvlvOfficeWord::pathContent (const string &path)\n{\n  auto pn = QString::fromLocal8Bit (path).toInt ();\n  auto pageRange = mDoc->querySubObject (\n      \"GoTo(int, int, int, const QVariant&)\", 1, 1, pn + 1);\n  auto endRange = mDoc->querySubObject (\n      \"GoTo(int, int, int, const QVariant&)\", 1, 1, pn + 2);\n  if (pageRange && endRange)\n    {\n      int endPosition;\n      if (endRange->property (\"Start\").toInt () == 1)\n        {\n          auto content = mDoc->querySubObject (\"Content\");\n          endPosition = content->property (\"End\").toInt ();\n          delete content;\n        }\n      else\n        {\n          endPosition = endRange->property (\"Start\").toInt () - 1;\n        }\n\n      pageRange->setProperty (\"End\", endPosition);\n\n      pageRange->dynamicCall (\"Copy()\");\n    }\n  else\n    {\n      qWarning () << \"Failed to get page range\";\n      return nullptr;\n    }\n\n  auto clip = QApplication::clipboard ();\n  auto mime = clip->mimeData ();\n  return QByteArray::fromStdString (mime->html ().toStdString ());\n}\n\nbool\nApvlvPowerPoint::load (const string &filename)\n{\n  auto qname = QString::fromLocal8Bit (filename);\n  mApp = new QAxWidget (\"PowerPoint.Application\");\n  mApp->setProperty (\"Visible\", false);\n  mDocs = mApp->querySubObject (\"Presentations\");\n  mDoc = mDocs->querySubObject (\"Open(const QString&, bool, bool, bool)\",\n                                qname, true, false, false);\n  if (mDoc == nullptr)\n    {\n      mApp->dynamicCall (\"Quit()\");\n      delete mApp;\n      return false;\n    }\n  return true;\n}\n\nint\nApvlvPowerPoint::sum ()\n{\n  auto slides = mDoc->querySubObject (\"Slides\");\n  auto count = slides->property (\"Count\");\n  return count.toInt ();\n}\n\nSizeF\nApvlvPowerPoint::pageSizeF (int pn, int rot)\n{\n  auto page = mDoc->querySubObject (\"PageSetup\");\n  auto width = page->property (\"SlideWidth\");\n  auto height = page->property (\"SlideHeight\");\n  return { width.toDouble (), height.toDouble () };\n}\n\nbool\nApvlvPowerPoint::pageText (int pn, const Rectangle &rect, string &text)\n{\n  auto content = mDoc->querySubObject (\"Content\");\n  return false;\n}\n\nbool\nApvlvPowerPoint::pageRenderToImage (int pn, double zm, int rot, QImage *pix)\n{\n  auto slides = mDoc->querySubObject (\"Slides\");\n  auto slide = slides->querySubObject (\"Item(int)\", pn + 1);\n  auto temppath\n      = QString (\"%1/apvlv_%2.png\").arg (QDir::tempPath ()).arg (rand ());\n  temppath.replace (\"/\", \"\\\\\");\n  slide->dynamicCall (\"Export(const QString &, const QString &)\", temppath,\n                      QString (\"PNG\"));\n\n  if (QFile::exists (temppath) == false)\n    {\n      return false;\n    }\n  *pix = QImage (temppath);\n  QFile::remove (temppath);\n  return true;\n}\n\nExcelWidget *\nApvlvExcel::getWidget ()\n{\n  auto wid = new ExcelWidget ();\n  wid->setFile (this);\n  return wid;\n}\n\nvoid\nExcelWidget::setFile (File *file)\n{\n  mFile = file;\n  auto qname = QString::fromLocal8Bit (mFile->getFilename ());\n  mAxWidget.setControl (qname);\n}\n\nvoid\nExcelWidget::showPage (int p, double s)\n{\n  auto sheets = mAxWidget.querySubObject (\"Sheets\");\n  auto sheet = sheets->querySubObject (\"Item(int)\", p + 1);\n  sheet->dynamicCall (\"Activate()\");\n  mPageNumber = p;\n  mScrollValue = s;\n}\n\nvoid\nExcelWidget::showPage (int p, const string &anchor)\n{\n  showPage (p, 0.0);\n  mPageNumber = p;\n  mScrollValue = 0;\n}\n\nbool\nApvlvExcel::load (const string &filename)\n{\n  auto qname = QString::fromLocal8Bit (filename);\n  mApp = new QAxWidget (\"Excel.Workbook\");\n  mApp->setProperty (\"Visible\", false);\n  mApp->setProperty (\"ReadOnly\", true);\n  mApp->setControl (qname);\n  mDoc = nullptr;\n  return true;\n}\n\nint\nApvlvExcel::sum ()\n{\n  auto sheets = mApp->querySubObject (\"Sheets\");\n  auto count = sheets->property (\"Count\");\n  return count.toInt ();\n}\n\nbool\nApvlvExcel::pageText (int pn, const Rectangle &rect, string &text)\n{\n  return false;\n  // auto content = mDoc->querySubObject (\"Content\");\n}\n\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/file/ApvlvAxOffice.h",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvOffice.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_AXOFFICE_H_\n#define _APVLV_AXOFFICE_H_\n\n#include <QtAxContainer>\n\n#include \"ApvlvFile.h\"\n#include \"ApvlvFileWidget.h\"\n\nnamespace apvlv\n{\nclass AxOffice\n{\npublic:\n  virtual ~AxOffice ()\n  {\n    if (mDoc)\n      mDoc->dynamicCall (\"Close()\");\n    if (mApp)\n      mApp->dynamicCall (\"Quit()\");\n    delete mDoc;\n    delete mApp;\n  }\n\nprotected:\n  QAxWidget *mApp;\n  QAxObject *mDocs;\n  QAxObject *mDoc;\n};\n\nclass ApvlvOfficeWord : public File, public AxOffice\n{\n  FILE_TYPE_DECLARATION (ApvlvOfficeWord);\n\npublic:\n  bool load (const std::string &filename) override;\n\n  int sum () override;\n\n  SizeF pageSizeF (int page, int rot) override;\n\n  bool pageText (int pn, const Rectangle &rect, std::string &text) override;\n\n  bool pageRenderToImage (int pn, double zm, int rot, QImage *pix) override;\n\n  bool pageRenderToWebView (int pn, double zm, int rot,\n                            WebView *webview) override;\n\n  std::optional<QByteArray> pathContent (const std::string &path) override;\n};\n\nclass ApvlvPowerPoint : public File, public AxOffice\n{\n  FILE_TYPE_DECLARATION (ApvlvPowerPoint);\n\npublic:\n  bool load (const std::string &filename) override;\n\n  int sum () override;\n\n  SizeF pageSizeF (int page, int rot) override;\n\n  bool pageText (int pn, const Rectangle &rect, std::string &text) override;\n\n  bool pageRenderToImage (int pn, double zm, int rot, QImage *pix) override;\n};\n\nclass ExcelWidget : public FileWidget\n{\npublic:\n  ExcelWidget ()\n  {\n    mAxWidget.setProperty (\"Visible\", true);\n    mAxWidget.setProperty (\"ReadOnly\", true);\n  }\n\n  [[nodiscard]] QWidget *\n  widget () override\n  {\n    return &mAxWidget;\n  }\n\n  void setFile (File *file) override;\n\n  void showPage (int, double s) override;\n  void showPage (int, const std::string &anchor) override;\n\nprivate:\n  QAxWidget mAxWidget{ \"Excel.Workbook\" };\n};\n\nclass ApvlvExcel : public File, public AxOffice\n{\n  FILE_TYPE_DECLARATION (ApvlvExcel);\n\npublic:\n  bool load (const std::string &filename) override;\n\n  [[nodiscard]] virtual DISPLAY_TYPE\n  getDisplayType () const override\n  {\n    return DISPLAY_TYPE::CUSTOM;\n  }\n\n  ExcelWidget *getWidget () override;\n\n  int sum () override;\n\n  bool pageText (int pn, const Rectangle &rect, std::string &text) override;\n};\n\n}\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/file/ApvlvDjvu.cc",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvDjvu.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QDebug>\n\n#include \"ApvlvDjvu.h\"\n#include \"ApvlvUtil.h\"\n\nnamespace apvlv\n{\nusing namespace std;\n\nvoid\nhandleDdjvuMessages (ddjvu_context_t *ctx, int wait)\n{\n  const ddjvu_message_t *msg;\n  if (wait)\n    ddjvu_message_wait (ctx);\n  while ((msg = ddjvu_message_peek (ctx)))\n    {\n      qDebug () << \"tag: \" << msg->m_any.tag;\n      switch (msg->m_any.tag)\n        {\n        case DDJVU_ERROR:\n          break;\n        case DDJVU_INFO:\n          break;\n        case DDJVU_PAGEINFO:\n          break;\n        default:\n          break;\n        }\n      ddjvu_message_pop (ctx);\n    }\n}\n\nFILE_TYPE_DEFINITION (\"djvulibre\", ApvlvDJVU, { \".djv\", \".djvu\" });\n\nbool\nApvlvDJVU::load (const string &filename)\n{\n  mContext = ddjvu_context_create (\"apvlv\");\n  if (mContext == nullptr)\n    {\n      qCritical () << \"djvu context error\";\n      return false;\n    }\n\n  mDoc = ddjvu_document_create_by_filename (mContext, filename.c_str (),\n                                            false);\n  if (mDoc == nullptr)\n    {\n      qCritical (\"djvu create document error\");\n      ddjvu_context_release (mContext);\n      mContext = nullptr;\n      return false;\n    }\n\n  if (ddjvu_document_get_type (mDoc) != DDJVU_DOCTYPE_SINGLEPAGE)\n    {\n      qCritical () << \"djvu type: \" << ddjvu_document_get_type (mDoc);\n      ddjvu_document_release (mDoc);\n      mDoc = nullptr;\n      ddjvu_context_release (mContext);\n      mContext = nullptr;\n      return false;\n    }\n\n  return true;\n}\n\nApvlvDJVU::~ApvlvDJVU ()\n{\n  if (mContext)\n    {\n      ddjvu_context_release (mContext);\n    }\n\n  if (mDoc)\n    {\n      ddjvu_document_release (mDoc);\n    }\n}\n\nSizeF\nApvlvDJVU::pageSizeF (int pn, int rot)\n{\n  ddjvu_status_t t;\n  ddjvu_pageinfo_t info;\n  while ((t = ddjvu_document_get_pageinfo (mDoc, 0, &info)) < DDJVU_JOB_OK)\n    {\n      handleDdjvuMessages (mContext, true);\n    }\n\n  SizeF sizef{ 0.0f, 0.0f };\n  if (t == DDJVU_JOB_OK)\n    {\n      sizef.width = static_cast<double> (info.width);\n      sizef.height = static_cast<double> (info.height);\n    }\n  return sizef;\n}\n\nint\nApvlvDJVU::sum ()\n{\n  return mDoc ? ddjvu_document_get_pagenum (mDoc) : 0;\n}\n\nbool\nApvlvDJVU::pageRenderToImage (int pn, double zm, int rot, QImage *pix)\n{\n  ddjvu_page_t *tpage;\n\n  if ((tpage = ddjvu_page_create_by_pageno (mDoc, pn)) == nullptr)\n    {\n      qDebug () << \"no this page: \" << pn;\n      return false;\n    }\n\n  auto size = pageSizeF (pn, rot);\n\n  auto dx = size.width * zm;\n  auto dy = size.height * zm;\n\n  auto ix = static_cast<int> (dx);\n  auto iy = static_cast<int> (dy);\n  ddjvu_rect_t prect = { 0, 0, static_cast<unsigned int> (ix),\n                         static_cast<unsigned int> (iy) };\n  ddjvu_rect_t rrect = { 0, 0, static_cast<unsigned int> (ix),\n                         static_cast<unsigned int> (iy) };\n  ddjvu_format_t *format\n      = ddjvu_format_create (DDJVU_FORMAT_RGB24, 0, nullptr);\n  ddjvu_format_set_row_order (format, true);\n\n  auto psize = 3 * ix * iy;\n  auto buffer = make_unique<char[]> (psize);\n\n  int retry = 0;\n  while (retry <= 20\n         && ddjvu_page_render (tpage, DDJVU_RENDER_COLOR, &prect, &rrect,\n                               format, 3 * ix, buffer.get ())\n                == false)\n    {\n      this_thread::sleep_for (50ms);\n      ++retry;\n      qDebug () << \"fender failed, retry \" << retry;\n    }\n\n  auto image = QImage (ix, iy, QImage::Format_RGB888);\n  for (auto y = 0; y < iy; ++y)\n    {\n      for (auto x = 0; x < ix; ++x)\n        {\n          auto rgb = buffer.get () + 3 * (x + y * ix);\n          image.setPixel (x, y, qRgb (rgb[0], rgb[1], rgb[2]));\n        }\n    }\n  *pix = std::move (image);\n\n  return true;\n}\n\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/file/ApvlvDjvu.h",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvDjvu.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_DJVU_H_\n#define _APVLV_DJVU_H_\n\n#include <libdjvu/ddjvuapi.h>\n\n#include \"ApvlvFile.h\"\n\nnamespace apvlv\n{\nclass ApvlvDJVU : public File\n{\n  FILE_TYPE_DECLARATION (ApvlvDJVU);\n\npublic:\n  bool load (const std::string &filename) override;\n\n  ~ApvlvDJVU () override;\n\n  SizeF pageSizeF (int page, int rot) override;\n\n  int sum () override;\n\n  bool pageRenderToImage (int pn, double zm, int rot, QImage *img) override;\n\nprivate:\n  ddjvu_context_t *mContext;\n  ddjvu_document_t *mDoc;\n};\n}\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/file/ApvlvEpub.cc",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvHtm.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QDomDocument>\n#include <QXmlStreamReader>\n#include <filesystem>\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wdeprecated-declarations\"\n#include <quazipfile.h>\n#pragma GCC diagnostic pop\n\n#include \"ApvlvEpub.h\"\n#include \"ApvlvUtil.h\"\n#include \"ApvlvWebViewWidget.h\"\n\nnamespace apvlv\n{\nFILE_TYPE_DEFINITION (\"Web\", ApvlvEPUB, { \".epub\" });\n\nusing namespace std;\n\nbool\nApvlvEPUB::load (const string &filename)\n{\n  mQuaZip = make_unique<QuaZip> (QString::fromLocal8Bit (filename));\n  if (mQuaZip->open (QuaZip::mdUnzip) == false)\n    {\n      return false;\n    }\n\n  auto filenames = mQuaZip->getFileNameList ();\n  if (!filenames.contains (\"META-INF/container.xml\"))\n    {\n      return false;\n    }\n\n  auto optcontainer = getZipFileContents (\"META-INF/container.xml\");\n  if (!optcontainer)\n    {\n      return false;\n    }\n\n  string contentfile = containerGetContentfile (optcontainer->constData (),\n                                                optcontainer->length ());\n  if (contentfile.empty ())\n    {\n      return false;\n    }\n\n  if (contentGetMedia (contentfile) == false)\n    {\n      return false;\n    }\n\n  ncxSetIndex (idSrcs[\"ncx\"]);\n  return true;\n}\n\nApvlvEPUB::~ApvlvEPUB ()\n{\n  mQuaZip->close ();\n}\n\nint\nApvlvEPUB::sum ()\n{\n  return int (mPages.size ());\n}\n\nbool\nApvlvEPUB::pageRenderToWebView (int pn, double zm, int rot, WebView *webview)\n{\n  webview->setZoomFactor (zm);\n  QUrl epuburi = QString (\"apvlv:///\") + QString::fromLocal8Bit (mPages[pn]);\n  webview->load (epuburi);\n  return true;\n}\n\nunique_ptr<WordListRectangle>\nApvlvEPUB::pageSearch (int pn, const char *s)\n{\n  auto qpath = QString::fromLocal8Bit (mPages[pn]);\n  auto content = getZipFileContents (qpath);\n  auto html = content->toStdString ();\n  auto pos = html.find (s);\n  if (pos == string::npos)\n    return nullptr;\n\n  auto wordlist = make_unique<WordListRectangle> ();\n  do\n    {\n      WordRectangle word{};\n      word.word = s;\n      wordlist->push_back (word);\n      pos = html.find (s, pos + 1);\n    }\n  while (pos != string::npos);\n\n  return wordlist;\n}\n\noptional<QByteArray>\nApvlvEPUB::pathContent (const string &path)\n{\n  auto optcontent = getZipFileContents (QString::fromLocal8Bit (path));\n  return optcontent;\n}\n\noptional<QByteArray>\nApvlvEPUB::getZipFileContents (const QString &name)\n{\n  if (mQuaZip->setCurrentFile (name) == false)\n    return nullopt;\n\n  auto zipfile = make_unique<QuaZipFile> (mQuaZip.get ());\n  zipfile->open (QIODevice::ReadOnly);\n  auto qarray = zipfile->readAll ();\n  zipfile->close ();\n  return qarray;\n}\n\nstring\nApvlvEPUB::containerGetContentfile (const char *container, int len)\n{\n  vector<string> names{ \"container\", \"rootfiles\", \"rootfile\" };\n  return xmlContentGetAttributeValue (container, len, names, \"full-path\");\n}\n\nbool\nApvlvEPUB::contentGetMedia (const string &contentfile)\n{\n  string cover_id = \"cover\";\n\n  auto optcontent = getZipFileContents (QString::fromLocal8Bit (contentfile));\n  if (!optcontent)\n    {\n      return false;\n    }\n\n  vector<string> metas{ \"package\", \"metadata\" };\n  auto optcover = xmlContentGetElement (optcontent->constData (),\n                                        optcontent->length (), metas);\n  if (optcover)\n    {\n      auto xml = optcover->get ();\n      while (!xml->atEnd ()\n             && !(xml->isEndElement ()\n                  && xml->name ().toString () == \"metadata\"))\n        {\n          xml->readNext ();\n          if (xml->isStartElement () && xml->name ().toString () == \"meta\")\n            {\n              auto attrs = xml->attributes ();\n              for (auto const &attr : attrs)\n                {\n                  if (attr.name ().toString () == \"name\"\n                      && attr.value ().toString () == \"cover\")\n                    {\n                      cover_id = xml->attributes ()\n                                     .value (\"content\")\n                                     .toString ()\n                                     .toStdString ();\n                    }\n                }\n            }\n        }\n    }\n\n  vector<string> items = { \"package\", \"manifest\", \"item\" };\n  auto optxml = xmlContentGetElement (optcontent->constData (),\n                                      optcontent->length (), items);\n  if (!optxml)\n    return false;\n\n  auto xml = optxml->get ();\n  while (!xml->atEnd () && xml->name ().toString () == \"item\")\n    {\n      if (xml->isStartElement ())\n        {\n          string href = xmlStreamGetAttributeValue (xml, \"href\");\n          if (href.empty ())\n            {\n              xml->readNextStartElement ();\n              continue;\n            }\n\n          if (contentfile.rfind ('/') != string::npos)\n            {\n              string dirname\n                  = contentfile.substr (0, contentfile.rfind ('/'));\n              href = dirname + \"/\" + href;\n            }\n\n          string id = xmlStreamGetAttributeValue (xml, \"id\");\n          string type = xmlStreamGetAttributeValue (xml, \"media-type\");\n          if (id == cover_id)\n            {\n              idSrcs[\"cover\"] = href;\n              srcMimeTypes[href] = type;\n            }\n          else\n            {\n              idSrcs[id] = href;\n              srcMimeTypes[href] = type;\n            }\n        }\n\n      xml->readNextStartElement ();\n    }\n\n  vector<string> names{ \"package\", \"spine\", \"itemref\" };\n  optxml = xmlContentGetElement (optcontent->constData (),\n                                 optcontent->length (), names);\n  if (!optxml)\n    return false;\n\n  xml = optxml->get ();\n  while (!xml->atEnd () && xml->name ().toString () == \"itemref\")\n    {\n      if (xml->isStartElement ())\n        {\n          string id = xmlStreamGetAttributeValue (xml, \"idref\");\n          mPages.push_back (idSrcs[id]);\n          srcPages[idSrcs[id]] = static_cast<int> (mPages.size () - 1);\n        }\n      xml->readNextStartElement ();\n    }\n\n  return true;\n}\n\nbool\nApvlvEPUB::ncxSetIndex (const string &ncxfile)\n{\n  auto opttoc = getZipFileContents (QString::fromLocal8Bit (ncxfile));\n  if (!opttoc)\n    {\n      return false;\n    }\n\n  vector<string> names{ \"ncx\", \"navMap\" };\n  auto optxml\n      = xmlContentGetElement (opttoc->constData (), opttoc->length (), names);\n\n  if (!optxml)\n    {\n      return false;\n    }\n\n  mIndex = { \"__cover__\", 0, getFilename (), FileIndexType::FILE };\n\n  auto xml = optxml->get ();\n  ncxNodeSetIndex (xml, \"navMap\", ncxfile, mIndex);\n\n  return true;\n}\n\nvoid\nApvlvEPUB::ncxNodeSetIndex (QXmlStreamReader *xml, const string &element_name,\n                            const string &ncxfile, FileIndex &index)\n{\n  while (!xml->atEnd ()\n         && !(xml->isEndElement ()\n              && xml->name ().toString ().toStdString () == element_name))\n    {\n      if (xml->isStartElement ())\n        {\n          if (xml->name ().toString () == \"navLabel\")\n            {\n              while (!(xml->isEndElement ()\n                       && xml->name ().toString () == \"navLabel\"))\n                {\n                  xml->readNextStartElement ();\n                  if (xml->name ().toString () == \"text\")\n                    {\n                      auto text = xml->readElementText (\n                          QXmlStreamReader::ReadElementTextBehaviour::\n                              SkipChildElements);\n                      index.title = text.toStdString ();\n                      break;\n                    }\n                }\n\n              xml->readNextStartElement ();\n            }\n\n          if (xml->name ().toString () == \"content\")\n            {\n              string srcstr = xmlStreamGetAttributeValue (xml, \"src\");\n              if (srcstr.empty ())\n                continue;\n\n              if (ncxfile.find ('/') != string::npos)\n                {\n                  auto ncxdir\n                      = filesystem::path (ncxfile).parent_path ().string ();\n                  srcstr = string (ncxdir) + '/' + srcstr;\n                }\n\n              index.path = srcstr;\n\n              auto href = srcstr;\n              if (srcstr.find ('#') != string::npos)\n                {\n                  index.anchor = srcstr.substr (srcstr.find ('#'));\n                  href = srcstr.substr (0, srcstr.find ('#'));\n                }\n\n              for (decltype (mPages.size ()) ind = 0; ind < mPages.size ();\n                   ++ind)\n                {\n                  if (mPages[ind] == href)\n                    {\n                      index.page = int (ind);\n                      break;\n                    }\n                }\n\n              xml->readNextStartElement ();\n            }\n\n          if (xml->name ().toString () == \"navPoint\")\n            {\n              xml->readNextStartElement ();\n              auto childindex = FileIndex{};\n              ncxNodeSetIndex (xml, \"navPoint\", ncxfile, childindex);\n              index.mChildrenIndex.emplace_back (childindex);\n            }\n        }\n\n      xml->readNextStartElement ();\n    }\n}\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/file/ApvlvEpub.h",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvEpub.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_EPUB_H_\n#define _APVLV_EPUB_H_\n\n#include <QXmlStreamReader>\n#include <map>\n#include <memory>\n#include <quazip.h>\n#include <string>\n\n#include \"ApvlvFile.h\"\n\nnamespace apvlv\n{\nclass ApvlvEPUB : public File\n{\n  FILE_TYPE_DECLARATION (ApvlvEPUB);\n\npublic:\n  bool load (const std::string &filename) override;\n  ~ApvlvEPUB () override;\n\n  int sum () override;\n\n  bool pageRenderToWebView (int pn, double zm, int rot,\n                            WebView *widget) override;\n\n  std::unique_ptr<WordListRectangle> pageSearch (int pn,\n                                                 const char *s) override;\n\n  std::optional<QByteArray> pathContent (const std::string &path) override;\n\nprivate:\n  std::optional<QByteArray> getZipFileContents (const QString &name);\n\n  static std::string containerGetContentfile (const char *container, int len);\n\n  bool contentGetMedia (const std::string &contentfile);\n\n  bool ncxSetIndex (const std::string &ncxfile);\n\n  void ncxNodeSetIndex (QXmlStreamReader *xml,\n                        const std::string &element_name,\n                        const std::string &ncxfile, FileIndex &index);\n\n  std::unique_ptr<QuaZip> mQuaZip;\n  std::map<std::string, std::string> idSrcs;\n};\n\n}\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/file/ApvlvFb2.cc",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvFb2.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QFile>\n#include <cmath>\n#include <sstream>\n\n#include \"ApvlvFb2.h\"\n#include \"ApvlvUtil.h\"\n#include \"ApvlvWebViewWidget.h\"\n\nnamespace apvlv\n{\n\nusing namespace std;\n\nconst string stylesheet_content = \".block_c {\\n\"\n                                  \"  display: block;\\n\"\n                                  \"  font-size: 2.5em;\\n\"\n                                  \"  font-weight: normal;\\n\"\n                                  \"  line-height: 33.6pt;\\n\"\n                                  \"  text-align: center;\\n\"\n                                  \"  text-indent: 0;\\n\"\n                                  \"  margin: 17pt 0;\\n\"\n                                  \"  padding: 0;\\n\"\n                                  \"}\\n\"\n                                  \".block_ {\\n\"\n                                  \"  display: block;\\n\"\n                                  \"  font-size: 1.5em;\\n\"\n                                  \"  font-weight: normal;\\n\"\n                                  \"  line-height: 33.6pt;\\n\"\n                                  \"  text-align: justify;\\n\"\n                                  \"  text-indent: 0;\\n\"\n                                  \"  margin: 17pt 0;\\n\"\n                                  \"  padding: 0;\\n\"\n                                  \"}\\n\"\n                                  \".block_1 {\\n\"\n                                  \"  display: block;\\n\"\n                                  \"  line-height: 1.2;\\n\"\n                                  \"  text-align: justify;\\n\"\n                                  \"  margin: 0 0 7pt;\\n\"\n                                  \"  padding: 0;\\n\"\n                                  \"}\\n\";\nconst string title_template\n    = \"<?xml version='1.0' encoding='UTF-8'?>\\n\"\n      \"<html xmlns=\\\"http://www.w3.org/1999/xhtml\\\" \"\n      \"lang=\\\"en\\\" xml:lang=\\\"en\\\">\\n\"\n      \"  <head>\\n\"\n      \"    <title></title>\\n\"\n      \"    <link rel=\\\"stylesheet\\\" type=\\\"text/css\\\" \"\n      \"href=\\\"stylesheet.css\\\"/>\\n\"\n      \"    <meta http-equiv=\\\"Content-Type\\\" \"\n      \"content=\\\"text/html; charset=utf-8\\\"/>\\n\"\n      \"  </head>\\n\"\n      \"  <body>\\n\"\n      \"  <br />\\n\"\n      \"  <br />\\n\"\n      \"  <br />\\n\"\n      \"  <br />\\n\"\n      \"    %s\\n\"\n      \"  </body>\\n\"\n      \"</html>\\n\";\nconst string section_template\n    = \"<?xml version='1.0' encoding='UTF-8'?>\\n\"\n      \"<html xmlns=\\\"http://www.w3.org/1999/xhtml\\\" \"\n      \"lang=\\\"en\\\" xml:lang=\\\"en\\\">\\n\"\n      \"  <head>\\n\"\n      \"    <title></title>\\n\"\n      \"    <link rel=\\\"stylesheet\\\" \"\n      \"type=\\\"text/css\\\" href=\\\"stylesheet.css\\\"/>\\n\"\n      \"    <meta http-equiv=\\\"Content-Type\\\" \"\n      \"content=\\\"text/html; charset=utf-8\\\"/>\\n\"\n      \"  </head>\\n\"\n      \"  <body>\\n\"\n      \"    %s\\n\"\n      \"  </body>\\n\"\n      \"</html>\\n\";\n\nFILE_TYPE_DEFINITION (\"Web\", ApvlvFB2, { \".fb2\" });\n\nbool\nApvlvFB2::load (const string &filename)\n{\n  QFile file (QString::fromLocal8Bit (filename));\n  if (!file.open (QFile::ReadOnly | QFile::Text))\n    {\n      return false;\n    }\n\n  auto bytes = file.readAll ();\n  parseFb2 (bytes.constData (), bytes.length ());\n  return true;\n}\n\nbool\nApvlvFB2::parseFb2 (const char *content, size_t length)\n{\n  parseDescription (content, length);\n  parseBinary (content, length);\n  parseBody (content, length);\n\n  generateIndex ();\n\n  return true;\n}\n\nbool\nApvlvFB2::parseDescription (const char *content, size_t length)\n{\n  vector<string> keys{ \"FictionBook\", \"description\", \"title-info\",\n                       \"coverpage\", \"image\" };\n  auto value = xmlContentGetAttributeValue (content, length, keys, \"href\");\n  mCoverHref = value;\n  return true;\n}\n\nbool\nApvlvFB2::parseBody (const char *content, size_t length)\n{\n  vector<string> keys = { \"FictionBook\", \"body\" };\n  auto optxml = xmlContentGetElement (content, length, keys);\n  if (!optxml)\n    return false;\n\n  auto xml = optxml->get ();\n  while (!xml->atEnd ()\n         && !(xml->isEndElement () && xml->name ().toString () == \"body\"))\n    {\n      if (xml->isStartElement () && xml->name () == QString (\"title\"))\n        {\n          stringstream ss;\n          while (!xml->atEnd ()\n                 && !(xml->isEndElement ()\n                      && xml->name ().toString () == \"title\"))\n            {\n              if (xml->isStartElement ())\n                {\n                  if (xml->name () == QString (\"empty-line\"))\n                    {\n                      ss << \"<br />\";\n                    }\n                  else if (xml->name () == QString (\"p\"))\n                    {\n                      ss << \"<h1 class=\\\"block_c\\\"><span>\";\n                      auto xmltext = xml->readElementText ().trimmed ();\n                      ss << xmltext.toStdString ();\n                      ss << \"</span></h1>\";\n                      ss << \"<br />\";\n                    }\n                }\n\n              xml->readNext ();\n            }\n\n          auto htmlstr = templateBuild (title_template, \"%s\", ss.str ());\n          appendTitle (htmlstr, \"application/xhtml+xml\");\n        }\n      else if (xml->isStartElement ()\n               && xml->name ().toString () == \"section\")\n        {\n          stringstream ss;\n          string title;\n          while (!xml->atEnd ()\n                 && !(xml->isEndElement ()\n                      && xml->name ().toString () == \"section\"))\n            {\n              if (xml->isStartElement ())\n                {\n                  if (xml->name ().toString () == \"title\")\n                    {\n                      auto xmltext\n                          = xml->readElementText (\n                                   QXmlStreamReader::IncludeChildElements)\n                                .trimmed ();\n                      title = xmltext.toStdString ();\n                      ss << \"<h1 class=\\\"block_\\\"><span>\";\n                      ss << title;\n                      ss << \"</span></h1>\";\n                      ss << \"<br />\";\n                    }\n                  else if (xml->name ().toString () == \"p\")\n                    {\n                      ss << \"<p class=\\\"block_1\\\"><span>\";\n                      auto xmltext = xml->readElementText ().trimmed ();\n                      ss << xmltext.toStdString ();\n                      ss << \"</span></p>\";\n                    }\n                }\n\n              xml->readNext ();\n            }\n\n          auto htmlstr = templateBuild (section_template, \"%s\", ss.str ());\n          appendSection (title, htmlstr, \"application/xhtml+xml\");\n        }\n\n      xml->readNext ();\n    }\n\n  return true;\n}\n\nbool\nApvlvFB2::parseBinary (const char *content, size_t length)\n{\n  string idstr;\n  vector<string> keys = { \"FictionBook\", \"binary\" };\n  auto optxml = xmlContentGetElement (content, length, keys);\n  if (!optxml)\n    return false;\n\n  auto xml = optxml->get ();\n  idstr = xmlStreamGetAttributeValue (xml, \"id\");\n\n  if (mCoverHref.empty () || idstr == mCoverHref.substr (1))\n    {\n      string mimetype = xmlStreamGetAttributeValue (xml, \"content-type\");\n      auto contents = xml->readElementText ().toStdString ();\n      QByteArray b64contents{ contents.c_str (),\n                              (qsizetype)contents.length () };\n      auto bytes = QByteArray::fromBase64 (b64contents);\n      auto section = bytes.toStdString ();\n      appendCoverpage (section, mimetype);\n    }\n\n  return true;\n}\n\nvoid\nApvlvFB2::appendCoverpage (const string &section, const string &mime)\n{\n  appendSection (\"__cover__\", section, mime);\n}\n\nvoid\nApvlvFB2::appendTitle (const string &section, const string &mime)\n{\n  appendSection (\"TITLE\", section, mime);\n}\n\nvoid\nApvlvFB2::appendSection (const string &title, const string &section,\n                         const string &mime)\n{\n  stringstream uri;\n  uri << mPages.size ();\n  appendPage (uri.str (), title, section, mime);\n}\n\nvoid\nApvlvFB2::appendPage (const string &uri, const string &title,\n                      const string &section, const string &mime)\n{\n  srcPages[uri] = (int)mPages.size ();\n  mPages.push_back (uri);\n  titleSections.insert ({ uri, { title, section } });\n  srcMimeTypes.insert ({ uri, mime });\n}\n\nbool\nApvlvFB2::generateIndex ()\n{\n  stringstream pagenum;\n\n  mIndex = { \"\", 0, getFilename (), FileIndexType::FILE };\n  for (int ind = 0; ind < (int)mPages.size (); ++ind)\n    {\n      pagenum << ind;\n      if (mPages[ind] == \"__cover__\")\n        continue;\n\n      auto title = titleSections[mPages[ind]].first;\n      auto chap = FileIndex (title, ind, pagenum.str (), FileIndexType::PAGE);\n      mIndex.mChildrenIndex.emplace_back (chap);\n    }\n\n  return true;\n}\n\nint\nApvlvFB2::sum ()\n{\n  return (int)mPages.size ();\n}\n\nbool\nApvlvFB2::pageRenderToWebView (int pn, double zm, int rot, WebView *webview)\n{\n  webview->setZoomFactor (zm);\n  QUrl url = QString (\"apvlv:///\") + QString::number (pn);\n  webview->load (url);\n  return true;\n}\n\noptional<QByteArray>\nApvlvFB2::pathContent (const string &uri)\n{\n  if (uri == \"stylesheet.css\")\n    {\n      auto byte_array = QByteArray::fromStdString (stylesheet_content);\n      return byte_array;\n    }\n\n  auto byte_array = QByteArray::fromStdString (titleSections[uri].second);\n  return byte_array;\n}\n\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/file/ApvlvFb2.h",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvFb2.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_FB2_H_\n#define _APVLV_FB2_H_\n\n#include <QXmlStreamReader>\n\n#include \"ApvlvFile.h\"\n\nnamespace apvlv\n{\nclass ApvlvFB2 : public File\n{\n  FILE_TYPE_DECLARATION (ApvlvFB2);\n\npublic:\n  bool load (const std::string &filename) override;\n\n  ~ApvlvFB2 () override = default;\n\n  int sum () override;\n\n  bool pageRenderToWebView (int pn, double zm, int rot,\n                            WebView *webview) override;\n\n  std::optional<QByteArray> pathContent (const std::string &path) override;\n\nprivate:\n  std::map<std::string, std::pair<std::string, std::string>> titleSections;\n  std::string mCoverHref;\n\n  bool parseFb2 (const char *content, size_t length);\n  bool parseDescription (const char *content, size_t length);\n  bool parseBody (const char *content, size_t length);\n  bool parseBinary (const char *content, size_t length);\n  void appendCoverpage (const std::string &section, const std::string &mime);\n  void appendTitle (const std::string &section, const std::string &mime);\n  void appendSection (const std::string &title, const std::string &section,\n                      const std::string &mime);\n  void appendPage (const std::string &uri, const std::string &title,\n                   const std::string &section, const std::string &mime);\n  bool generateIndex ();\n};\n\n}\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/file/ApvlvHtm.cc",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvHtm.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include \"ApvlvHtm.h\"\n#include \"ApvlvWebViewWidget.h\"\n\nnamespace apvlv\n{\nFILE_TYPE_DEFINITION (\"Web\", ApvlvHTML, { \".htm\", \".html\" });\n\nusing namespace std;\n\nbool\nApvlvHTML::load (const string &filename)\n{\n  mUrl.setScheme (\"file\");\n  mUrl.setPath (QString::fromLocal8Bit (filename));\n  return true;\n}\n\nbool\nApvlvHTML::pageRenderToWebView (int pn, double zm, int rot, WebView *webview)\n{\n  webview->setZoomFactor (zm);\n  webview->load (mUrl);\n  return true;\n}\n\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/file/ApvlvHtm.h",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvHtm.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_HTM_H_\n#define _APVLV_HTM_H_\n\n#include <QUrl>\n\n#include \"ApvlvFile.h\"\n\nnamespace apvlv\n{\nclass ApvlvHTML : public File\n{\n  FILE_TYPE_DECLARATION (ApvlvHTML);\n\npublic:\n  bool load (const std::string &filename) override;\n\n  bool pageRenderToWebView (int pn, double zm, int rot,\n                            WebView *webview) override;\n\nprotected:\n  QUrl mUrl;\n};\n\n}\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/file/ApvlvImage.cc",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvImage.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include \"ApvlvImage.h\"\n\nnamespace apvlv\n{\n\nFILE_TYPE_DEFINITION (\"Web\", ApvlvIMAGE,\n                      { \".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\" });\n\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/file/ApvlvImage.h",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvImage.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_IMAGE_H_\n#define _APVLV_IMAGE_H_\n\n#include \"ApvlvHtm.h\"\n\nnamespace apvlv\n{\nclass ApvlvIMAGE : public ApvlvHTML\n{\n  FILE_TYPE_DECLARATION (ApvlvIMAGE);\n\npublic:\n  bool\n  pageIsOnlyImage (int pn) override\n  {\n    return true;\n  }\n};\n\n}\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/file/ApvlvLibreOffice.cc",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvOffice.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <LibreOfficeKit/LibreOfficeKit.hxx>\n#include <QDebug>\n#include <QDir>\n#include <QTemporaryFile>\n#include <filesystem>\n\n#include \"ApvlvLibreOffice.h\"\n\nnamespace apvlv\n{\nFILE_TYPE_DEFINITION (\"libreOffice\", ApvlvOFFICE,\n                      { \".doc\", \".docx\", \".xls\", \".xlsx\", \"ppt\", \"pptx\" });\n\nusing namespace std;\n\nmutex ApvlvOFFICE::mLokMutex;\nTokenDispatcher ApvlvOFFICE::mDispatcher{ 1, true };\nunique_ptr<lok::Office> ApvlvOFFICE::mOffice;\n\nbool\nApvlvOFFICE::load (const string &filename)\n{\n  auto filepath = filesystem::path (filename);\n  if (filepath.filename ().string ().starts_with (\"~\"))\n    {\n      qWarning () << \"filename: \" << QString::fromLocal8Bit (filename)\n                  << \" maybe a temporary file, skip.\";\n      return false;\n    }\n\n  auto token = mDispatcher.getToken (true);\n\n  initLokInstance ();\n\n  lock_guard<mutex> lk (mLokMutex);\n  mDoc = unique_ptr<lok::Document>{ mOffice->documentLoad (\n      filename.c_str ()) };\n  if (mDoc == nullptr)\n    return false;\n\n  mDoc->initializeForRendering ();\n  return true;\n}\n\nApvlvOFFICE::~ApvlvOFFICE ()\n{\n  auto token = mDispatcher.getToken (true);\n  lock_guard<mutex> lk (mLokMutex);\n  mDoc = nullptr;\n}\n\nint\nApvlvOFFICE::sum ()\n{\n  auto token = mDispatcher.getToken (true);\n  lock_guard<mutex> lk (mLokMutex);\n  auto num = mDoc->getParts ();\n  return num;\n}\n\nbool\nApvlvOFFICE::pageText (int pn, const Rectangle &rect, string &text)\n{\n  auto tmpname = QString (\"%1/apvlv.%2.txt\")\n                     .arg (QDir::temp ().path ())\n                     .arg (random ());\n\n  auto token = mDispatcher.getToken (false);\n  unique_lock<mutex> lk (mLokMutex);\n  mDoc->setPart (pn);\n  mDoc->saveAs (tmpname.toStdString ().c_str (), \"txt\");\n  lk.unlock ();\n  token.reset ();\n\n  QFile file (tmpname);\n  if (file.open (QIODeviceBase::ReadOnly) == false)\n    return false;\n\n  auto bytes = file.readAll ();\n  text.append (bytes.toStdString ());\n  file.close ();\n  file.remove ();\n  return true;\n}\n\nbool\nApvlvOFFICE::pageRenderToImage (int pn, double zm, int rot, QImage *pix)\n{\n  auto token = mDispatcher.getToken (true);\n  lock_guard<mutex> lk (mLokMutex);\n  mDoc->setPart (pn);\n  QTemporaryFile file;\n  if (file.open ())\n    {\n      mDoc->saveAs (file.fileName ().toStdString ().c_str (), \"png\");\n      *pix = QImage (file.fileName (), \"png\");\n      file.close ();\n    }\n  return true;\n}\n\nvoid\nApvlvOFFICE::initLokInstance ()\n{\n  lock_guard<mutex> lk (mLokMutex);\n  if (mOffice == nullptr)\n    {\n      auto lok_path = ApvlvParams::instance ()->getStringOrDefault (\n          \"lok_path\", DEFAULT_LOK_PATH);\n      mOffice\n          = unique_ptr<lok::Office>{ lok::lok_cpp_init (lok_path.c_str ()) };\n    }\n}\n\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/file/ApvlvLibreOffice.h",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvOffice.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_OFFICE_H_\n#define _APVLV_OFFICE_H_\n\n#include <LibreOfficeKit/LibreOfficeKit.hxx>\n#include <memory>\n\n#include \"ApvlvFile.h\"\n\nnamespace apvlv\n{\n\nconst char *const DEFAULT_LOK_PATH = \"/usr/lib64/libreoffice/program\";\n\nclass ApvlvOFFICE : public File\n{\n  FILE_TYPE_DECLARATION (ApvlvOFFICE);\n\npublic:\n  bool load (const std::string &filename) override;\n  ~ApvlvOFFICE () override;\n\n  int sum () override;\n\n  bool pageText (int pn, const Rectangle &rect, std::string &text) override;\n\n  bool pageRenderToImage (int pn, double zm, int rot, QImage *pix) override;\n\nprotected:\n  std::unique_ptr<lok::Document> mDoc;\n\nprivate:\n  static std::unique_ptr<lok::Office> mOffice;\n  static std::mutex mLokMutex;\n  static TokenDispatcher mDispatcher;\n\n  static void initLokInstance ();\n};\n\n}\n\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/file/ApvlvMuPdf.cc",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvPdf.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QMessageBox>\n#include <filesystem>\n#include <fstream>\n#include <mupdf/fitz.h>\n\n#include \"ApvlvMuPdf.h\"\n\nnamespace apvlv\n{\nusing namespace std;\n\nFILE_TYPE_DEFINITION (\"MuPDF\", ApvlvMuPDF,\n                      { \".pdf\", \".xps\", \".epub\", \".mobi\", \".fb2\", \".cbz\",\n                        \".svg\", \".txt\" });\n\nApvlvMuPDF::ApvlvMuPDF () : mDoc{ nullptr }\n{\n  mContext = fz_new_context (nullptr, nullptr, FZ_STORE_UNLIMITED);\n  fz_register_document_handlers (mContext);\n}\n\nApvlvMuPDF::~ApvlvMuPDF ()\n{\n  fz_drop_document (mContext, mDoc);\n  fz_drop_context (mContext);\n}\n\nbool\nApvlvMuPDF::load (const string &filename)\n{\n  mDoc = fz_open_document (mContext, filename.c_str ());\n  if (mDoc == nullptr)\n    {\n      return false;\n    }\n\n  generateIndex ();\n  return true;\n}\n\nSizeF\nApvlvMuPDF::pageSizeF (int pn, int rot)\n{\n  auto page = fz_load_page (mContext, mDoc, pn);\n  auto rect = fz_bound_page (mContext, page);\n  fz_drop_page (mContext, page);\n  SizeF sizef{ rect.x1 - rect.x0, rect.y1 - rect.y0 };\n  return sizef;\n}\n\nint\nApvlvMuPDF::sum ()\n{\n  auto pages = fz_count_pages (mContext, mDoc);\n  return pages;\n}\n\nbool\nApvlvMuPDF::pageIsOnlyImage (int pn)\n{\n  auto text_page\n      = fz_new_stext_page_from_page_number (mContext, mDoc, pn, nullptr);\n  if (text_page == nullptr)\n    {\n      return true;\n    }\n\n  auto only_image = (text_page->first_block == nullptr);\n\n  fz_drop_stext_page (mContext, text_page);\n  return only_image;\n}\n\nbool\nApvlvMuPDF::pageRenderToImage (int pn, double zm, int rot, QImage *pix)\n{\n  auto scale = fz_scale (static_cast<float> (zm), static_cast<float> (zm));\n  auto mat = fz_pre_rotate (scale, static_cast<float> (rot));\n  auto color = fz_device_rgb (mContext);\n  auto pixmap\n      = fz_new_pixmap_from_page_number (mContext, mDoc, pn, mat, color, 0);\n  if (pixmap == nullptr)\n    return false;\n\n  auto comments = mNote.getCommentsInPage (pn);\n  pageRenderComments (pn, pixmap, comments, mat);\n\n  QImage img{ pixmap->w, pixmap->h, QImage::Format_RGB32 };\n  for (auto y = 0; y < pixmap->h; ++y)\n    {\n      auto p = pixmap->samples + y * pixmap->stride;\n      for (auto x = 0; x < pixmap->w; ++x)\n        {\n          QColor c{ int (p[0]), int (p[1]), int (p[2]) };\n          img.setPixelColor (x, y, c);\n          p += pixmap->n;\n        }\n    }\n\n  fz_drop_pixmap (mContext, pixmap);\n\n  *pix = img;\n  return true;\n}\n\noptional<vector<Rectangle>>\nApvlvMuPDF::pageHighlight (int pn, const ApvlvPoint &pa, const ApvlvPoint &pb)\n{\n  auto options = fz_stext_options{};\n  auto text_page\n      = fz_new_stext_page_from_page_number (mContext, mDoc, pn, &options);\n  auto fa = fz_point{ static_cast<float> (pa.x), static_cast<float> (pa.y) };\n  auto fb = fz_point{ static_cast<float> (pb.x), static_cast<float> (pb.y) };\n  std::array<fz_quad, 1024> quad_array;\n  auto quads = fz_highlight_selection (\n      mContext, text_page, fa, fb, quad_array.data (), quad_array.size ());\n  fz_drop_stext_page (mContext, text_page);\n  if (quads == 0)\n    return nullopt;\n\n  auto rect_list = vector<Rectangle>{};\n  for (auto i = 0; i < quads; ++i)\n    {\n      auto quad = &quad_array[i];\n      Rectangle r{ quad->ul.x, quad->ul.y, quad->lr.x, quad->lr.y };\n      rect_list.emplace_back (r);\n    }\n  return rect_list;\n}\n\nbool\nApvlvMuPDF::pageText (int pn, const Rectangle &rect, string &text)\n{\n  auto text_page\n      = fz_new_stext_page_from_page_number (mContext, mDoc, pn, nullptr);\n  auto fzrect = fz_rect{\n    .x0 = static_cast<float> (rect.p1x),\n    .y0 = static_cast<float> (rect.p1y),\n    .x1 = static_cast<float> (rect.p2x),\n    .y1 = static_cast<float> (rect.p2y),\n  };\n  text = fz_copy_rectangle (mContext, text_page, fzrect, 0);\n  fz_drop_stext_page (mContext, text_page);\n  return true;\n}\n\nunique_ptr<WordListRectangle>\nApvlvMuPDF::pageSearch (int pn, const char *str)\n{\n  int hit;\n  std::array<fz_quad, 1024> quad_array;\n  auto count = fz_search_page_number (mContext, mDoc, pn, str, &hit,\n                                      quad_array.data (), quad_array.size ());\n  if (count == 0)\n    return nullptr;\n\n  auto list = make_unique<WordListRectangle> ();\n  for (auto i = 0; i < count; ++i)\n    {\n      WordRectangle rectangle;\n      rectangle.word = str;\n      auto quad = quad_array[i];\n      Rectangle rect{ quad.ul.x, quad.lr.y, quad.lr.x, quad.ul.y };\n      rectangle.rect_list.push_back (rect);\n      list->push_back (rectangle);\n    }\n  return list;\n}\n\nvoid\nApvlvMuPDF::pageRenderComments (int pn, fz_pixmap *pixmap,\n                                const vector<Comment> &comments,\n                                const fz_matrix &mat)\n{\n  if (comments.empty ())\n    return;\n\n  auto dev = fz_new_draw_device (mContext, mat, pixmap);\n  auto stroke = fz_new_stroke_state (mContext);\n  stroke->linewidth = 0.4;\n  fz_path *path = fz_new_path (mContext);\n  auto scale = fz_scale (1.0, 1.0);\n  std::array<float, 3> color = { 0.0, 0.0, 1.0 };\n\n  for (const auto &comment : comments)\n    {\n      ApvlvPoint pa{ comment.begin.x, comment.begin.y };\n      ApvlvPoint pb{ comment.end.x, comment.end.y };\n      auto rect_list = pageHighlight (pn, pa, pb);\n      if (rect_list->empty ())\n        continue;\n\n      for (auto const &rect : rect_list.value ())\n        {\n          fz_moveto (mContext, path, static_cast<float> (rect.p1x),\n                     static_cast<float> (rect.p2y));\n          fz_lineto (mContext, path, static_cast<float> (rect.p2x),\n                     static_cast<float> (rect.p2y));\n          fz_stroke_path (mContext, dev, path, stroke, scale,\n                          fz_device_rgb (mContext), color.data (), 0.8,\n                          fz_default_color_params);\n        }\n    }\n\n  fz_drop_path (mContext, path);\n  fz_drop_stroke_state (mContext, stroke);\n  fz_drop_device (mContext, dev);\n}\n\nvoid\nApvlvMuPDF::generateIndex ()\n{\n  mIndex = { \"\", 0, \"\", FileIndexType::FILE };\n\n  fz_outline *top_toc;\n  fz_try (mContext) top_toc = fz_load_outline (mContext, mDoc);\n  fz_catch (mContext)\n  {\n    qCritical () << \"load \" << mFilename << \" outline error\";\n    fz_report_error (mContext);\n    top_toc = nullptr;\n  }\n\n  if (top_toc == nullptr)\n    return;\n\n  auto toc = top_toc;\n  while (toc != nullptr)\n    {\n      auto child_index = FileIndex{};\n      generateIndexRecursively (child_index, toc);\n      mIndex.mChildrenIndex.push_back (child_index);\n      toc = toc->next;\n    }\n\n  fz_drop_outline (mContext, top_toc);\n}\n\nvoid\nApvlvMuPDF::generateIndexRecursively (FileIndex &index,\n                                      const fz_outline *outline)\n{\n  index.type = FileIndexType::PAGE;\n  if (outline->title)\n    index.title = outline->title;\n  index.page = fz_page_number_from_location (mContext, mDoc, outline->page);\n  if (outline->uri != nullptr)\n    {\n      index.path = outline->uri;\n      auto pos = index.path.find ('#');\n      if (pos != string::npos)\n        {\n          index.anchor = index.path.substr (pos);\n          index.path = index.path.substr (0, pos);\n        }\n      if (index.page == -1)\n        {\n          auto dest = fz_resolve_link (mContext, mDoc, outline->uri, nullptr,\n                                       nullptr);\n          index.page = fz_page_number_from_location (mContext, mDoc, dest);\n        }\n    }\n\n  auto toc = outline->down;\n  while (toc != nullptr)\n    {\n      auto child_index = FileIndex{};\n      generateIndexRecursively (child_index, toc);\n      index.mChildrenIndex.push_back (child_index);\n      toc = toc->next;\n    }\n}\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/file/ApvlvMuPdf.h",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvPdf.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_MUPDF_H_\n#define _APVLV_MUPDF_H_\n\n#include <mupdf/fitz.h>\n#include <vector>\n\n#include \"ApvlvFile.h\"\n\nnamespace apvlv\n{\n\nclass ApvlvMuPDF : public File\n{\n  FILE_TYPE_DECLARATION (ApvlvMuPDF);\n\npublic:\n  ApvlvMuPDF ();\n  ~ApvlvMuPDF () override;\n\n  bool load (const std::string &filename) override;\n\n  [[nodiscard]] DISPLAY_TYPE\n  getDisplayType () const override\n  {\n    return DISPLAY_TYPE::IMAGE;\n  }\n\n  SizeF pageSizeF (int page, int rot) override;\n\n  int sum () override;\n\n  bool pageIsOnlyImage (int pn) override;\n\n  bool pageRenderToImage (int pn, double zm, int rot, QImage *img) override;\n\n  std::optional<std::vector<Rectangle>>\n  pageHighlight (int pn, const ApvlvPoint &pa, const ApvlvPoint &pb) override;\n\n  bool pageText (int pn, const Rectangle &rect, std::string &text) override;\n\n  std::unique_ptr<WordListRectangle> pageSearch (int pn,\n                                                 const char *str) override;\n\nprivate:\n  fz_context *mContext;\n  fz_document *mDoc;\n\n  void pageRenderComments (int pn, fz_pixmap *pixmap,\n                           const std::vector<Comment> &comments,\n                           const fz_matrix &mat);\n  void generateIndex ();\n  void generateIndexRecursively (FileIndex &index, const fz_outline *outline);\n};\n}\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/file/ApvlvPopplerPdf.cc",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvPdf.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QInputDialog>\n#include <QMessageBox>\n#include <filesystem>\n#include <fstream>\n#include <qt6/poppler-qt6.h>\n\n#include \"ApvlvPopplerPdf.h\"\n#include \"ApvlvView.h\"\n\nnamespace apvlv\n{\nFILE_TYPE_DEFINITION (\"poppler\", ApvlvPopplerPDF, { \".pdf\" });\n\nusing namespace std;\nusing namespace Poppler;\n\nbool\nApvlvPopplerPDF::load (const string &filename)\n{\n  mDoc = Document::load (QString::fromLocal8Bit (filename));\n  if (mDoc == nullptr)\n    {\n      auto text\n          = QInputDialog::getText (nullptr, \"password\", \"input password\");\n      auto pass = QByteArray::fromStdString (text.toStdString ());\n      mDoc = Document::load (QString::fromStdString (filename), pass, pass);\n    }\n\n  if (mDoc == nullptr)\n    {\n      return false;\n    }\n\n  generateIndex ();\n  return true;\n}\n\nSizeF\nApvlvPopplerPDF::pageSizeF (int pn, int rot)\n{\n  auto page = mDoc->page (pn);\n  auto qsize = page->pageSizeF ();\n  if (rot == 0 || rot == 180)\n    {\n      return { qsize.width (), qsize.height () };\n    }\n  else\n    {\n      return { qsize.height (), qsize.width () };\n    }\n}\n\nint\nApvlvPopplerPDF::sum ()\n{\n  return mDoc ? mDoc->numPages () : 0;\n}\n\nunique_ptr<WordListRectangle>\nApvlvPopplerPDF::pageSearch (int pn, const char *str)\n{\n  if (mDoc == nullptr)\n    return nullptr;\n\n  auto page = mDoc->page (pn);\n  auto results = page->search (str);\n  if (results.empty ())\n    return nullptr;\n\n  auto poses = make_unique<WordListRectangle> ();\n  for (auto const &res : results)\n    {\n      WordRectangle wr;\n      wr.rect_list.push_back (\n          { res.left (), res.top (), res.right (), res.bottom () });\n      poses->push_back (wr);\n    }\n  return poses;\n}\n\nbool\nApvlvPopplerPDF::pageIsOnlyImage (int pn)\n{\n  auto page = mDoc->page (pn);\n  auto list = page->textList ();\n  return list.empty ();\n}\n\nbool\nApvlvPopplerPDF::pageRenderToImage (int pn, double zm, int rot, QImage *pix)\n{\n  if (mDoc == nullptr)\n    return false;\n\n  auto xres = 72.0 * zm;\n  auto yres = 72.0 * zm;\n\n  auto prot = Poppler::Page::Rotate0;\n  if (rot == 90)\n    prot = Poppler::Page::Rotate90;\n  if (rot == 180)\n    prot = Poppler::Page::Rotate180;\n  if (rot == 270)\n    prot = Poppler::Page::Rotate270;\n\n  auto page = mDoc->page (pn);\n  auto size = page->pageSizeF ();\n  auto image = page->renderToImage (xres, yres, 0, 0, size.width () * zm,\n                                    size.height () * zm, prot);\n  *pix = std::move (image);\n  return true;\n}\n\nbool\nApvlvPopplerPDF::generateIndex ()\n{\n  auto outlines = mDoc->outline ();\n  if (outlines.empty ())\n    return false;\n\n  mIndex = { \"\", 0, getFilename (), FileIndexType::FILE };\n  generateChildrenIndex (mIndex, outlines);\n  return true;\n}\n\nvoid\nApvlvPopplerPDF::generateChildrenIndex (FileIndex &root_index,\n                                        const QVector<OutlineItem> &outlines)\n{\n  for (auto const &outline : outlines)\n    {\n      FileIndex index{ outline.name ().toStdString (),\n                       outline.destination ()->pageNumber () - 1, \"\",\n                       FileIndexType::PAGE };\n      auto child_outlines = outline.children ();\n      generateChildrenIndex (index, child_outlines);\n      root_index.mChildrenIndex.emplace_back (index);\n    }\n}\n\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/file/ApvlvPopplerPdf.h",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvPdf.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_POPPLERPDF_H_\n#define _APVLV_POPPLERPDF_H_\n\n#include <qt6/poppler-qt6.h>\n\n#include \"ApvlvFile.h\"\n\nnamespace apvlv\n{\n\nclass ApvlvPopplerPDF : public File\n{\n  FILE_TYPE_DECLARATION (ApvlvPopplerPDF);\n\npublic:\n  bool load (const std::string &filename) override;\n\n  ~ApvlvPopplerPDF () override = default;\n\n  SizeF pageSizeF (int page, int rot) override;\n\n  int sum () override;\n\n  bool pageIsOnlyImage (int pn) override;\n\n  bool pageRenderToImage (int pn, double zm, int rot, QImage *img) override;\n\n  std::unique_ptr<WordListRectangle> pageSearch (int pn,\n                                                 const char *s) override;\n\nprivate:\n  bool generateIndex ();\n  void generateChildrenIndex (FileIndex &root_index,\n                              const QVector<Poppler::OutlineItem> &outlines);\n\n  std::unique_ptr<Poppler::Document> mDoc;\n};\n}\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/file/ApvlvQtPdf.cc",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvPdf.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QApplication>\n#include <QInputDialog>\n#include <QPainter>\n#include <QPdfPageNavigator>\n#include <QThread>\n#include <QtPdfWidgets/QPdfView>\n#include <filesystem>\n#include <fstream>\n\n#include \"ApvlvQtPdf.h\"\n#include \"ApvlvUtil.h\"\n\nnamespace apvlv\n{\nusing namespace std;\n\nFILE_TYPE_DEFINITION (\"QtPdf\", ApvlvPDF, { \".pdf\" });\n\nbool\nApvlvPDF::load (const string &filename)\n{\n  mDoc = make_unique<QPdfDocument> ();\n  mView = nullptr;\n  auto res = mDoc->load (QString::fromLocal8Bit (filename));\n  if (res == QPdfDocument::Error::IncorrectPassword)\n    {\n      if (QThread::currentThread () == QApplication::instance ()->thread ())\n        {\n          auto text\n              = QInputDialog::getText (nullptr, \"password\", \"input password\");\n          if (text.isEmpty ())\n            return false;\n\n          auto pass = QByteArray::fromStdString (text.toStdString ());\n          mDoc->setPassword (pass);\n          res = mDoc->load (QString::fromLocal8Bit (filename));\n        }\n      else\n        {\n          qWarning () << \"file: \" << filename << \" has password, skip !!!\";\n        }\n    }\n\n  if (res != QPdfDocument::Error::None)\n    {\n      return false;\n    }\n\n  mSearchModel = make_unique<QPdfSearchModel> ();\n  mSearchModel->setDocument (mDoc.get ());\n\n  generateIndex ();\n  return true;\n}\n\nPDFWidget *\nApvlvPDF::getWidget ()\n{\n  auto wid = new PDFWidget ();\n  wid->setFile (this);\n  return wid;\n}\n\nSizeF\nApvlvPDF::pageSizeF (int pn, int rot)\n{\n  auto qsize = mDoc->pagePointSize (pn);\n  if (rot == 0 || rot == 180)\n    {\n      return { qsize.width (), qsize.height () };\n    }\n  else\n    {\n      return { qsize.height (), qsize.width () };\n    }\n}\n\nint\nApvlvPDF::sum ()\n{\n  return mDoc ? mDoc->pageCount () : 0;\n}\n\nunique_ptr<WordListRectangle>\nApvlvPDF::pageSearch (int pn, const char *str)\n{\n  if (mDoc == nullptr)\n    return nullptr;\n\n  mSearchModel->setSearchString (str);\n  auto results = mSearchModel->resultsOnPage (pn);\n  if (results.empty ())\n    return nullptr;\n\n  auto poses = make_unique<WordListRectangle> ();\n  for (auto const &res : results)\n    {\n      WordRectangle word_rectangle;\n      for (auto const &rect : res.rectangles ())\n        {\n          word_rectangle.rect_list.push_back (\n              { rect.left (), rect.bottom (), rect.right (), rect.top () });\n        }\n      poses->push_back (word_rectangle);\n    }\n  return poses;\n}\n\nbool\nApvlvPDF::pageIsOnlyImage (int pn)\n{\n  auto sel = mDoc->getAllText (pn);\n  auto has_text = sel.isValid ();\n  return !has_text;\n}\n\nbool\nApvlvPDF::pageRenderToImage (int pn, double zm, int rot, QImage *pix)\n{\n  if (mDoc == nullptr)\n    return false;\n\n  using enum QPdfDocumentRenderOptions::Rotation;\n  auto sizeF = pageSizeF (pn, rot);\n  QSize image_size{ int (sizeF.width * zm), int (sizeF.height * zm) };\n  auto prot = None;\n  if (rot == 90)\n    prot = Clockwise90;\n  if (rot == 180)\n    prot = Clockwise180;\n  if (rot == 270)\n    prot = Clockwise270;\n  QPdfDocumentRenderOptions options{};\n  options.setRotation (prot);\n  options.setScaledSize (image_size);\n  *pix = mDoc->render (pn, image_size, options);\n\n  if (auto comments = mNote.getCommentsInPage (pn); !comments.empty ())\n    {\n      pageRenderComments (pn, pix, comments);\n    }\n\n  return true;\n}\n\nbool\nApvlvPDF::pageText (int pn, const Rectangle &rect, string &text)\n{\n  if (mDoc == nullptr)\n    return false;\n\n  auto selection = mDoc->getSelection (pn, { rect.p1x, rect.p1y },\n                                       { rect.p2x, rect.p2y });\n  text = selection.text ().toStdString ();\n  return true;\n}\n\nvoid\nApvlvPDF::pageRenderComments (int pn, QImage *img,\n                              const std::vector<Comment> &comments)\n{\n  auto model = make_unique<QPdfSearchModel> ();\n  model->setDocument (mDoc.get ());\n  QPainter painter (img);\n  painter.setPen (QPen (Qt::blue, 0.4));\n\n  // TODO\n  // search not work, need better impl\n  for (auto const &comment : comments)\n    {\n      model->setSearchString (QString::fromLocal8Bit (comment.quoteText));\n      auto links = model->resultsOnPage (pn);\n      if (links.empty ())\n        continue;\n\n      for (auto const &link : links)\n        {\n          auto rects = link.rectangles ();\n          auto brect = rects[0];\n          auto erect = rects[rects.count () - 1];\n          painter.drawLine (brect.x (), brect.y () + brect.height (),\n                            erect.x (), brect.y () + brect.height ());\n        }\n    }\n\n  painter.end ();\n}\n\nbool\nApvlvPDF::generateIndex ()\n{\n  auto bookmark_model = make_unique<QPdfBookmarkModel> ();\n  bookmark_model->setDocument (mDoc.get ());\n  mIndex = { \"\", 0, getFilename (), FileIndexType::FILE };\n  getIndexIter (mIndex, bookmark_model.get (), QModelIndex ());\n  return true;\n}\n\nvoid\nApvlvPDF::getIndexIter (FileIndex &file_index,\n                        const QPdfBookmarkModel *bookmark_model,\n                        const QModelIndex &parent)\n{\n  for (auto row = 0; row < bookmark_model->rowCount (parent); ++row)\n    {\n      auto index = bookmark_model->index (row, 0, parent);\n      auto title = bookmark_model->data (index, Qt::UserRole);\n      auto level = bookmark_model->data (index, 257);\n      auto page = bookmark_model->data (index, 258);\n      auto location = bookmark_model->data (index, 259);\n\n      FileIndex child_index (title.toString ().toStdString (), page.toInt (),\n                             \"\", FileIndexType::PAGE);\n      if (bookmark_model->hasChildren (index))\n        {\n          getIndexIter (child_index, bookmark_model, index);\n        }\n\n      file_index.mChildrenIndex.emplace_back (child_index);\n    }\n}\n\nvoid\nPDFWidget::setFile (File *file)\n{\n  mFile = file;\n  auto pdf = dynamic_cast<ApvlvPDF *> (mFile);\n  mPdfView.setDocument (pdf->mDoc.get ());\n  mPdfView.setSearchModel (pdf->mSearchModel.get ());\n}\n\nvoid\nPDFWidget::showPage (int p, double s)\n{\n  auto nav = mPdfView.pageNavigator ();\n  nav->jump (p, { 0, 0 });\n  scrollTo (s, 0.0);\n  mPageNumber = p;\n  mScrollValue = s;\n}\n\nvoid\nPDFWidget::showPage (int p, const string &anchor)\n{\n  auto nav = mPdfView.pageNavigator ();\n  nav->jump (p, { 0, 0 });\n  mPageNumber = p;\n  mScrollValue = 0;\n}\n\nvoid\nPDFWidget::setSearchSelect (int select)\n{\n  auto pdf = dynamic_cast<ApvlvPDF *> (mFile);\n  auto model = pdf->mSearchModel.get ();\n\n  auto doc_select = 0;\n  for (auto pn = 0; pn < mPageNumber; ++pn)\n    {\n      auto res = model->resultsOnPage (pn);\n      doc_select += static_cast<int> (res.size ());\n    }\n  doc_select += select;\n  mPdfView.setCurrentSearchResultIndex (doc_select);\n\n  auto link = model->resultAtIndex (doc_select);\n  auto scr = static_cast<int> (link.location ().y ());\n  mValScrollBar->setValue (scr);\n\n  qDebug (\"link: %d,{%f,%f}: select: %d\", link.page (), link.location ().x (),\n          link.location ().y (), doc_select);\n  mSearchSelect = select;\n}\n\nvoid\nPDFWidget::setZoomrate (double zm)\n{\n  mZoomrate = zm;\n  mPdfView.setZoomFactor (zm);\n}\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/file/ApvlvQtPdf.h",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvPdf.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_QTPDF_H_\n#define _APVLV_QTPDF_H_\n\n#include <QPdfBookmarkModel>\n#include <QPdfSearchModel>\n\n#include \"ApvlvFile.h\"\n#include \"ApvlvFileWidget.h\"\n\nnamespace apvlv\n{\n\nclass PDFWidget : public FileWidget\n{\npublic:\n  PDFWidget ()\n  {\n    mHalScrollBar = mPdfView.horizontalScrollBar ();\n    mValScrollBar = mPdfView.verticalScrollBar ();\n  }\n\n  [[nodiscard]] QWidget *\n  widget () override\n  {\n    return &mPdfView;\n  }\n\n  void setFile (File *file) override;\n\n  void showPage (int pn, double s) override;\n  void showPage (int pn, const std::string &anchor) override;\n\n  void setSearchSelect (int select) override;\n\n  void setZoomrate (double zm) override;\n\nprivate:\n  QPdfView mPdfView{};\n};\n\nclass ApvlvPDF : public File\n{\n  FILE_TYPE_DECLARATION (ApvlvPDF);\n\npublic:\n  bool load (const std::string &filename) override;\n\n  ~ApvlvPDF () override = default;\n\n  [[nodiscard]] DISPLAY_TYPE\n  getDisplayType () const override\n  {\n    return DISPLAY_TYPE::IMAGE;\n  }\n\n  PDFWidget *getWidget () override;\n\n  SizeF pageSizeF (int page, int rot) override;\n\n  int sum () override;\n\n  bool pageIsOnlyImage (int pn) override;\n\n  bool pageRenderToImage (int pn, double zm, int rot, QImage *img) override;\n\n  bool pageText (int, const Rectangle &rect, std::string &text) override;\n\n  std::unique_ptr<WordListRectangle> pageSearch (int pn,\n                                                 const char *s) override;\n\nprivate:\n  void pageRenderComments (int pn, QImage *img,\n                           const std::vector<Comment> &comments);\n  bool generateIndex ();\n  void getIndexIter (FileIndex &file_index,\n                     const QPdfBookmarkModel *bookmark_model,\n                     const QModelIndex &parent);\n\n  std::unique_ptr<QPdfDocument> mDoc;\n  std::unique_ptr<QPdfSearchModel> mSearchModel;\n\n  QWidget *mView;\n\n  friend class PDFWidget;\n};\n}\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/file/ApvlvTxt.cc",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvTxt.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <QFile>\n#include <QWebEngineSettings>\n#include <fstream>\n\n#include \"ApvlvTxt.h\"\n#include \"ApvlvWebViewWidget.h\"\n\nnamespace apvlv\n{\nFILE_TYPE_DEFINITION (\"Web\", ApvlvTXT, { \".txt\", \".text\" });\n\nbool\nApvlvTXT::pageText (int pn, const Rectangle &rect, std::string &text)\n{\n  QFile file (mUrl.path ());\n  if (file.open (QIODeviceBase::ReadOnly) == false)\n    return false;\n\n  auto bytes = file.readAll ();\n  text.append (bytes.toStdString ());\n  file.close ();\n  return true;\n}\n\nbool\nApvlvTXT::pageRenderToWebView (int pn, double zm, int rot, WebView *webview)\n{\n  auto settings = webview->settings ();\n  settings->setDefaultTextEncoding (\"UTF-8\");\n  return ApvlvHTML::pageRenderToWebView (pn, zm, rot, webview);\n}\n\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/file/ApvlvTxt.h",
    "content": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n */\n/* @CPPFILE ApvlvTxt.h\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#ifndef _APVLV_TXT_H_\n#define _APVLV_TXT_H_\n\n#include \"ApvlvHtm.h\"\n\nnamespace apvlv\n{\nclass ApvlvTXT : public ApvlvHTML\n{\n  FILE_TYPE_DECLARATION (ApvlvTXT);\n\npublic:\n  bool\n  load (const std::string &filename) override\n  {\n    return true;\n  }\n\n  bool pageText (int pn, const Rectangle &rect, std::string &text) override;\n\n  bool pageRenderToWebView (int pn, double zm, int rot,\n                            WebView *webview) override;\n  std::string\n  pathMimeType (const std::string &path) override\n  {\n    return \"text/plain\";\n  };\n};\n\n}\n#endif\n\n/* Local Variables: */\n/* mode: c++ */\n/* End: */\n"
  },
  {
    "path": "src/main.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/*@CPPFILE main.cpp Apvlv start at here\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <filesystem>\n\n#include <QApplication>\n#include <QCommandLineParser>\n#include <QDir>\n#include <QLocale>\n#include <QMessageBox>\n#include <QTranslator>\n#include <QWebEngineUrlScheme>\n\n#include \"ApvlvInfo.h\"\n#include \"ApvlvLog.h\"\n#include \"ApvlvParams.h\"\n#include \"ApvlvUtil.h\"\n#include \"ApvlvView.h\"\n\nusing namespace std;\nusing namespace apvlv;\n\n#if defined WIN32 && defined NDEBUG\n#pragma comment(linker, \"/subsystem:windows\")\n#pragma comment(linker, \"/ENTRY:mainCRTStartup\")\n#endif\n\nstatic void\nregisterUrlScheme ()\n{\n  QWebEngineUrlScheme scheme (\"apvlv\");\n  scheme.setSyntax (QWebEngineUrlScheme::Syntax::Path);\n  QWebEngineUrlScheme::registerScheme (scheme);\n}\n\nstatic void\nusageExit ()\n{\n  std::cout << PACKAGE_NAME << \" [options] paths\" << endl;\n  std::cout << endl;\n  std::cout << \"Options: \" << endl;\n  std::cout << \"\\t-h               display this and exit\\n\"\n               \"\\t-v               display version info and exit\\n\"\n               \"\\t-c [file]        set user configuration file\\n\"\n               \"\\t paths           document path list\"\n            << endl;\n  FileFactory::typeEngineDescription (std::cout);\n  std::cout << \"Please send bug report to \" << PACKAGE_BUGREPORT << endl;\n  exit (0);\n}\n\nstatic void\nversionExit ()\n{\n  fprintf (stdout,\n           \"%s %s-%s\\n\"\n           \"Please send bug report to %s\\n\"\n           \"\\n\",\n           PACKAGE_NAME, PACKAGE_VERSION, RELEASE, PACKAGE_BUGREPORT);\n  exit (0);\n}\n\nstatic list<string>\nparseCommandLine (const QCoreApplication &app)\n{\n  QCommandLineParser parser;\n\n  auto versionOption = QCommandLineOption (QStringList () << \"v\"\n                                                          << \"version\",\n                                           QObject::tr (\"version number\"));\n  parser.addOption (versionOption);\n\n  auto helpOption = QCommandLineOption (QStringList () << \"h\"\n                                                       << \"help\",\n                                        QObject::tr (\"help information\"));\n  parser.addOption (helpOption);\n\n  auto configFileOption\n      = QCommandLineOption (QStringList () << \"c\"\n                                           << \"config-file\",\n                            QObject::tr (\"config file\"), \"config\");\n  parser.addOption (configFileOption);\n\n  auto logFileOption = QCommandLineOption (QStringList () << \"l\"\n                                                          << \"log-file\",\n                                           QObject::tr (\"log file\"), \"log\");\n  parser.addOption (logFileOption);\n\n  parser.addPositionalArgument (\"path\", QObject::tr (\"document path\"));\n\n  parser.process (app);\n\n  if (parser.isSet (helpOption))\n    {\n      usageExit ();\n    }\n  if (parser.isSet (versionOption))\n    {\n      versionExit ();\n    }\n  if (parser.isSet (configFileOption))\n    {\n      auto value = parser.value (configFileOption);\n      IniFile = filesystem::absolute (value.toStdString ()).string ();\n    }\n  if (parser.isSet (logFileOption))\n    {\n      auto value = parser.value (logFileOption);\n      LogFile = filesystem::absolute (value.toStdString ()).string ();\n    }\n\n  /*\n   * load the global sys conf file\n   * */\n  auto sysIni = string (SYSCONFDIR) + \"/apvlvrc\";\n  auto params = ApvlvParams::instance ();\n  params->loadFile (sysIni);\n\n  /*\n   * load the user conf file\n   * */\n  qDebug () << \"using config: \" << IniFile;\n  ApvlvParams::instance ()->loadFile (IniFile);\n\n  list<string> paths;\n  auto pathlist = parser.positionalArguments ();\n  for (const auto &path : pathlist)\n    {\n      paths.emplace_back (path.toStdString ());\n    }\n\n  return paths;\n}\n\nstatic void\nloadTranslator (QTranslator &translator)\n{\n  map<string, string> lanuage_translator{ { \"Chinese\", \"zh_CN\" } };\n  auto lan = QLocale::system ().language ();\n  auto lanstr = QLocale::languageToString (lan).toStdString ();\n  if (lanuage_translator.find (lanstr) != lanuage_translator.end ())\n    {\n      auto lantrans = lanuage_translator[lanstr];\n      if (!translator.load (QString::fromLocal8Bit (lantrans),\n                            QString::fromLocal8Bit (Translations)))\n        {\n          qWarning () << \"Load i18n file failed, using English\";\n        }\n      else\n        {\n          QCoreApplication::installTranslator (&translator);\n        }\n    }\n}\n\nint\nmain (int argc, char *argv[])\n{\n  registerUrlScheme ();\n\n  QApplication app (argc, argv);\n\n  getRuntimePaths ();\n\n  QTranslator translator;\n  loadTranslator (translator);\n\n  ApvlvInfo::instance ()->loadFile (SessionFile);\n\n  auto paths = parseCommandLine (app);\n\n  NotesDir = ApvlvParams::instance ()->getGroupStringOrDefault (\n      \"notes\", \"dir\", NotesDir);\n\n  ApvlvLog::instance ()->setLogFile (LogFile);\n\n  string path = HelpPdf;\n  if (!paths.empty ())\n    {\n      path = paths.front ();\n      paths.pop_front ();\n    }\n  if (!filesystem::is_regular_file (path) && !filesystem::is_directory (path))\n    {\n      qFatal () << \"File '\" << path << \"' is not readable.\";\n      return 1;\n    }\n\n  ApvlvView sView (nullptr);\n  if (!sView.newTab (path))\n    {\n      exit (1);\n    }\n\n  while (!paths.empty ())\n    {\n      path = paths.front ();\n      paths.pop_front ();\n      auto apath = filesystem::absolute (path).string ();\n      if (!sView.newTab (apath))\n        {\n          qCritical () << \"Can't open document: \" << apath;\n        }\n    }\n\n  QApplication::exec ();\n\n  return 0;\n}\n\n// Local Variables:\n// mode: c++\n// End:\n"
  },
  {
    "path": "src/testNote.cc",
    "content": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2.0 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public\n * License along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n/* @CPPFILE ApvlvEditor.cc\n *\n *  Author: Alf <naihe2010@126.com>\n */\n\n#include <filesystem>\n#include <fstream>\n\n#include \"ApvlvNote.h\"\n\nnamespace apvlv\n{\nstd::string NotesDir = \"/tmp\";\n}\n\nusing namespace std;\nusing namespace apvlv;\nint\nmain (int argc, const char *argv[])\n{\n  auto note = Note{};\n  note.setScore (8.0);\n  note.addTag (\"abc\");\n  note.addTag (\"def\");\n  note.addTag (\"hijklmn\");\n  auto comment1 = Comment{};\n  comment1.commentText = \"lksdflkasdlfkaj\";\n  comment1.quoteText\n      = \"lksjdflkajdsklfjkaldklsdlkfasldkjf\\nlksdjfkla\\nsdflkasd\\n\";\n  comment1.begin = Location{ 0, 1.0, 2.0, 3 };\n  comment1.end = Location{ 0, 2.0, 3.0, 4 };\n  note.addComment (comment1);\n  auto comment2 = Comment{};\n  comment2.commentText = \"bbbbbbbbbbbbbbbbbbbbbbb\";\n  comment2.quoteText = \"bbbbbbbbbbbbbbbbbbbb\\nlksdjfkla\\nsdflkasd\\n\";\n  comment2.begin = Location{ 1, 1.0, 2.0, 5 };\n  comment2.end = Location{ 1, 2.0, 3.0, 8 };\n  note.addComment (comment2);\n  note.addReference (\"abcdehijklmn1\");\n  note.addReference (\"abcdehijklmn2\");\n  note.addReference (\"abcdehijklmn3\");\n  note.addLink (\"link1sldkfajlksdjfaldsk1\");\n  note.addLink (\"link1sldkfajlksdjfaldsk2\");\n  note.addLink (\"link1sldkfajlksdjfaldsk3\");\n  auto fos = ofstream{ \"/tmp/testNote1.md\" };\n  note.dumpStream (fos);\n  fos.close ();\n  auto fis = ifstream{ \"/tmp/testNote1.md\" };\n  auto note1 = Note{};\n  note1.loadStream (fis);\n  auto fos2 = ofstream{ \"/tmp/testNote2.md\" };\n  note1.dumpStream (fos2);\n  fos2.close ();\n\n  return 0;\n}\n"
  },
  {
    "path": "vcpkg-manifests/system-qt/vcpkg.json",
    "content": "{\n  \"name\": \"apvlv-system-qt\",\n  \"version\": \"0.7.0\",\n  \"description\": \"apvlv deps manifest when using system Qt (avoid building vcpkg qtbase)\",\n  \"license\": \"GPL-3.0\",\n  \"dependencies\": [\n    \"libmupdf\",\n    \"cmark\"\n  ],\n  \"features\": {\n    \"ocr\": {\n      \"description\": \"OCR support\",\n      \"dependencies\": [\"tesseract\"]\n    }\n  }\n}\n\n\n"
  },
  {
    "path": "vcpkg.json",
    "content": "{\n    \"name\": \"apvlv\",\n    \"version\": \"0.7.0\",\n    \"description\": \"Alf's PDF/DJVU/EPUB Viewer like Vim\",\n    \"homepage\": \"https://github.com/naihe2010/apvlv\",\n    \"license\": \"GPL-3.0\",\n    \"dependencies\": [\n\t\"qtbase\",\n\t\"qtwebengine\",\n\t\"quazip\",\n\t\"libmupdf\",\n\t{\n\t    \"name\": \"poppler\",\n\t    \"features\": [\"qt\"]\n\t},\n\t\"cmark\",\n\t\"tesseract\"\n    ]\n}\n"
  },
  {
    "path": "zh_CN.ts",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"zh_CN\">\n<context>\n    <name>ApvlvSearchDialog</name>\n    <message>\n        <source>Case sensitive</source>\n        <translation type=\"vanished\">大小写</translation>\n    </message>\n    <message>\n        <source>Regular expression</source>\n        <translation type=\"vanished\">正则表达式</translation>\n    </message>\n</context>\n<context>\n    <name>ApvlvView</name>\n    <message>\n        <source>File</source>\n        <translation type=\"obsolete\">文件</translation>\n    </message>\n    <message>\n        <source>Open</source>\n        <translation type=\"obsolete\">打开</translation>\n    </message>\n    <message>\n        <source>OpenDir</source>\n        <translation type=\"obsolete\">打开文件夹</translation>\n    </message>\n    <message>\n        <source>New Tab</source>\n        <translation type=\"obsolete\">新建标签页</translation>\n    </message>\n    <message>\n        <source>Close Tab</source>\n        <translation type=\"obsolete\">关闭标签页</translation>\n    </message>\n    <message>\n        <source>Quit</source>\n        <translation type=\"obsolete\">退出</translation>\n    </message>\n    <message>\n        <source>View</source>\n        <translation type=\"obsolete\">视图</translation>\n    </message>\n    <message>\n        <source>ToolBar</source>\n        <translation type=\"obsolete\">工具栏</translation>\n    </message>\n    <message>\n        <source>Horizontal Split</source>\n        <translation type=\"obsolete\">横向分割窗口</translation>\n    </message>\n    <message>\n        <source>Vertical Split</source>\n        <translation type=\"obsolete\">纵向分割窗口</translation>\n    </message>\n    <message>\n        <source>Close Split</source>\n        <translation type=\"obsolete\">关闭分割</translation>\n    </message>\n    <message>\n        <source>Navigate</source>\n        <translation type=\"obsolete\">导航</translation>\n    </message>\n    <message>\n        <source>Previous Page</source>\n        <translation type=\"obsolete\">前一页</translation>\n    </message>\n    <message>\n        <source>Next Page</source>\n        <translation type=\"obsolete\">下一页</translation>\n    </message>\n    <message>\n        <source>Tools</source>\n        <translation type=\"obsolete\">工具</translation>\n    </message>\n    <message>\n        <source>Help</source>\n        <translation type=\"obsolete\">帮助</translation>\n    </message>\n    <message>\n        <source>Toggle Content</source>\n        <translation type=\"obsolete\">显示/隐藏目录</translation>\n    </message>\n</context>\n<context>\n    <name>QObject</name>\n    <message>\n        <source>config file</source>\n        <translation>配置文件</translation>\n    </message>\n    <message>\n        <source>log file</source>\n        <translation>日志文件</translation>\n    </message>\n    <message>\n        <source>document path</source>\n        <translation>文档路径</translation>\n    </message>\n    <message>\n        <source>version number</source>\n        <translation>版本号</translation>\n    </message>\n    <message>\n        <source>help information</source>\n        <translation>帮助信息</translation>\n    </message>\n</context>\n<context>\n    <name>apvlv::ApvlvContent</name>\n    <message>\n        <source>title</source>\n        <translation type=\"vanished\">标题</translation>\n    </message>\n    <message>\n        <source>Filter</source>\n        <translation type=\"vanished\">过滤</translation>\n    </message>\n    <message>\n        <source>Expand All</source>\n        <translation type=\"vanished\">展开所有</translation>\n    </message>\n    <message>\n        <source>Collapse All</source>\n        <translation type=\"vanished\">收起所有</translation>\n    </message>\n    <message>\n        <source>Refresh</source>\n        <translation type=\"vanished\">刷新</translation>\n    </message>\n    <message>\n        <source>Sort By Title</source>\n        <translation type=\"vanished\">以标题排序</translation>\n    </message>\n    <message>\n        <source>Sort By Modified Time</source>\n        <translation type=\"vanished\">以更改时间排序</translation>\n    </message>\n    <message>\n        <source>Sort By File Size</source>\n        <translation type=\"vanished\">以文件大小排序</translation>\n    </message>\n    <message>\n        <source>size</source>\n        <translation type=\"vanished\">大小</translation>\n    </message>\n    <message>\n        <source>modified time</source>\n        <translation type=\"vanished\">更改时间</translation>\n    </message>\n    <message>\n        <source>Title</source>\n        <translation type=\"vanished\">标题</translation>\n    </message>\n    <message>\n        <source>File Size</source>\n        <translation type=\"vanished\">文件大小</translation>\n    </message>\n    <message>\n        <source>Modified Time</source>\n        <translation type=\"vanished\">更改时间</translation>\n    </message>\n    <message>\n        <source>None</source>\n        <translation type=\"vanished\">无</translation>\n    </message>\n    <message>\n        <source>Filter Title</source>\n        <translation type=\"vanished\">过滤标题</translation>\n    </message>\n    <message>\n        <source>Filter File Name</source>\n        <translation type=\"vanished\">过滤文件名</translation>\n    </message>\n    <message>\n        <source>Filter File Size &gt;=</source>\n        <translation type=\"vanished\">过滤文件&gt;=</translation>\n    </message>\n    <message>\n        <source>Filter FileSize &lt;=</source>\n        <translation type=\"vanished\">过滤文件&lt;=</translation>\n    </message>\n    <message>\n        <source>Filter Modified Time &gt;=</source>\n        <translation type=\"vanished\">过滤更改时间&gt;=</translation>\n    </message>\n    <message>\n        <source>Filter Modified Time &lt;=</source>\n        <translation type=\"vanished\">过滤更改时间&lt;=</translation>\n    </message>\n    <message>\n        <source>Filter Type is invalid</source>\n        <translation type=\"vanished\">过滤类型错误</translation>\n    </message>\n    <message>\n        <source>Confirm</source>\n        <translation type=\"vanished\">确认</translation>\n    </message>\n    <message>\n        <source>Delete This File</source>\n        <translation type=\"vanished\">删除这个文件</translation>\n    </message>\n    <message>\n        <source>Will delete the \n%1, confirm ?</source>\n        <translation type=\"vanished\">将要删除%1，确定吗？</translation>\n    </message>\n    <message>\n        <source>Input new name of %1</source>\n        <translation type=\"vanished\">输入%1的新文件名</translation>\n    </message>\n    <message>\n        <source>Rename</source>\n        <translation type=\"vanished\">重命名</translation>\n    </message>\n    <message>\n        <source>Rename %1 to %2 failed</source>\n        <translation type=\"vanished\">重命名%1为%2失败</translation>\n    </message>\n    <message>\n        <source>Warning</source>\n        <translation type=\"vanished\">警告</translation>\n    </message>\n    <message>\n        <source>Rename File</source>\n        <translation type=\"vanished\">重命名文件</translation>\n    </message>\n    <message>\n        <source>Delete File</source>\n        <translation type=\"vanished\">删除文件</translation>\n    </message>\n</context>\n<context>\n    <name>apvlv::ApvlvCore</name>\n    <message>\n        <source>no content</source>\n        <translation type=\"vanished\">没有目录</translation>\n    </message>\n    <message>\n        <source>the file has no content, if still display content?</source>\n        <oldsource>this file has no content, if close the content?</oldsource>\n        <translation type=\"vanished\">此文件没有目录，是否仍然显示目录？</translation>\n    </message>\n</context>\n<context>\n    <name>apvlv::ApvlvFrame</name>\n    <message>\n        <source>Previous Page</source>\n        <translation type=\"vanished\">前一页</translation>\n    </message>\n    <message>\n        <source>Next Page</source>\n        <translation type=\"vanished\">下一页</translation>\n    </message>\n    <message>\n        <source>Default</source>\n        <translation>默认</translation>\n    </message>\n    <message>\n        <source>Fit Width</source>\n        <translation>适应宽度</translation>\n    </message>\n    <message>\n        <source>Fit Height</source>\n        <translation>适应高度</translation>\n    </message>\n    <message>\n        <source>Custom</source>\n        <translation>自定义</translation>\n    </message>\n    <message>\n        <source>text in clipboard</source>\n        <translation>剪贴板文本</translation>\n    </message>\n</context>\n<context>\n    <name>apvlv::ApvlvSearchDialog</name>\n    <message>\n        <source>Case sensitive</source>\n        <translation type=\"vanished\">大小写</translation>\n    </message>\n    <message>\n        <source>Regular expression</source>\n        <translation type=\"vanished\">正则表达式</translation>\n    </message>\n    <message>\n        <source>...</source>\n        <translation type=\"vanished\">……</translation>\n    </message>\n    <message>\n        <source>Find Directory: </source>\n        <translation type=\"vanished\">查找目录：</translation>\n    </message>\n</context>\n<context>\n    <name>apvlv::ApvlvToolStatus</name>\n    <message>\n        <source>Previous Page</source>\n        <translation>前一页</translation>\n    </message>\n    <message>\n        <source>Next Page</source>\n        <translation>后一页</translation>\n    </message>\n    <message>\n        <source>Zoom In</source>\n        <translation>放大</translation>\n    </message>\n    <message>\n        <source>Zoom Out</source>\n        <translation>缩小</translation>\n    </message>\n    <message>\n        <source>OCR Copy</source>\n        <translation>OCR复制</translation>\n    </message>\n    <message>\n        <source>OCR Parse</source>\n        <translation>OCR解析</translation>\n    </message>\n</context>\n<context>\n    <name>apvlv::ApvlvView</name>\n    <message>\n        <source>File</source>\n        <translatorcomment>文件</translatorcomment>\n        <translation>文件</translation>\n    </message>\n    <message>\n        <source>Open</source>\n        <translatorcomment>打开</translatorcomment>\n        <translation>打开</translation>\n    </message>\n    <message>\n        <source>Quit</source>\n        <translatorcomment>退出</translatorcomment>\n        <translation>退出</translation>\n    </message>\n    <message>\n        <source>Edit</source>\n        <translatorcomment>编辑</translatorcomment>\n        <translation>编辑</translation>\n    </message>\n    <message>\n        <source>Navigate</source>\n        <translatorcomment>导航</translatorcomment>\n        <translation>导航</translation>\n    </message>\n    <message>\n        <source>Tools</source>\n        <translatorcomment>工具</translatorcomment>\n        <translation>工具</translation>\n    </message>\n    <message>\n        <source>Help</source>\n        <translatorcomment>帮助</translatorcomment>\n        <translation>帮助</translation>\n    </message>\n    <message>\n        <source>Next Page</source>\n        <translatorcomment>下一页</translatorcomment>\n        <translation>下一页</translation>\n    </message>\n    <message>\n        <source>New Tab</source>\n        <oldsource>new tab</oldsource>\n        <translation>新建标签页</translation>\n    </message>\n    <message>\n        <source>Close Tab</source>\n        <oldsource>close tab</oldsource>\n        <translation>关闭标签页</translation>\n    </message>\n    <message>\n        <source>View</source>\n        <translation>视图</translation>\n    </message>\n    <message>\n        <source>ToolBar</source>\n        <translation>工具栏</translation>\n    </message>\n    <message>\n        <source>Horizontal Split</source>\n        <translation>横向分割窗口</translation>\n    </message>\n    <message>\n        <source>Vertical Split</source>\n        <translation>纵向分割窗口</translation>\n    </message>\n    <message>\n        <source>Close Split</source>\n        <translation>关闭分割</translation>\n    </message>\n    <message>\n        <source>Previous Page</source>\n        <translation>前一页</translation>\n    </message>\n    <message>\n        <source>Toggle Content</source>\n        <translation type=\"vanished\">显示/隐藏目录</translation>\n    </message>\n    <message>\n        <source>OpenDir</source>\n        <translatorcomment>打开文件夹</translatorcomment>\n        <translation>打开文件夹</translation>\n    </message>\n    <message>\n        <source>Search</source>\n        <translation>查找</translation>\n    </message>\n    <message>\n        <source>Advanced Search</source>\n        <translation>高级查找</translation>\n    </message>\n    <message>\n        <source>Back Search</source>\n        <translation>反向查找</translation>\n    </message>\n    <message>\n        <source>StatusBar</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Dired</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>input url: </source>\n        <translation>输入网址：</translation>\n    </message>\n    <message>\n        <source>OpenUrl</source>\n        <translation>打开网址</translation>\n    </message>\n    <message>\n        <source>Toggle Directory</source>\n        <translation>切换目录</translation>\n    </message>\n</context>\n<context>\n    <name>apvlv::Directory</name>\n    <message>\n        <source>Title</source>\n        <translation>标题</translation>\n    </message>\n    <message>\n        <source>Modified Time</source>\n        <translation>更改时间</translation>\n    </message>\n    <message>\n        <source>File Size</source>\n        <translation>文件大小</translation>\n    </message>\n    <message>\n        <source>Sort By Title</source>\n        <translation>以标题排序</translation>\n    </message>\n    <message>\n        <source>Sort By Modified Time</source>\n        <translation>以更改时间排序</translation>\n    </message>\n    <message>\n        <source>Sort By File Size</source>\n        <translation>以文件大小排序</translation>\n    </message>\n    <message>\n        <source>Filter Title</source>\n        <translation>过滤标题</translation>\n    </message>\n    <message>\n        <source>Filter File Name</source>\n        <translation>过滤文件名</translation>\n    </message>\n    <message>\n        <source>Filter Modified Time &gt;=</source>\n        <translation>过滤更改时间&gt;=</translation>\n    </message>\n    <message>\n        <source>Filter Modified Time &lt;=</source>\n        <translation>过滤更改时间&lt;=</translation>\n    </message>\n    <message>\n        <source>Filter File Size &gt;=</source>\n        <translation>过滤文件&gt;=</translation>\n    </message>\n    <message>\n        <source>Filter FileSize &lt;=</source>\n        <translation type=\"vanished\">过滤文件&lt;=</translation>\n    </message>\n    <message>\n        <source>Refresh</source>\n        <translation>刷新</translation>\n    </message>\n    <message>\n        <source>Expand All</source>\n        <translation>展开所有</translation>\n    </message>\n    <message>\n        <source>Collapse All</source>\n        <translation>收起所有</translation>\n    </message>\n    <message>\n        <source>Input new name of %1</source>\n        <translation>输入%1的新文件名</translation>\n    </message>\n    <message>\n        <source>Rename</source>\n        <translation>重命名</translation>\n    </message>\n    <message>\n        <source>Rename %1 to %2 failed</source>\n        <translation>重命名%1为%2失败</translation>\n    </message>\n    <message>\n        <source>Warning</source>\n        <translation>警告</translation>\n    </message>\n    <message>\n        <source>Will delete the \n%1, confirm ?</source>\n        <translation>将要删除%1，确定吗？</translation>\n    </message>\n    <message>\n        <source>Confirm</source>\n        <translation>确认</translation>\n    </message>\n    <message>\n        <source>Filter Type is invalid</source>\n        <translation>过滤类型错误</translation>\n    </message>\n    <message>\n        <source>Rename File</source>\n        <translation>重命名文件</translation>\n    </message>\n    <message>\n        <source>Delete File</source>\n        <translation>删除文件</translation>\n    </message>\n    <message>\n        <source>Tags</source>\n        <translation>标签</translation>\n    </message>\n    <message>\n        <source>Score</source>\n        <translation>评分</translation>\n    </message>\n    <message>\n        <source>Sort By Tags</source>\n        <translation>以标签排序</translation>\n    </message>\n    <message>\n        <source>Sort By Score</source>\n        <translation>以评分排序</translation>\n    </message>\n    <message>\n        <source>Filter File Size &lt;=</source>\n        <translation>过滤文件&lt;=</translation>\n    </message>\n    <message>\n        <source>Filter Tag</source>\n        <translation>过滤标签</translation>\n    </message>\n    <message>\n        <source>Filter Score &gt;=</source>\n        <translation>过滤评分&gt;=</translation>\n    </message>\n    <message>\n        <source>Filter Score &lt;=</source>\n        <translation>过滤评分&lt;=</translation>\n    </message>\n    <message>\n        <source>load note of %s error</source>\n        <translation>加载%s的笔记错误</translation>\n    </message>\n    <message>\n        <source>error</source>\n        <translation>错误</translation>\n    </message>\n</context>\n<context>\n    <name>apvlv::DiredDialog</name>\n    <message>\n        <source>Regular expression</source>\n        <translation type=\"obsolete\">正则表达式</translation>\n    </message>\n    <message>\n        <source>Find Directory: </source>\n        <translation type=\"obsolete\">查找目录：</translation>\n    </message>\n    <message>\n        <source>...</source>\n        <translation type=\"obsolete\">……</translation>\n    </message>\n</context>\n<context>\n    <name>apvlv::ImageContainer</name>\n    <message>\n        <source>Copy</source>\n        <translation>复制</translation>\n    </message>\n    <message>\n        <source>Underline</source>\n        <translation>划线</translation>\n    </message>\n    <message>\n        <source>Comment</source>\n        <translation>注释</translation>\n    </message>\n    <message>\n        <source>Input</source>\n        <translation>输入</translation>\n    </message>\n</context>\n<context>\n    <name>apvlv::NoteDialog</name>\n    <message>\n        <source>tag</source>\n        <translation type=\"vanished\">标签</translation>\n    </message>\n    <message>\n        <source>input tag:</source>\n        <translation type=\"vanished\">输入标签：</translation>\n    </message>\n    <message>\n        <source>OK</source>\n        <translation>确定</translation>\n    </message>\n    <message>\n        <source>Cancel</source>\n        <translation>取消</translation>\n    </message>\n    <message>\n        <source>Input tag:</source>\n        <translation>输入标签：</translation>\n    </message>\n</context>\n<context>\n    <name>apvlv::SearchDialog</name>\n    <message>\n        <source>Case sensitive</source>\n        <translation>区分大小写</translation>\n    </message>\n    <message>\n        <source>Regular expression</source>\n        <translation>正则表达式</translation>\n    </message>\n    <message>\n        <source>Find Directory: </source>\n        <translation>查找目录：</translation>\n    </message>\n    <message>\n        <source>...</source>\n        <translation>……</translation>\n    </message>\n</context>\n<context>\n    <name>apvlv::WebView</name>\n    <message>\n        <source>Copy</source>\n        <translation>复制</translation>\n    </message>\n    <message>\n        <source>Underline</source>\n        <translation>划线</translation>\n    </message>\n    <message>\n        <source>Comment</source>\n        <translation>注释</translation>\n    </message>\n    <message>\n        <source>Input</source>\n        <translation>输入</translation>\n    </message>\n</context>\n</TS>\n"
  }
]